<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Ghost in the data</title><link>https://ghostinthedata.info/posts/</link><description>Ghost in the data</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Ghost in the data</copyright><lastBuildDate>Sat, 18 Jul 2026 09:00:00 +1000</lastBuildDate><atom:link href="https://ghostinthedata.info/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>SQL Tells You What. Comments Tell You Why.</title><link>https://ghostinthedata.info/posts/2026/2026-06-06-code-tells-what-comments-why/</link><pubDate>Sat, 06 Jun 2026 09:00:00 +1000</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-06-06-code-tells-what-comments-why/</guid><author>Chris Hillman</author><description>SQL is a declarative language — it tells you what the query does, never why. Here's why that distinction matters more in data engineering than anywhere else.</description><content:encoded>&lt;p>The best SQL doesn&amp;rsquo;t need comments. Write meaningful CTE names, descriptive aliases, clear column labels — and a skilled reader will follow your logic without a single annotation. That&amp;rsquo;s the right instinct.&lt;/p>
&lt;p>It&amp;rsquo;s also only half right.&lt;/p>
&lt;p>SQL is a declarative language. You&amp;rsquo;re not writing &lt;em>how&lt;/em> the database retrieves your data; you&amp;rsquo;re writing &lt;em>what&lt;/em> you want. That&amp;rsquo;s a useful distinction, because &amp;ldquo;what&amp;rdquo; and &amp;ldquo;why&amp;rdquo; are very different questions, and SQL can answer exactly one of them.&lt;/p>
&lt;p>A query can be perfectly, elegantly readable and still be completely opaque about the reason it exists. The name of your CTE, no matter how well chosen, cannot explain the business decision that gave birth to it.&lt;/p>
&lt;hr>
&lt;h3 id="the-self-documenting-ceiling">The self-documenting ceiling&lt;/h3>
&lt;p>Consider this query:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(order_total) &lt;span style="color:#66d9ef">AS&lt;/span> lifetime_value,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATEDIFF(&lt;span style="color:#66d9ef">day&lt;/span>, &lt;span style="color:#66d9ef">MIN&lt;/span>(order_date), &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> customer_age_days
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#66d9ef">day&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">90&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> status_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">9&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Clean SQL. No ambiguity about &lt;em>what&lt;/em> it does. But try answering these questions from the code alone:&lt;/p>
&lt;p>Why 90 days? Why not 60, or 180, or the full history? Is 90 the standard customer lifetime window, a finance reporting period, the SLA in a merchant agreement, or the number someone picked in a meeting four years ago and nobody has questioned since?&lt;/p>
&lt;p>What are status codes 1, 2, and 9? You might guess from context — pending, draft, cancelled — but you&amp;rsquo;re guessing. More importantly: why are they excluded? Is this a business rule about what counts as &amp;ldquo;real&amp;rdquo; revenue, or a data quality workaround because those status codes appear when an upstream webhook fires twice?&lt;/p>
&lt;p>These are not trivial questions. The 90-day cutoff defines what &amp;ldquo;active customer&amp;rdquo; means across your entire reporting layer. The excluded status codes determine what gets counted as revenue. Change either without understanding the original intent, and you&amp;rsquo;re not refactoring — you&amp;rsquo;re silently redefining business logic that stakeholders are relying on.&lt;/p>
&lt;p>Better SQL helps, but only so far:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(order_total) &lt;span style="color:#66d9ef">AS&lt;/span> lifetime_value,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATEDIFF(&lt;span style="color:#66d9ef">day&lt;/span>, &lt;span style="color:#66d9ef">MIN&lt;/span>(order_date), &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> customer_age_days
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#66d9ef">day&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">90&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> status_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#75715e">-- pending
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#75715e">-- draft
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#ae81ff">9&lt;/span> &lt;span style="color:#75715e">-- cancelled
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is better. You&amp;rsquo;ve replaced the mystery with labels. But inline labels aren&amp;rsquo;t explanations — they&amp;rsquo;re just naming the what more explicitly. You still don&amp;rsquo;t know why.&lt;/p>
&lt;hr>
&lt;h3 id="what-sql-literally-cannot-tell-you">What SQL literally cannot tell you&lt;/h3>
&lt;p>SQL cannot explain why the program was written the way it was. It cannot discuss the reasons certain alternative approaches were taken. It cannot tell you that the business definition changed, that the upstream system is broken, or that this logic exists specifically to handle a problem that was supposed to be fixed in Q2 and wasn&amp;rsquo;t.&lt;/p>
&lt;p>Here&amp;rsquo;s the kind of context that belongs in a comment:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Customer lifetime window: 90-day lookback aligns with the SLA in the Merchant Agreement (v3.2).
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Reviewed and confirmed with Finance, Dec 2024. Don&amp;#39;t change without checking with that team first.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- status_codes 1, 2, 9 excluded per the order lifecycle defined in the legacy Salesforce migration.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- These codes appear as artefacts when the Salesforce sync fires before the order is confirmed.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- The upstream fix was deprioritised (JIRA: DATA-3841). Removing this filter will cause double-counting.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(order_total) &lt;span style="color:#66d9ef">AS&lt;/span> lifetime_value,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATEDIFF(&lt;span style="color:#66d9ef">day&lt;/span>, &lt;span style="color:#66d9ef">MIN&lt;/span>(order_date), &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> customer_age_days
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#66d9ef">day&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">90&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> status_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">9&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That comment block is five lines. It contains: the business rationale, the stakeholder who owns the decision, the date it was confirmed, the upstream system causing the problem, the known issue reference, and the consequence of removing the filter. None of that information is recoverable from the SQL. All of it is load-bearing.&lt;/p>
&lt;hr>
&lt;h3 id="the-deduplication-that-nobody-remembers-adding">The deduplication that nobody remembers adding&lt;/h3>
&lt;p>Have you seen this line before:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>QUALIFY ROW_NUMBER() OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id, order_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> ingested_at &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The SQL is completely transparent about what it does: take the most recently ingested row for each customer/order combination. What it cannot tell you is why duplicates exist in the first place, whether the source of those duplicates has been fixed, whether this deduplication logic is still necessary, or whether &amp;ldquo;most recently ingested&amp;rdquo; is actually the right tiebreaker for your use case.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Deduplicating on customer_id + order_id to handle duplicate webhook events from Eftpos.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Eftpos retries failed webhooks, which can fire the same order_created event multiple times.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Taking the latest ingested_at gives us the most recent event state (status, amount, etc.)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Revisit if we adopt Eftpos&amp;#39;s idempotency keys at the integration layer — this dedup may become redundant.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Background: this logic was added after the October 2024 incident (post-mortem in Confluence).
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>QUALIFY ROW_NUMBER() OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id, order_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> ingested_at &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The second version tells the reader everything they need to know to maintain this safely: the upstream cause, the logic behind the tiebreaker, when to reconsider it, and where to find the history. Without that comment, every future engineer who touches this code has to reverse-engineer context that no longer exists anywhere.&lt;/p>
&lt;hr>
&lt;h3 id="when-two-models-define-the-same-word-differently">When two models define the same word differently&lt;/h3>
&lt;p>This one doesn&amp;rsquo;t generate an error. It generates confusion at the worst possible moment.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- IMPORTANT: &amp;#39;churned&amp;#39; here means &amp;gt;90 days since last order.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- This differs from mkt_customer_segments, which uses &amp;gt;60 days for winback campaign targeting.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Both definitions are intentional. Customer Success uses 90 days because cohort analysis
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- showed that 30% of &amp;#34;60-day churned&amp;#34; customers reorder organically within the next 30 days
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- and shouldn&amp;#39;t receive a discount. Marketing uses the stricter threshold to maximise winback opportunities.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- The discrepancy is documented and known. Do not &amp;#34;fix&amp;#34; one to match the other.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> days_since_last_order &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">30&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;active&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> days_since_last_order &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">90&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;at_risk&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;churned&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> customer_segment
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Without that comment, someone eventually &amp;ldquo;cleans up&amp;rdquo; one of the two definitions because having different churn thresholds in two models looks like a bug. They&amp;rsquo;re wrong, and they&amp;rsquo;re about to break a winback campaign that&amp;rsquo;s been performing well for six months.&lt;/p>
&lt;p>The SQL cannot warn them. The comment can.&lt;/p>
&lt;hr>
&lt;h3 id="dbt-descriptions-arent-exempt">dbt descriptions aren&amp;rsquo;t exempt&lt;/h3>
&lt;p>dbt gives you a proper home for the &amp;ldquo;why&amp;rdquo;: model descriptions and column-level descriptions in your YAML.&lt;/p>
&lt;p>Bad:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">fct_orders&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;The orders fact table. Contains order data.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_segment&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;The customer segment based on days since last order.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That description restates what the model name already says. It is a comment-shaped void.&lt;/p>
&lt;p>Good:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">fct_orders&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &amp;gt;&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Order-level fact table used as the single source of truth for revenue reporting.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Revenue excludes test accounts (prefixed &amp;#39;INTERNAL_&amp;#39;) and gift card redemptions,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> per the Finance revenue recognition policy agreed January 2025.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> See Confluence: Revenue Recognition Standards v3.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Do not use this model for marketing attribution — use mkt_attributed_revenue instead,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> which applies different channel logic.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_segment&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &amp;gt;&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Lifecycle segment defined by the Customer Success team, Q1 2025.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Thresholds (30/90 days) were derived from cohort analysis showing a median
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> reorder window of 28 days. Intentionally differs from the Marketing segment
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> definition in mkt_customer_segments, which uses a 60-day churn threshold
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> for winback campaign purposes. Both are correct for their respective use cases.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That description does something a column name never can: it tells you who owns the definition, when it was set, what analysis underpins it, and — critically — where the definition intentionally diverges from a similar field elsewhere in the warehouse. That last part is the thing that saves someone three hours of confused Teams messages.&lt;/p>
&lt;hr>
&lt;h3 id="the-archaeology-problem">The archaeology problem&lt;/h3>
&lt;p>Data teams inherit codebases. It happens constantly, and it will keep happening. Engineers leave. Consultants finish their contracts. Reorgs shuffle ownership. What gets left behind is the SQL, and whatever context wasn&amp;rsquo;t written down is gone.&lt;/p>
&lt;p>Good SQL survives the handover. It&amp;rsquo;s readable, consistent, and correct. But &amp;ldquo;readable&amp;rdquo; in the sense of mechanically parseable is not the same as &amp;ldquo;understandable&amp;rdquo; in the sense of knowing what business problem the code was solving, whose decision it was, and what you&amp;rsquo;d need to change if that decision changed.&lt;/p>
&lt;p>Comments are how you write for the engineer who inherits this in two years. They&amp;rsquo;re how you write for yourself in six months. They&amp;rsquo;re how you ensure that a working pipeline stays working through the context collapse that happens every time anyone leaves a team.&lt;/p>
&lt;p>SQL can only tell you what. Try not to shortchange the people reading it in either direction.&lt;/p></content:encoded><category>Data Engineering</category><category>Data Quality</category><category>SQL</category><category>dbt</category><category>Documentation</category><category>Data Quality</category><category>Code Comments</category><category>Data Pipelines</category><category>Best Practices</category></item><item><title>Don't Go Dark: Visibility Is a Data Engineering Skill</title><link>https://ghostinthedata.info/posts/2026/2026-05-23-dont-go-dark/</link><pubDate>Sat, 23 May 2026 09:00:00 +1000</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-05-23-dont-go-dark/</guid><author>Chris Hillman</author><description>Jeff Atwood wrote 'Don't Go Dark' for software engineers in 2008. The advice didn't reach us. Here's what it means for data engineers navigating long migrations, invisible pipelines, and distributed teams.</description><content:encoded>&lt;p>There&amp;rsquo;s a specific kind of silence in data engineering that I&amp;rsquo;ve learned to fear.&lt;/p>
&lt;p>Not the silence of a system that&amp;rsquo;s working well. Not the comfortable quiet of a team in flow. I mean the silence of a project that&amp;rsquo;s been running for three weeks and you still can&amp;rsquo;t point to a single visible thing it has produced. The kind of silence where, if your manager stopped you in the hallway and asked &amp;ldquo;how&amp;rsquo;s that migration going?&amp;rdquo;, you&amp;rsquo;d say &amp;ldquo;fine&amp;rdquo; because saying anything more accurate would require explaining things you haven&amp;rsquo;t fully articulated yet — even to yourself.&lt;/p>
&lt;p>I watched a talented engineer do this once. Let&amp;rsquo;s call her Mia. She&amp;rsquo;d been handed a significant refactor: six months of accumulated technical debt in a set of dbt models that sat at the centre of our revenue reporting. The kind of work that is genuinely unglamorous, invisible by nature, and deeply important. She approached it the way a lot of good engineers approach hard problems — she went quiet and started digging.&lt;/p>
&lt;p>For three weeks, she showed up, she worked hard, and she said almost nothing. Her pull requests were sparse. Her standups were &amp;ldquo;still investigating.&amp;rdquo; Her Teams messages were infrequent. She wasn&amp;rsquo;t slacking off. She was grinding through some of the most complex lineage work I&amp;rsquo;d ever seen someone untangle.&lt;/p>
&lt;p>Then a finance stakeholder messaged me — not her, me. Someone had noticed a spike in our Snowflake credit usage. They were nervous. They&amp;rsquo;d been burned by silent changes before.&lt;/p>
&lt;p>I didn&amp;rsquo;t have an answer. Mia had one, but nobody knew to ask her.&lt;/p>
&lt;p>I went to her and said, &amp;ldquo;walk me through what you&amp;rsquo;ve been doing.&amp;rdquo; What she showed me was extraordinary. She&amp;rsquo;d traced dependency chains through twelve models. She&amp;rsquo;d found three bugs that had been silently wrong for months. She&amp;rsquo;d drafted a migration plan that would cut query time in half. It was genuinely impressive work — and it had been completely invisible for twenty-one days.&lt;/p>
&lt;p>That&amp;rsquo;s the moment I understood something that Jeff Atwood wrote in 2008, in a post called &lt;a href="https://blog.codinghorror.com/dont-go-dark/" target="_blank" rel="noopener">Don&amp;rsquo;t Go Dark&lt;/a>, that I wish someone had made me read before I managed my first data team. Atwood&amp;rsquo;s essay was aimed at software engineers. It quoted a Microsoft engineering rule that said, in essence: &lt;em>three weeks is going dark.&lt;/em> The rule wasn&amp;rsquo;t about laziness. It wasn&amp;rsquo;t about bad engineers. It was about a failure mode so common in technical work that it needed a name.&lt;/p>
&lt;p>Mia wasn&amp;rsquo;t lazy. She was going dark.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-atwood-actually-meant">What Atwood Actually Meant&lt;/h3>
&lt;p>Atwood&amp;rsquo;s post is short — shorter than most people expect from the weight it carries. He builds on two sources: a software engineering principle from Jim McCarthy&amp;rsquo;s &lt;em>Dynamics of Software Development&lt;/em>, and an observation from open-source contributor Ben Collins-Sussman. Together they identify a pattern that every engineering lead will recognise the moment they read it.&lt;/p>
&lt;p>The pattern is this: programmers don&amp;rsquo;t want to show work in progress. They want to disappear into the problem, solve it completely, and emerge holding something finished. Collins-Sussman pinned it to fear — not arrogance — arguing that most developers treat coding as an arena for personal heroics rather than an inherently social activity. They&amp;rsquo;ll share code once it&amp;rsquo;s polished. They&amp;rsquo;ll share failures once they&amp;rsquo;re resolved. But the messy in-between, the half-formed thinking, the models with todos and the branches with four commits and no description? That stays private.&lt;/p>
&lt;p>McCarthy&amp;rsquo;s rule was direct: manage your tasks in short enough increments that you always have a visible deliverable at the end of each one. Three weeks without something to show is too long. The early warning of slippage — one day, discovered this week — is worth ten times more than six months of slippage discovered at deadline.&lt;/p>
&lt;p>Atwood&amp;rsquo;s additional observation is the one that has aged best: agile development structures made going dark mechanically difficult. If you&amp;rsquo;re running sprints, you&amp;rsquo;re producing something reviewable every two weeks regardless. The iteration boundary forces surfacing. Joel Spolsky, Atwood&amp;rsquo;s co-founder at Stack Overflow and another writer in this intellectual lineage, made the same argument about daily builds a few years earlier: a team that builds its software every day has, at minimum, a daily signal that things are either working or not. The build is the forcing function. It converts invisible progress into visible evidence.&lt;/p>
&lt;p>Open source projects, Atwood observed, face the highest risk of going dark — because there&amp;rsquo;s no manager, no sprint ceremony, no daily build process unless someone chooses to implement one. The discipline has to be entirely internal. The only currency that matters on an open source project is visible, reviewable work.&lt;/p>
&lt;p>Data engineering, as a discipline, has no such forcing function. Our work doesn&amp;rsquo;t end at a sprint boundary with a demo. Our pipelines run on schedules that have nothing to do with how long the underlying thinking took. Our models transform quietly, in the background, producing outputs that are visible only when something breaks — or worse, when something breaks &lt;em>subtly&lt;/em> and doesn&amp;rsquo;t look broken at all.&lt;/p>
&lt;p>That&amp;rsquo;s the gap Atwood&amp;rsquo;s advice never closed. He fixed going dark for software teams. Nobody translated it for us.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="why-data-engineering-goes-dark-more-than-anyone-else">Why Data Engineering Goes Dark More Than Anyone Else&lt;/h3>
&lt;p>There&amp;rsquo;s a structural reason data work is so prone to invisibility.&lt;/p>
&lt;p>When a front-end engineer ships a feature, there&amp;rsquo;s an artifact: a button, a page, a flow you can click through. When a backend engineer merges a PR, there&amp;rsquo;s a diff: before and after, visible in the tool, reviewable by anyone. The work and the evidence of work are coupled. They arrive together.&lt;/p>
&lt;p>Data work decouples them. Benn Stancil, writing about the data industry, put it well when he described the fuzzy boundary between production and everything else in data teams — there&amp;rsquo;s often no clean staging/prod line, no clear &amp;ldquo;shipped&amp;rdquo; moment, no obvious artifact to point to. A dbt model runs. A dashboard refreshes. A pipeline completes. Did anything change? Did anything improve? Is this better than it was last week? The answers require knowing the context that lives in the engineer&amp;rsquo;s head, which is precisely the context that isn&amp;rsquo;t written down.&lt;/p>
&lt;p>This gets worse the longer the project. Migrations are the canonical example. A warehouse migration — say, moving from Redshift to Snowflake, or rebuilding a legacy reporting layer in dbt — might take three months of continuous work before a stakeholder sees a single dashboard change. During those three months, the data engineer is doing real, difficult, valuable work: tracing lineage, resolving naming conflicts, negotiating data contracts with upstream producers, writing tests that have never existed before. All of it is invisible. All of it looks like nothing to anyone watching from the outside.&lt;/p>
&lt;p>There&amp;rsquo;s also the problem that Locally Optimistic describes as the distinction between linear and circular projects. Linear projects have a known path and a clear endpoint. Circular projects — most of the genuinely interesting data work — have outcomes that depend heavily on what you discover along the way. The best-laid migration plan doesn&amp;rsquo;t survive contact with a production schema that hasn&amp;rsquo;t been properly documented since 2019. Exploration takes time that doesn&amp;rsquo;t map cleanly to sprint cards or progress percentages.&lt;/p>
&lt;p>When you combine those two things — structural invisibility and genuinely unpredictable progress — you get a discipline that almost naturally produces going-dark behaviour, even among engineers who are working diligently and in good faith. Mia wasn&amp;rsquo;t hiding. The nature of her work was hiding it for her.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-ic-chapter-staying-visible-without-performing-busyness">The IC Chapter: Staying Visible Without Performing Busyness&lt;/h3>
&lt;p>Staying visible doesn&amp;rsquo;t mean performing busyness. It doesn&amp;rsquo;t mean posting standup updates about tasks you haven&amp;rsquo;t started. It doesn&amp;rsquo;t mean narrating every dead end like it&amp;rsquo;s a breakthrough. That&amp;rsquo;s noise, and experienced leads learn to tune it out.&lt;/p>
&lt;p>What it does mean is producing &lt;em>artifacts&lt;/em> — real, durable, reviewable traces of your thinking — on a cadence that keeps your work legible to the people around you. The goal is to make silence meaningful. When there&amp;rsquo;s nothing from you for three days, the people you work with should know that&amp;rsquo;s fine because you&amp;rsquo;ve pre-declared it and pre-committed to what you&amp;rsquo;ll surface when you come up for air. When you&amp;rsquo;re grinding through something genuinely hard, the work itself should leave evidence that can be reviewed asynchronously, without requiring a meeting to explain it.&lt;/p>
&lt;p>&lt;strong>Draft PRs as the lowest-friction visibility tool you already have.&lt;/strong>&lt;/p>
&lt;p>Open a pull request on day one of a significant piece of work. Not when it&amp;rsquo;s ready for review — when you have a scaffold and an intent. GitHub&amp;rsquo;s draft PR feature exists exactly for this: your code can&amp;rsquo;t be merged, reviewers aren&amp;rsquo;t auto-requested, and everyone in the team can see that the work exists, what it&amp;rsquo;s targeting, and roughly how it&amp;rsquo;s going. Update it with a comment as you progress. &amp;ldquo;Blocked on upstream source delay — will resume Thursday.&amp;rdquo; &amp;ldquo;Discovered three extra joins needed — revised estimate to end of week.&amp;rdquo; &amp;ldquo;Models passing, running full test suite now.&amp;rdquo;&lt;/p>
&lt;p>This pattern turns the PR into the canonical artifact of the work — a living document that replaces scattered messages, status check-ins, and &amp;ldquo;hey, how&amp;rsquo;s that thing going?&amp;rdquo; conversations. The PR description, written well, tells the story of what changed, why it changed, and how it was tested. Six months from now, when a model breaks at 2am and the engineer who built it is on leave, that description is the thing that saves the on-call.&lt;/p>
&lt;p>&lt;strong>Commit messages as team communication.&lt;/strong>&lt;/p>
&lt;p>There&amp;rsquo;s a widely-cited piece by Chris Beams on how to write a good git commit message that&amp;rsquo;s been floating around engineering circles since 2014. The core argument is simple: the subject line tells you &lt;em>what&lt;/em> changed; the body tells you &lt;em>why&lt;/em>. A commit message that says &lt;code>fix bug&lt;/code> costs the next engineer ten minutes of archaeology. A commit message that says &lt;code>Fix null handling in fct_orders when currency column is missing&lt;/code> and then explains that a vendor renamed a field and the defensive &lt;code>.get()&lt;/code> was silently returning zero for three months — that commit message is worth something.&lt;/p>
&lt;p>The Conventional Commits specification formalises this further: &lt;code>feat:&lt;/code>, &lt;code>fix:&lt;/code>, &lt;code>refactor:&lt;/code>, &lt;code>docs:&lt;/code>, &lt;code>chore:&lt;/code>, each with optional scope. It sounds bureaucratic until you try to understand a six-month-old change at 9pm during an incident, and someone had the discipline to write &lt;code>refactor(revenue): consolidate gross margin calculation into shared macro&lt;/code> in the subject line. Then you understand immediately.&lt;/p>
&lt;p>&lt;strong>dbt documentation as a visibility discipline.&lt;/strong>&lt;/p>
&lt;p>Every model in a dbt project can carry descriptions at the model level and at the column level, written in the same &lt;code>schema.yml&lt;/code> file that defines your tests. Most teams don&amp;rsquo;t write them. Or they write them at launch and never update them. Or they live in a separate Confluence page that nobody visits.&lt;/p>
&lt;p>Here&amp;rsquo;s the thing: &lt;code>schema.yml&lt;/code> descriptions review in the same PR as the code they describe. They can&amp;rsquo;t silently drift from the implementation because they&amp;rsquo;re changed in the same commit. A model description that says &amp;ldquo;Revenue aggregated by market, excluding internal transfers — see ADR-004 for the exclusion logic&amp;rdquo; is a piece of team communication that persists across every engineer who will ever touch that model.&lt;/p>
&lt;p>Go further. Use dbt Exposures to document which dashboards and downstream artifacts depend on your models. A handful of YAML lines, committing which report or ML model consumes each table, converts &amp;ldquo;will this refactor break anything?&amp;rdquo; from a Slack archaeology project into something you can run a query against. When a finance stakeholder asks if their dashboard will be affected by a model change, you can show them the dependency chain. That&amp;rsquo;s a conversation that previously required guesswork. Now it requires a terminal command.&lt;/p>
&lt;p>&lt;strong>Architecture Decision Records for the decisions people will question.&lt;/strong>&lt;/p>
&lt;p>ADRs — short markdown files committed to &lt;code>docs/adr/&lt;/code> in your repository — document the decisions that will otherwise live only in one engineer&amp;rsquo;s memory. Why did you choose Snowflake over BigQuery? Why does the revenue model exclude returns that arrived after 30 days? Why is this model incremental rather than table? These questions come up six months later, in a meeting, when the person who made the decision has moved on or forgotten the reasoning. An ADR is three paragraphs and a status field. It takes fifteen minutes to write. It has saved me more than one painful meeting where the alternative was reconstructing a decision from Slack history.&lt;/p>
&lt;p>&lt;strong>Async standups that actually work.&lt;/strong>&lt;/p>
&lt;p>The daily standup ritual made sense when software teams sat together and needed a quick synchronising pulse. For data teams — often distributed across timezones, frequently doing work that looks the same from the outside regardless of whether it&amp;rsquo;s going well or terribly — the fifteen-minute morning video call has become a source of collective fiction. Everyone says &amp;ldquo;still on X, should be done soon&amp;rdquo; and nobody learns anything.&lt;/p>
&lt;p>Async standup tools like Geekbot (for Slack) or equivalent bots for Teams send each person the standup questions by DM on a schedule and post responses to a shared channel. The format that consistently performs best isn&amp;rsquo;t the classic three questions — it&amp;rsquo;s a single question: &lt;em>&amp;ldquo;What would you like the team to know today?&amp;rdquo;&lt;/em> That question has enough latitude to surface a blocker, a discovery, a risk, or a win. It invites signal rather than mandating a ritual. Responses posted to a shared channel are searchable, linkable, and skimmable — a week of them gives a manager more information than five daily standups would.&lt;/p>
&lt;p>The written standup format also decouples availability from communication. A data engineer in Melbourne doesn&amp;rsquo;t need to be online at the same time as her lead in London for him to understand what&amp;rsquo;s happening. The post is there when he arrives. She&amp;rsquo;s not interrupted in her maker&amp;rsquo;s block to attend a meeting she&amp;rsquo;ll spend eight minutes waiting through before she has sixty seconds to talk.&lt;/p>
&lt;p>One discipline worth enforcing: when you name a blocker in a written standup, link to it. Don&amp;rsquo;t write &amp;ldquo;blocked on upstream data delay&amp;rdquo; — write &amp;ldquo;blocked on upstream &lt;code>raw_orders&lt;/code> freshness, opened Issue #247 to track.&amp;rdquo; The standup post becomes navigable rather than descriptive. Anyone who wants to understand the blocker can follow the link. Anyone who just needs the pulse can read the post and move on.&lt;/p>
&lt;p>&lt;strong>Data diffs in CI — making the invisible visible before it ships.&lt;/strong>&lt;/p>
&lt;p>The traditional CI check for a dbt project tells you whether models compile and whether your schema-level tests pass. Unique keys, not-null constraints, accepted values — these are important. They catch structural problems before they reach production.&lt;/p>
&lt;p>What they don&amp;rsquo;t catch is when a model that was returning &lt;code>65.00&lt;/code> starts returning &lt;code>65.10&lt;/code>, or when a currency denomination silently shifts from whole dollars to cents because an upstream source changed its representation without changing its schema. Those changes are invisible to schema tests. They&amp;rsquo;re invisible to the log output. They&amp;rsquo;re invisible right up until a finance stakeholder notices that last month&amp;rsquo;s revenue looks 100× higher than expected in a board deck.&lt;/p>
&lt;p>Data diff tooling — whether through Datafold integrated into GitHub CI, or the open-source &lt;code>audit_helper&lt;/code> package in dbt, or a homegrown comparison query — runs value-level comparisons between the current state of a model and a reference point. Merge a PR that refactors how gross margin is calculated, and the CI comment shows you a row-level diff of what changed. Every affected row. Every affected column. Before the code reaches production.&lt;/p>
&lt;p>This changes the nature of what a PR review means. Instead of reviewing whether the SQL is logically correct — a genuinely hard thing to assess from reading code alone — reviewers can look at the data that would result. A refactor that should be semantically identical shows zero diff. One that inadvertently changes three percent of records in a specific market shows exactly which records and by how much. The artifact created by data diff in CI is one of the most powerful anti-going-dark tools available to a data team, because it makes the consequence of code changes legible to anyone who can read a table — not just the engineers who wrote the SQL.&lt;/p>
&lt;p>&lt;strong>Async documentation that travels with the work.&lt;/strong>&lt;/p>
&lt;p>One pattern that consistently separates data teams that are legible from those that aren&amp;rsquo;t: Loom or short screen recordings attached to PR descriptions on significant changes. Not for every fix. Not as a replacement for the written description. But for a model redesign, a migration milestone, or a new data product, a two-minute screen recording walking through the dbt DAG and explaining the decisions is worth more than three paragraphs of prose that nobody will read.&lt;/p>
&lt;p>The video doesn&amp;rsquo;t have to be polished. It has to exist. It converts work that is legible only to the person who did it into work that is legible to a finance analyst, a product manager, or a new data engineer joining in six months. It replaces the meeting where you&amp;rsquo;d have explained this anyway, except now it&amp;rsquo;s available asynchronously, can be rewatched, and is linked from the artifact it describes.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-distributed-team-problem">The Distributed Team Problem&lt;/h3>
&lt;p>Remote and distributed data teams face a compounded version of the going-dark problem. The structural invisibility of data work — no natural demo format, no clean shipped/not-shipped boundary — gets worse when your team spans timezones and your primary communication channel is a text-based messaging app with a scroll-back limit.&lt;/p>
&lt;p>The teams that navigate this best tend to share a principle: optimize for knowledge retrieval, not knowledge transfer. The goal isn&amp;rsquo;t to ensure that information passes from person A to person B in a meeting. The goal is to ensure that the information exists in a form that person B can find when they need it, regardless of whether person A is online.&lt;/p>
&lt;p>GitLab, operating as a fully remote company since its founding, formalised this as handbook-first communication: any decision, policy, or significant piece of information gets documented before it gets communicated or implemented. Not as bureaucracy, but because documentation creates the retrievable artifact. A verbal decision made in a meeting exists only in the attendees&amp;rsquo; memories. A written decision linked from a PR exists for the lifetime of the repository.&lt;/p>
&lt;p>For data teams, this translates practically into a few habits. Meeting decisions get a one-paragraph summary posted to a Teams thread, linked from the relevant GitHub issue. A verbal conversation about model design gets a brief written follow-up: &amp;ldquo;Following on from our chat — going with incremental strategy for &lt;code>fct_orders&lt;/code> because full refresh at scale was timing out in staging. ADR drafted at &lt;code>docs/adr/0012-incremental-fct-orders.md&lt;/code>.&amp;rdquo; The conversation happened. Now it has an artifact that travels.&lt;/p>
&lt;p>Long-running projects — migrations, platform changes, major refactors — benefit from a written brief at the start that defines the scope, the success criteria, and the milestone cadence. Not a Jira epic with fifty tasks. A two-page document that answers: what are we doing, why are we doing it, how will we know when we&amp;rsquo;re done, and what are the milestones where we&amp;rsquo;ll regroup. The brief is the thing you hand to a new stakeholder who asks what&amp;rsquo;s happening with the platform migration. The brief is the thing you update when the scope changes. The brief is what prevents the three-week silence.&lt;/p>
&lt;p>Locally Optimistic describes a useful scripting pattern for circular projects — the kind where the outcomes depend heavily on what you discover along the way. Rather than promising a deliverable, you promise a process: &amp;ldquo;We will investigate X, Y, and Z over the next two weeks. Those might not give us a conclusive answer, but we&amp;rsquo;ll regroup to discuss the findings and determine next steps.&amp;rdquo; That framing is honest. It sets appropriate expectations. And critically, it includes a defined moment of resurfacing — the regroup — which prevents the silence from becoming open-ended.&lt;/p>
&lt;p>Julia Evans has written persuasively about the value of maintaining a running document of your own accomplishments — not as a vanity project but as a memory aid. The problem she identifies is real: at review time, you&amp;rsquo;ve forgotten half of what you did in the last six months, and your manager has forgotten more than that. A brag document is a log of things that mattered: bugs found before they became incidents, migrations completed, models refactored, stakeholders unblocked, junior engineers mentored.&lt;/p>
&lt;p>This isn&amp;rsquo;t about inflating your achievements. It&amp;rsquo;s about making them visible in a context where the work itself produces no natural evidence trail.&lt;/p>
&lt;p>Equally useful: weekly notes. A short message to your team lead, every Friday, that says &amp;ldquo;this week I did X, I&amp;rsquo;m picking up Y on Monday, and I want to flag Z as a potential blocker.&amp;rdquo; It doesn&amp;rsquo;t have to be long. It doesn&amp;rsquo;t have to be polished. It just has to exist. Will Larson calls this the drip — communication on cadence, regardless of whether there&amp;rsquo;s exciting news. The drip is what makes silence informative. When you&amp;rsquo;ve been sending a Friday note every week for three months and then you stop, the absence is a signal. When you&amp;rsquo;ve never sent one, silence tells nobody anything.&lt;/p>
&lt;p>&lt;strong>Tanya Reilly&amp;rsquo;s glue trap — for senior ICs especially.&lt;/strong>&lt;/p>
&lt;p>If you&amp;rsquo;re a staff or principal data engineer, you&amp;rsquo;re probably doing a lot of work that doesn&amp;rsquo;t look like work from a promotion perspective: reviewing designs, unblocking others, noticing what&amp;rsquo;s slipping, maintaining relationships with stakeholders. Reilly calls this glue work — the coordination and communication and knowledge-transfer that holds projects together. It&amp;rsquo;s real work. It&amp;rsquo;s often the most valuable work on the team. And it&amp;rsquo;s completely invisible without a deliberate effort to create artifacts.&lt;/p>
&lt;p>Design proposals, written-up meeting decisions, documented onboarding pathways, group emails that summarise a decision made in a verbal conversation — these are the artifacts that make glue work visible. Without them, you&amp;rsquo;re building organisational infrastructure that has no evidence it exists.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-fear-dimension-whats-really-happening-when-engineers-go-dark">The Fear Dimension: What&amp;rsquo;s Really Happening When Engineers Go Dark&lt;/h3>
&lt;p>Here&amp;rsquo;s something I think we don&amp;rsquo;t talk about honestly enough — and I&amp;rsquo;m including myself in this.&lt;/p>
&lt;p>I&amp;rsquo;ve been on both sides of it. I&amp;rsquo;ve gone dark on work I wasn&amp;rsquo;t confident about, telling myself I&amp;rsquo;d surface when I had something worth showing. I&amp;rsquo;ve also been the lead who didn&amp;rsquo;t notice, or didn&amp;rsquo;t ask, until a stakeholder beat me to it. Neither version felt like failure at the time. Both were.&lt;/p>
&lt;p>Most going-dark behaviour isn&amp;rsquo;t strategic. It isn&amp;rsquo;t laziness. It&amp;rsquo;s fear — specifically, the fear that surfacing in-progress work will reveal that you don&amp;rsquo;t have it as together as you implied you did.&lt;/p>
&lt;p>Collins-Sussman put this precisely in the piece Atwood quoted: developers don&amp;rsquo;t want their peers to see mistakes or failures. They want to present themselves as infallible. They&amp;rsquo;re comfortable sharing code once it&amp;rsquo;s polished. The messy middle, the exploratory branches, the models with big question marks in the comments — that stays private.&lt;/p>
&lt;p>This fear is rational in the short term. Showing a half-finished thing invites feedback that can derail you. Showing a broken thing invites questions you can&amp;rsquo;t answer yet. Staying silent avoids both. The problem is that in the medium term, silence accumulates into something much worse: an engineer who surfaces three weeks into a problem with nothing to show, facing a stakeholder conversation where the first question is &amp;ldquo;why didn&amp;rsquo;t you just tell us?&amp;rdquo;&lt;/p>
&lt;p>Paloma Medina&amp;rsquo;s BICEPS framework identifies six core needs that, when threatened, produce withdrawal: Belonging, Improvement, Choice, Equality, Predictability, Significance. (Yes, it spells BICEPS. Yes, it will appear on a workshop slide near you. The underlying insight is still useful.) Lara Hogan, who has done more than anyone I know of to translate this framework into engineering management practice, makes the observation that going dark is often a flight response to one of these needs being threatened. A project pivot that makes the last three months of work feel wasted threatens Significance — and the engineer who feels that their work no longer matters is likely to disengage before they articulate why. An abrupt change in priorities threatens Predictability and Choice simultaneously. A team reorganisation threatens Belonging.&lt;/p>
&lt;p>The leader&amp;rsquo;s job, when they notice going-dark behaviour, isn&amp;rsquo;t to increase accountability pressure. It&amp;rsquo;s to identify which need has been threatened and address it directly. Fournier and others recommend this as the actual content of the 1:1 when something feels off: not &amp;ldquo;how&amp;rsquo;s the project going?&amp;rdquo; but &amp;ldquo;how are you doing?&amp;rdquo; The project status comes second. The human state comes first.&lt;/p>
&lt;p>Hogan&amp;rsquo;s Red/Yellow/Green check-in is a lightweight mechanism for this. At the start of a 1:1, an engineer can say &amp;ldquo;red&amp;rdquo; without having to explain why — just to signal that something is wrong. The permission to say &amp;ldquo;red&amp;rdquo; without an explanation is itself the thing that makes the saying possible. When explanation is required before disclosure, disclosure gets deferred until the explanation is formed — and by then, the problem has grown. Hogan&amp;rsquo;s observation: &lt;em>only having to say red, and not having to explain the why, is huge.&lt;/em> The absence of the explanation requirement is the mechanism. It lowers the cost of disclosure below the threshold where people start editing themselves.&lt;/p>
&lt;p>Amy Edmondson&amp;rsquo;s research on psychological safety is directly relevant here. Her finding, counterintuitively, was that better hospital units didn&amp;rsquo;t make fewer medication errors — they reported more of them. Her reframe: the better units weren&amp;rsquo;t more error-prone, they were more willing to surface problems early. The error rate looked higher because the environment was safe enough that people actually said what was happening.&lt;/p>
&lt;p>Google&amp;rsquo;s Project Aristotle, which studied 180 teams over several years, found the same pattern at scale: the single strongest predictor of team performance was whether team members felt safe to take interpersonal risks. Safe to say &amp;ldquo;I&amp;rsquo;m stuck.&amp;rdquo; Safe to say &amp;ldquo;I found a bug.&amp;rdquo; Safe to say &amp;ldquo;I don&amp;rsquo;t know how long this will take.&amp;rdquo;&lt;/p>
&lt;p>The data engineering implication is uncomfortable but important: if your team never has visible incidents, never surfaces broken assumptions, never flags a model that&amp;rsquo;s been silently wrong — that&amp;rsquo;s probably not a sign of excellence. It&amp;rsquo;s probably a sign that the environment doesn&amp;rsquo;t feel safe enough to tell the truth. The team that surfaces the most broken pipelines, the most failed tests, the most &amp;ldquo;we got this wrong&amp;rdquo; disclosures might be your best team. They&amp;rsquo;re the ones telling you what&amp;rsquo;s actually happening.&lt;/p>
&lt;p>The &amp;ldquo;I&amp;rsquo;ll tell them when it&amp;rsquo;s fixed&amp;rdquo; trap is the going-dark failure mode in its purest form. An engineer discovers a problem — a data quality issue, a budget overrun, a model that produces subtly wrong results. Rather than surface it, they decide to fix it first. Each day that passes raises the psychological cost of disclosure. The problem grows. The fix gets harder. Eventually, either the engineer surfaces in a state of exhaustion with a fix and hopes nobody asks hard questions, or a stakeholder finds the problem externally — at which point the disclosure isn&amp;rsquo;t just about the bug. It&amp;rsquo;s about the three weeks of silence.&lt;/p>
&lt;p>Fixing the number is not the same as fixing what the number did to people who trusted it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-lead-chapter-building-conditions-where-your-team-doesnt-go-dark">The Lead Chapter: Building Conditions Where Your Team Doesn&amp;rsquo;t Go Dark&lt;/h3>
&lt;p>If you lead a data team, the going-dark problem is partly yours to own — not because your engineers are your responsibility in a parental sense, but because the &lt;em>conditions&lt;/em> that make going dark feel safe are conditions you set.&lt;/p>
&lt;p>Camille Fournier&amp;rsquo;s framing, in &lt;em>The Manager&amp;rsquo;s Path&lt;/em>, is that 1:1s are like oil changes: if you skip them, plan to get stranded. The 1:1 is the primary diagnostic for whether someone is going dark, and it works only if it creates genuine psychological safety — which means it has to be something other than a status update. If every 1:1 is &amp;ldquo;what are you working on this week?&amp;rdquo;, you&amp;rsquo;ll get accurate project status and zero signal about whether someone is actually stuck, scared, or burning out.&lt;/p>
&lt;p>Lara Hogan recommends opening the relationship with simple questions: what makes you grumpy? How will I know when you&amp;rsquo;re grumpy? What do you need from me when you&amp;rsquo;re struggling? These aren&amp;rsquo;t soft questions — they&amp;rsquo;re diagnostic instruments. They establish that the 1:1 is a place where the real state of things can be named.&lt;/p>
&lt;p>Her Red/Yellow/Green check-in is a lightweight version of the same tool. At the start of a 1:1, an engineer can say &amp;ldquo;red&amp;rdquo; without having to explain why — just to signal that something is wrong. The permission to say &amp;ldquo;red&amp;rdquo; without an explanation is itself the thing that makes the saying possible. When explanation is required before disclosure, disclosure gets deferred until the explanation is formed — and by then, the problem has grown.&lt;/p>
&lt;p>Will Larson frames visibility for leads as a mechanical practice rather than an art form. The goal isn&amp;rsquo;t to inspire your team to communicate — it&amp;rsquo;s to build systems that make communication the path of least resistance. A weekly update template, committed to as a team norm. An async standup channel where the format is so lightweight that posting takes two minutes. A retrospective cadence where surfacing what went wrong is the expected behaviour, not the exception.&lt;/p>
&lt;p>&lt;strong>Warning signs to watch for.&lt;/strong>&lt;/p>
&lt;p>Going dark rarely announces itself. The early signals are easy to miss.&lt;/p>
&lt;p>Standups that shift from specific to vague — &amp;ldquo;still on X&amp;rdquo; across multiple days with no detail — are an indicator. PRs that open in draft and stay there, accumulating commits but never progressing to review. Estimates that consistently slip by a day or two, week after week, without the engineer naming the slippage explicitly. A previously engaged person who starts attending meetings on camera-off, contributing less, leaving threads unresponded to.&lt;/p>
&lt;p>The most useful signal is often external. When a stakeholder or adjacent team member asks you what&amp;rsquo;s happening with a project before the engineer working on it has raised anything — that&amp;rsquo;s the canary. The information is flowing around the engineer rather than through them. That&amp;rsquo;s a conversation to have in the next 1:1, and it&amp;rsquo;s worth naming directly: &amp;ldquo;I heard from finance about the revenue models. Tell me where things actually are.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Deep work is not going dark — and the distinction matters.&lt;/strong>&lt;/p>
&lt;p>There&amp;rsquo;s a real risk of over-correcting. Data engineering requires extended, cognitively-demanding focus. Paul Graham&amp;rsquo;s framing of the maker&amp;rsquo;s schedule versus the manager&amp;rsquo;s schedule is directly applicable: a data engineer tracing lineage through a DAG, rebuilding a model from first principles, or debugging an intermittent pipeline failure needs hours of uninterrupted context, not a constant stream of check-in messages.&lt;/p>
&lt;p>Deep work and going dark are not the same thing. The difference is legibility.&lt;/p>
&lt;p>Deep work has a pre-declared output and timebox. Going dark does not. Deep work is legible at a coarse grain — &amp;ldquo;I&amp;rsquo;m heads-down on X until Thursday, update Friday&amp;rdquo; — even when it&amp;rsquo;s illegible at a fine grain. Going dark is illegible at every grain.&lt;/p>
&lt;p>The mechanism that makes the distinction: the cadence contract. Before going heads-down, the engineer declares: here&amp;rsquo;s what I&amp;rsquo;m working on, here&amp;rsquo;s when you&amp;rsquo;ll hear from me, here&amp;rsquo;s what would make me surface early. &amp;ldquo;If I&amp;rsquo;m stuck for more than a day, I&amp;rsquo;ll tell you. If I&amp;rsquo;m on track, Friday update. Ping me in #urgent if there&amp;rsquo;s a fire.&amp;rdquo; With that contract in place, silence during the week means &amp;ldquo;on track.&amp;rdquo; Without it, silence means nothing — or worse, triggers anxiety that prompts the exact interruptions that fragment deep work.&lt;/p>
&lt;p>The goal is to make silence meaningful. That requires prior investment in communication, not more communication during the work itself.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-visible-data-team-playbook">The Visible Data Team Playbook&lt;/h3>
&lt;p>There are a handful of practices that the best data teams I&amp;rsquo;ve seen use consistently. None of them are revolutionary. Most of them are embarrassingly simple. They work because they&amp;rsquo;re habitual, not because they&amp;rsquo;re clever.&lt;/p>
&lt;p>&lt;strong>Weekly insight posts.&lt;/strong>&lt;/p>
&lt;p>A practice described from data teams at companies like Netlify involves a dedicated Teams channel where each analyst posts one insight per week — not a status update, not a task list, but one thing they found interesting in the data. The format is designed to answer five questions: what am I looking at, why should you care, what caught my eye, where can you learn more, and who do you ask if you have questions.&lt;/p>
&lt;p>The explicit benefit isn&amp;rsquo;t just visibility — it&amp;rsquo;s demonstrating the range of what the data team does. Stakeholders who only see the data team when something breaks, or when they submit a request, have no model of what the team actually knows. A weekly insight post gives them one. Over time, it becomes the evidence base for why the team should be resourced, why data work is complex, why the business should care about data quality. It&amp;rsquo;s not reporting. It&amp;rsquo;s positioning.&lt;/p>
&lt;p>&lt;strong>Parity dashboards during migrations.&lt;/strong>&lt;/p>
&lt;p>One of the most effective anti-going-dark tools for long migrations is a parity dashboard: a side-by-side view of critical metrics computed in the old and new systems simultaneously. Before you&amp;rsquo;ve finished the migration, you build the parallel calculation and make it visible. Divergence between the two numbers becomes the artifact of progress — something demoable, something reviewable, something that gives a stakeholder a concrete thing to look at even when the underlying models are still being rebuilt.&lt;/p>
&lt;p>The parity dashboard also builds confidence in a way that purely technical communication can&amp;rsquo;t. When a finance stakeholder sees that their ARR number matches between the legacy and new system for three weeks running, they start to trust the migration in a way that no amount of &amp;ldquo;we&amp;rsquo;ve tested this thoroughly&amp;rdquo; can produce. Evidence beats assertion.&lt;/p>
&lt;p>&lt;strong>Incident communication as a team norm.&lt;/strong>&lt;/p>
&lt;p>The instinct to fix a data quality issue quietly and move on is almost universal among data engineers. The impulse is understandable — nobody wants to be the person who caused the problem, and surfacing it draws attention to the failure. But the silent fix has a consistent failure mode: it gets discovered later, retrospectively, in a context where the silence becomes the story rather than the fix.&lt;/p>
&lt;p>Monte Carlo&amp;rsquo;s research showed that the data teams with the highest stakeholder trust tend to over-communicate around incidents — sending post-mortems after significant issues, flagging discoveries before fixes are complete, maintaining a clear status trail from &amp;ldquo;investigating&amp;rdquo; to &amp;ldquo;resolved.&amp;rdquo; What one team described as transformative wasn&amp;rsquo;t their mean time to resolution. It was the shift from reactive firefighting to proactive communication that demonstrated awareness and control. The communication is the product, as much as the fix.&lt;/p>
&lt;p>A simple practice: any time you discover a data quality issue, the first act is naming it in writing — a Teams message, a GitHub issue, an incident channel post — before you start fixing it. Not to perform distress, but to start the paper trail. &amp;ldquo;Found an issue with the &lt;code>fct_orders&lt;/code> null handling — investigating now, will update by EOD.&amp;rdquo; That message, and the chain of messages that follow it, is what turns an incident into evidence of a well-run team rather than evidence of a careless one.&lt;/p>
&lt;p>&lt;strong>ADRs for the decisions people will question.&lt;/strong>&lt;/p>
&lt;p>The architecture decisions that feel obvious when you make them feel arbitrary when someone encounters them six months later without context. Why incremental and not table? Why Snowflake and not BigQuery? Why is revenue calculated at invoice date rather than payment receipt date? These questions have answers. The answers live in someone&amp;rsquo;s head, or in a Confluence page that nobody linked to the model.&lt;/p>
&lt;p>ADRs — short markdown files in &lt;code>docs/adr/&lt;/code>, committed to the repository — are the durable artifact of those decisions. They don&amp;rsquo;t need to be long. They need a title, a status, a description of the context, the decision, and the consequences. Once written and committed, they&amp;rsquo;re part of the repository&amp;rsquo;s history. They review in PRs. They can be linked from model descriptions. They don&amp;rsquo;t disappear when someone leaves the team.&lt;/p>
&lt;p>&lt;strong>dbt Exposures for impact legibility.&lt;/strong>&lt;/p>
&lt;p>If you&amp;rsquo;re using dbt and you haven&amp;rsquo;t adopted Exposures, this is worth doing this week. Exposures are YAML-defined references to downstream artifacts — dashboards, ML models, reverse-ETL syncs — that extend the dbt DAG past the warehouse. A few lines in &lt;code>schema.yml&lt;/code> that say &amp;ldquo;this model feeds the Weekly Revenue dashboard&amp;rdquo; means that when someone runs &lt;code>dbt ls --select +exposure:weekly_revenue&lt;/code>, they get the complete list of upstream models that would need to change to affect that dashboard.&lt;/p>
&lt;p>For a migration, this means you can answer &amp;ldquo;will this change break the CEO&amp;rsquo;s dashboard?&amp;rdquo; without archaeology. For a refactor, it means PR reviewers have a concrete list of stakeholders to notify. For a data quality incident, it means you know who to tell before they find out themselves.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="going-back-to-mia">Going Back to Mia&lt;/h3>
&lt;p>After my conversation with Mia — the one where she walked me through three weeks of extraordinary, invisible work — we had a more direct conversation about why none of it had been visible.&lt;/p>
&lt;p>Her answer was the one Collins-Sussman identified in 2008: she didn&amp;rsquo;t want to show something until it was right. She was worried about raising a false alarm. She was concerned that surfacing work in progress would invite questions she couldn&amp;rsquo;t answer yet. There was also something she said that stuck with me: &amp;ldquo;I thought if I kept my head down long enough, I&amp;rsquo;d come up with something worth showing.&amp;rdquo;&lt;/p>
&lt;p>All of those concerns were reasonable. None of them were wrong, exactly. What she&amp;rsquo;d underestimated was the cost of the silence — not to herself, but to the people around her. The finance stakeholder who&amp;rsquo;d gone to me with questions would have gone to Mia first if they&amp;rsquo;d had any signal that the work was in progress and legible. My not knowing, in a week where I&amp;rsquo;d been asked directly by leadership about the revenue model timeline, was a problem I hadn&amp;rsquo;t been equipped to solve.&lt;/p>
&lt;p>What I should have done differently, and what I&amp;rsquo;ve done differently since: established the cadence contract at the start of the project, not after the silence had already built. Agreed, before the first commit, what &amp;ldquo;on track&amp;rdquo; communication would look like — what the update cadence would be, what would trigger an early surface, and what silence during a declared heads-down period meant. With that contract in place, three weeks of quiet is three weeks of &amp;ldquo;on track, as agreed.&amp;rdquo; Without it, three weeks of quiet is three weeks of &amp;ldquo;nobody knows what&amp;rsquo;s happening.&amp;rdquo;&lt;/p>
&lt;p>The artifact we built together after that conversation was simple. For the remaining six weeks of the migration, Mia opened a draft PR on Monday morning with a brief plan for the week. She posted a comment each Friday with what had been done, what was pending, and any surprises. She added a parity dashboard that the finance lead could check at any time. She wrote three ADRs for the decisions that had taken the most deliberation.&lt;/p>
&lt;p>The work didn&amp;rsquo;t change. The visibility changed. And the experience of the migration — for Mia, for the stakeholders, for me — changed entirely.&lt;/p>
&lt;p>At the end of the project, the finance lead sent a message that I still think about. She said: &amp;ldquo;I&amp;rsquo;ve never felt so informed during a data migration. Usually I find out things are done when the thing I was waiting for just starts working.&amp;rdquo;&lt;/p>
&lt;p>Mia had gone dark for three weeks. For the remaining six, she hadn&amp;rsquo;t. The difference wasn&amp;rsquo;t effort. It was artifacts.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-closing-thought">The Closing Thought&lt;/h3>
&lt;p>Jeff Atwood closed his post with something direct: don&amp;rsquo;t go dark. Don&amp;rsquo;t be the developer in the room who hides their code until it&amp;rsquo;s done. Sharing work in progress feels riskier than it is. The feedback and connection it produces are worth more than the protection the silence provides.&lt;/p>
&lt;p>He was right in 2008. The principle has only compounded since, as data teams have become more distributed, more remote, and more responsible for the kind of long-horizon work that naturally produces invisibility.&lt;/p>
&lt;p>The tradeoff is real, though. Better visibility takes time that could go to the work itself. An ADR written at the end of a hard week is time you&amp;rsquo;re not spending fixing the problem. A parity dashboard adds engineering overhead before the migration is even done. Writing commit messages properly requires discipline you don&amp;rsquo;t always have at 5pm on a Friday when you just want to push and go home.&lt;/p>
&lt;p>The case I&amp;rsquo;m making isn&amp;rsquo;t that artifacts are free. It&amp;rsquo;s that the alternative — silence — costs more than you can see while you&amp;rsquo;re inside it. The cost lands on the stakeholder who went to your manager instead of you. On the on-call engineer at 2am who has nothing to go on. On the project that looked fine until it suddenly didn&amp;rsquo;t.&lt;/p>
&lt;p>Amy Edmondson&amp;rsquo;s hospital study keeps coming back to me. She&amp;rsquo;d expected to find that better units made fewer errors. Instead she found that they reported more. Her reframe: maybe the better units don&amp;rsquo;t have fewer problems. Maybe they&amp;rsquo;re just more willing to say so.&lt;/p>
&lt;p>The best data teams are the same. Not the ones with the fewest broken pipelines — the ones where a broken pipeline doesn&amp;rsquo;t stay quiet for long.&lt;/p>
&lt;p>Three weeks is going dark. One good artifact is enough. The rest follows from that.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Career Development</category><category>Leadership</category><category>Communication</category><category>Data Quality</category><category>GitHub</category><category>dbt</category><category>Remote Work</category><category>Career Development</category><category>Engineering Culture</category><category>Psychological Safety</category></item><item><title>The Broken Window in Your Data Pipeline</title><link>https://ghostinthedata.info/posts/2026/2026-05-09-broken-window-theory/</link><pubDate>Sat, 09 May 2026 09:00:00 +1000</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-05-09-broken-window-theory/</guid><author>Chris Hillman</author><description>A single ignored data quality issue doesn't stay local. In pipelines, broken windows travel — and by the time anyone notices, the damage is already downstream.</description><content:encoded>&lt;p>There&amp;rsquo;s a particular kind of data problem that doesn&amp;rsquo;t announce itself. It accumulates.&lt;/p>
&lt;p>We were receiving Salesforce data through delta extraction — sensible in theory, because full snapshots can run to hundreds of terabytes and less than 1% of records change on any given day. The problem is that deltas require someone to know what &amp;ldquo;changed&amp;rdquo; means. In Salesforce, that&amp;rsquo;s less obvious than it sounds. Watch a &lt;code>last_modified&lt;/code> column and you&amp;rsquo;ll miss objects that get updated when a related object changes, without their own timestamp reflecting it.&lt;/p>
&lt;p>Over time: drift. Orphan records. Data that &lt;em>looks&lt;/em> current because the record exists, but isn&amp;rsquo;t. The fix was documented — run a full snapshot periodically to correct the accumulated drift, painful as that was — and everyone with any context on the system knew it.&lt;/p>
&lt;p>What happened was roughly this: the drift accumulated silently until something downstream looked wrong. An investigation traced it back to the delta logic. The full snapshot was run. The problem was resolved. Notes were written. The workaround was filed away.&lt;/p>
&lt;p>And then, about twelve months later, the same conversation happened again.&lt;/p>
&lt;hr>
&lt;p>What made that moment stick wasn&amp;rsquo;t the technical failure. It was the recognition that the window had been broken for a while. Everybody who walked past it knew it was broken. We&amp;rsquo;d even put a note next to it.&lt;/p>
&lt;p>I&amp;rsquo;ve been that person — the one who wrote the documentation, filed the Jira ticket, and moved on. Which is probably why I remember the twelve-month cycle so clearly. And why I started paying attention to the pattern of it, across teams and companies, long after that particular incident.&lt;/p>
&lt;p>We just hadn&amp;rsquo;t fixed it.&lt;/p>
&lt;hr>
&lt;p>Bear with me here, because what I&amp;rsquo;m about to describe starts with an abandoned car in the Bronx in 1969 — and I promise it ends somewhere relevant to your dbt models.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="a-broken-window-in-the-bronx-a-broken-window-in-your-warehouse">A broken window in the Bronx, a broken window in your warehouse&lt;/h3>
&lt;/br>
&lt;p>A Stanford psychologist named Philip Zimbardo ran a strange experiment. He abandoned two cars — one in the Bronx, one in Palo Alto — and watched what happened.&lt;/p>
&lt;p>The Bronx car was stripped within twenty-four hours. Within three days it was completely gutted.
The Palo Alto car sat untouched for a week — until Zimbardo himself walked up with a sledgehammer and broke a window. Within hours, it had been stripped too.&lt;/p>
&lt;p>Same car. Different signal.&lt;/p>
&lt;p>Thirteen years later, criminologists James Q. Wilson and George Kelling built a theory on that experiment. If a window in a building is broken and left unrepaired, they argued, all the rest will soon follow. Not because there&amp;rsquo;s a particular breed of window-breaker lurking around, but because an unrepaired window sends a message: &lt;em>nobody here cares&lt;/em>. And once that signal is broadcast, breaking more windows costs nothing.&lt;/p>
&lt;p>The insight was semiotic, not structural. It wasn&amp;rsquo;t about windows. It was about what an unrepaired window communicates about the norms of a place.&lt;/p>
&lt;p>The theory eventually found its way into software. Andrew Hunt and David Thomas put it into &lt;em>The Pragmatic Programmer&lt;/em> almost verbatim: don&amp;rsquo;t leave broken windows — bad designs, wrong decisions, poor code — unrepaired. They&amp;rsquo;d watched clean systems deteriorate quickly once windows started breaking. The mechanism was the same. A developer looking at a messy codebase thinks: &lt;em>if someone else got away with being careless, maybe I can too.&lt;/em> The norm shifts. The entropy accelerates.&lt;/p>
&lt;p>Researchers at Empirical Software Engineering ran a controlled experiment — twenty-nine developers, codebases seeded with either high or low technical-debt density — and found exactly what Hunt and Thomas had intuited. Pre-existing debt measurably caused developers to introduce &lt;em>more&lt;/em> debt. Non-descriptive variable names. Duplicated logic instead of reuse. Additional code smells. The broken window was statistically contagious.&lt;/p>
&lt;p>It&amp;rsquo;s a compelling idea, and it maps well to software. But it doesn&amp;rsquo;t map perfectly to data engineering. And the gap between &amp;ldquo;maps well&amp;rdquo; and &amp;ldquo;maps perfectly&amp;rdquo; is where teams get into serious trouble.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-thing-thats-different-about-data">The thing that&amp;rsquo;s different about data&lt;/h3>
&lt;/br>
&lt;p>When a broken window appears in a codebase, it&amp;rsquo;s local. A messy function, an undocumented module, a class that&amp;rsquo;s doing five things at once — these are ugly, they invite imitation, and they slow future development. But they stay where they are. They don&amp;rsquo;t go anywhere.&lt;/p>
&lt;p>A broken window in a data pipeline doesn&amp;rsquo;t stay where it is.&lt;/p>
&lt;p>It travels.&lt;/p>
&lt;p>That&amp;rsquo;s the thing nobody really talks about when they apply broken-windows thinking to data work. In a pipeline, everything is connected. A schema drift in a source table doesn&amp;rsquo;t just make that table annoying to work with — it silently corrupts every model downstream that touches that field. Which means it corrupts every dashboard that uses those models. Which means it corrupts the metrics those dashboards expose. Which means it corrupts the business decisions made from those metrics.&lt;/p>
&lt;p>The broken window is in row one. By the time someone notices, the damage is in the boardroom.&lt;/p>
&lt;p>And unlike software, where the damage is visible — a stacktrace, a failing build, a crash — data damage is often invisible. The pipeline still runs. The dashboard still renders. The report still reconciles. The numbers just happen to be wrong, quietly, for reasons nobody can easily trace.&lt;/p>
&lt;p>Software bugs produce noise. Bad data produces silence.&lt;/p>
&lt;p>In software, the broken window signals disorder and invites imitation. In data pipelines, it does all of that &lt;em>and&lt;/em> it propagates at machine speed through every downstream system that trusts the upstream to be clean. By the time the propagation is discovered, it&amp;rsquo;s usually been underway for a while.&lt;/p>
&lt;p>This is the propagation problem. It&amp;rsquo;s not just that bad data begets more bad data. It&amp;rsquo;s that one bad window can contaminate an entire watershed.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-a-contaminated-watershed-actually-looks-like">What a contaminated watershed actually looks like&lt;/h3>
&lt;/br>
&lt;p>In May 2022, Unity Software — the game engine company — disclosed something remarkable in their SEC earnings filing. Their Audience Pinpointer ML model, the system that powered their ad targeting business, had ingested bad data from a large customer. That data had corrupted their training set.&lt;/p>
&lt;p>CEO John Riccitiello on the earnings call: &amp;ldquo;we lost the value of a portion of our training data due in part to us ingesting bad data from a large customer.&amp;rdquo;&lt;/p>
&lt;p>The bad data didn&amp;rsquo;t just produce one wrong prediction. It poisoned the model weights. Every subsequent retrain was built on a contaminated foundation. The model had to be taken offline, the bad data removed, and training restarted from scratch. The estimated impact was $110 million in revenue for 2022. The stock dropped 37% in a single day. Market cap losses in the billions.&lt;/p>
&lt;p>The broken window wasn&amp;rsquo;t in Unity&amp;rsquo;s systems — it was in data they were &lt;em>ingesting&lt;/em>. Once it crossed the boundary into their training pipeline, it propagated in the only direction data knows: forward and downstream, embedding itself into every layer of the system that touched it.&lt;/p>
&lt;p>Riccitiello&amp;rsquo;s pledge after the fact was almost poignant: &amp;ldquo;We are deploying monitoring, alerting and recovery systems and processes to promptly mitigate future events.&amp;rdquo; The observability came after the disaster. The window had already broken every other window in the building.&lt;/p>
&lt;hr>
&lt;p>These are the normal failure mode of connected data systems. The propagation isn&amp;rsquo;t a bug in the design — it&amp;rsquo;s inherent to the architecture. Data flows in one direction. Trust flows with it. The moment you have a pipeline, you have propagation risk.&lt;/p>
&lt;p>The question isn&amp;rsquo;t whether your broken windows will propagate. It&amp;rsquo;s how far they&amp;rsquo;ll travel before anyone notices.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-tools-we-built-to-tolerate-this">The tools we built to tolerate this&lt;/h3>
&lt;/br>
&lt;p>Modern data tooling has given us genuinely sophisticated ways to formally accept broken windows.&lt;/p>
&lt;p>dbt — the transformation tool most data engineering teams live inside — has a &lt;code>severity&lt;/code> configuration on data tests. You can set a test to &lt;code>warn&lt;/code> instead of &lt;code>error&lt;/code>. The test runs, detects a problem, and&amp;hellip; doesn&amp;rsquo;t fail the pipeline. It records the warning and moves on.&lt;/p>
&lt;p>The documentation is cheerful about it: &amp;ldquo;Maybe 1 duplicate record can count as a warning, but 10 duplicate records should count as an error.&amp;rdquo;&lt;/p>
&lt;p>In principle, this is sensible. In practice, the &lt;code>warn&lt;/code> threshold becomes a Schelling point. Teams configure tests to warn to avoid breaking CI. The warnings accumulate. And then — almost invariably — the warnings become wallpaper. &lt;em>(Go check your own test results right now. Count how many have been in warn state for more than two weeks. I&amp;rsquo;ll wait.)&lt;/em>&lt;/p>
&lt;p>dbt also has a feature called &lt;code>store_failures&lt;/code> that saves failed records to an audit schema table. The test is doing its job. The failures are being recorded. They overwrite the previous run&amp;rsquo;s failures on the next run. If nobody is actively querying that audit table — and almost nobody is — the failures exist only to make the test feel like it&amp;rsquo;s being taken seriously.&lt;/p>
&lt;p>It&amp;rsquo;s a passive graveyard. The window is monitored. Nobody fixes the glass.&lt;/p>
&lt;p>Airflow has an equivalent pattern. The &lt;code>soft_fail&lt;/code> parameter on sensors means that if an exception is raised — the source system is down, the file hasn&amp;rsquo;t arrived — the task is marked as &lt;em>skipped&lt;/em> rather than &lt;em>failed&lt;/em>. Downstream tasks, depending on their trigger rules, may also skip. An entire branch of your DAG quietly collapses to a skipped state, which most pipelines treat as benign, and your stakeholders get a dashboard with no data in it instead of an error message.&lt;/p>
&lt;p>Retries do something similar. A flaky source that fails 30% of the time gets &lt;code>retries=3&lt;/code> configured. The task eventually succeeds on the third attempt. The 30% failure rate never surfaces as an anomaly in any meaningful way. Until the source dies entirely, at which point the symptom everyone responds to is not &amp;ldquo;this has been flaky for six months&amp;rdquo; but &amp;ldquo;this suddenly started failing today.&amp;rdquo;&lt;/p>
&lt;p>None of this is the fault of the tools. dbt and Airflow are doing what they&amp;rsquo;re designed to do. The issue is that the default ergonomics of both make &lt;em>tolerating&lt;/em> failure significantly easier than &lt;em>stopping propagation&lt;/em>. &amp;ldquo;Don&amp;rsquo;t break the build&amp;rdquo; is a more convenient goal than &amp;ldquo;don&amp;rsquo;t ship broken data downstream.&amp;rdquo; The tools give you excellent knobs for the former and adequate knobs for the latter.&lt;/p>
&lt;p>Chad Sanderson — formerly at Convoy, now building in the data contracts space — has a name for what this produces over time. He calls it the POSIWID principle: the Purpose Of a System Is What It Does. If your data pipelines are consistently producing low-quality data, and your team consistently tolerates that, then whatever you think the purpose of your data platform is, its actual purpose is to enable teams to move fast, ship without accountability, and tolerate breakages.&lt;/p>
&lt;p>The broken windows aren&amp;rsquo;t exceptions. They&amp;rsquo;re the product.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="weve-built-monitoring-why-isnt-it-working">We&amp;rsquo;ve built monitoring. Why isn&amp;rsquo;t it working?&lt;/h3>
&lt;/br>
&lt;p>Data observability emerged as a formal discipline. The concept — largely popularised by Barr Moses at Monte Carlo — drew directly from the software world&amp;rsquo;s SRE and distributed systems monitoring practices: freshness, distribution, volume, schema, lineage. Five pillars. Automated anomaly detection. Alerts in Slack when something looks wrong.&lt;/p>
&lt;p>The framing was right. The problem is what happened next.&lt;/p>
&lt;p>Monte Carlo&amp;rsquo;s own telemetry — across millions of monitored tables — shows that alert engagement drops 15% when a Slack channel exceeds 50 alerts per week, and a further 20% past 100 per week. The data observability system has a data quality problem: the signal-to-noise ratio degrades, and so does the human response.&lt;/p>
&lt;p>This isn&amp;rsquo;t specific to data. The clinical literature on alarm fatigue is sobering. ICU environments average more than 150 alarms per bed per day. Studies consistently find that 72% to 99% of those alarms are non-actionable. The clinical community&amp;rsquo;s response to this was institutional — the Joint Commission made alarm safety a National Patient Safety Goal in 2014 — because they recognised that a monitoring system that produces more noise than signal doesn&amp;rsquo;t just fail to help. It actively worsens outcomes by training humans to stop responding.&lt;/p>
&lt;p>Cybersecurity teams face the same thing. Security operations centres receive thousands of alerts daily; most go unaddressed, not because analysts are lazy, but because the ratio of genuine signals to false positives has degraded to the point where sustained attention is cognitively impossible.&lt;/p>
&lt;p>The Google SRE book is direct on this: &amp;ldquo;Every page should be actionable. If a page merely merits a robotic response, it shouldn&amp;rsquo;t be a page.&amp;rdquo;&lt;/p>
&lt;p>In data engineering, the equivalent of a &amp;ldquo;robotic response&amp;rdquo; is the acknowledged-and-unresolved alert. The monitor that fires every Tuesday morning, gets a thumbs up in Teams, gets added to the &amp;ldquo;known issues&amp;rdquo; document, and never gets fixed. The window is now monitored. The fact that it&amp;rsquo;s monitored makes the team feel responsible. The window stays broken.&lt;/p>
&lt;p>There&amp;rsquo;s a term for this: observability theatre. The infrastructure of visibility exists. The dashboards are green. Nobody&amp;rsquo;s actually looking at the glass.&lt;/p>
&lt;p>This is where the broken windows metaphor earns its keep most fully. Wilson and Kelling weren&amp;rsquo;t saying that disorder is bad because it looks bad. They were saying that an unrepaired broken window sends a signal that no one cares — and that signal is the actual mechanism of decay. Monitoring a broken window without repairing it sends exactly the same signal. Possibly a worse one, because now everyone knows the problem is being tracked and nobody&amp;rsquo;s acting on it. The norm becomes: acknowledged problems are not necessarily fixed problems.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-stakeholder-sees-it-differently">The stakeholder sees it differently&lt;/h3>
&lt;/br>
&lt;p>Here is the asymmetry that data teams consistently underestimate.&lt;/p>
&lt;p>When an engineer looks at a broken window in a data pipeline — a column with a known null rate, a test that&amp;rsquo;s been in warn state for three weeks, a DAG that soft-fails every second run — they see technical debt. A problem to eventually be addressed. Something that exists on a spectrum of severity, probably not at the top of the list.&lt;/p>
&lt;p>When a business stakeholder encounters the downstream consequence of that broken window — a dashboard that contradicts a number they just presented to the CFO, a metric that moved in an unexplained direction, a report that doesn&amp;rsquo;t reconcile with another report — they don&amp;rsquo;t think &amp;ldquo;technical debt.&amp;rdquo; They think: &lt;em>can I trust the data team?&lt;/em>&lt;/p>
&lt;p>Benn Stancil — one of the clearest thinkers writing about this — put it well: &amp;ldquo;Trust is built, and blown up, by the outputs — and specifically, the consistency of those outputs.&amp;rdquo; He uses a vivid image that I keep coming back to: &amp;ldquo;Data and the dashboards that display it create a shared sense of reality. Looking at two dashboards that don&amp;rsquo;t match is like looking out two adjacent windows and not seeing the same thing.&amp;rdquo;&lt;/p>
&lt;p>That&amp;rsquo;s the experience of broken-window propagation from the stakeholder&amp;rsquo;s side. Two adjacent windows. Different views. A reality that doesn&amp;rsquo;t cohere.&lt;/p>
&lt;p>Monte Carlo&amp;rsquo;s 2023 State of Data Quality research put a number to the trust inversion that most data engineers already sense. In 2022, 47% of respondents reported that business stakeholders identified data issues first &amp;ldquo;all or most of the time&amp;rdquo; — more often than the data team itself. By 2023, that figure had risen to 74%. &lt;em>(Three in four. Let that land.)&lt;/em>&lt;/p>
&lt;p>Think about what that means. In three out of four data incidents, the people who rely on the data found the problem before the people who built it. The pipeline runs, the test passes, the dashboard renders, and somewhere downstream a business analyst is staring at a number that doesn&amp;rsquo;t look right and is about to send a message that starts with: &amp;ldquo;Quick question about this figure&amp;hellip;&amp;rdquo;&lt;/p>
&lt;p>The damage isn&amp;rsquo;t technical. It&amp;rsquo;s relational. And it compounds in a way that&amp;rsquo;s harder to reverse than any schema migration.&lt;/p>
&lt;p>Thomas Redman — who has spent decades studying data quality — made this point in Harvard Business Review: &amp;ldquo;When data are unreliable, managers quickly lose faith in them and fall back on their intuition to make decisions.&amp;rdquo; Once that happens, you haven&amp;rsquo;t just produced bad data. You&amp;rsquo;ve trained decision-makers to ignore good data too, because they can no longer distinguish between the two.&lt;/p>
&lt;p>The broken window didn&amp;rsquo;t just propagate through the pipeline. It propagated into the culture.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="nobody-owns-the-broken-window">Nobody owns the broken window&lt;/h3>
&lt;/br>
&lt;p>There&amp;rsquo;s a specific pattern worth naming, because it&amp;rsquo;s where most data teams I&amp;rsquo;ve worked with have the most unacknowledged broken windows: the orphaned asset.&lt;/p>
&lt;p>The pipeline that was built by an engineer who left eighteen months ago. The table in the warehouse that exists in the data catalogue but whose owner field says &amp;ldquo;Unknown&amp;rdquo; or, worse, the name of someone who&amp;rsquo;s no longer at the company. The dbt model that runs in production, that feeds two dashboards, that nobody on the current team can fully explain.&lt;/p>
&lt;p>These aren&amp;rsquo;t broken in any obvious sense. They run. They load. The tests pass, or they&amp;rsquo;re set to warn. But they&amp;rsquo;re broken windows in the original sense: they signal that nobody cares. And because nobody owns them, nobody&amp;rsquo;s in a position to repair them even when something goes wrong.&lt;/p>
&lt;p>What typically happens is this: a new engineer joins the team. They need to understand how the data flows. They look at the catalogue. They look at the undocumented table. They look at the model that references it. They look at the Jira ticket from fourteen months ago that says &amp;ldquo;Known issue — downstream teams aware.&amp;rdquo; And they make a rational decision: don&amp;rsquo;t touch it, build around it, replicate it if necessary.&lt;/p>
&lt;p>The broken window has now inspired a new window. Same mechanism as the Bronx car. Different materials.&lt;/p>
&lt;p>The organisational research on this is unambiguous. Ron Westrum&amp;rsquo;s typology of organisational cultures — validated empirically by the DORA research programme across thousands of software teams — found that information flow predicts safety and performance more reliably than almost any structural variable. In pathological cultures, information is hoarded or withheld for political reasons. In generative cultures, information flows freely, failures are shared, and problems get fixed because surfacing problems is rewarded rather than penalised.&lt;/p>
&lt;p>Amy Edmondson&amp;rsquo;s research on psychological safety adds the other half: 85% of employees have withheld important information from their manager due to fear of speaking up. In data teams, this looks like: the junior engineer who noticed the null rate had been climbing for two weeks and didn&amp;rsquo;t raise it because they weren&amp;rsquo;t sure it was their call to make. The analyst who suspected the metric definition had drifted but didn&amp;rsquo;t want to slow down the dashboard delivery. The data engineer who knew the pipeline was flaky but figured someone more senior would have noticed if it really mattered.&lt;/p>
&lt;p>The broken window gets left unrepaired not because nobody sees it, but because the culture hasn&amp;rsquo;t made repair feel safe or worthwhile.&lt;/p>
&lt;p>Edmondson on this: &amp;ldquo;If there&amp;rsquo;s no bad news, remind yourself: It&amp;rsquo;s not that it&amp;rsquo;s not there. It&amp;rsquo;s that you&amp;rsquo;re not hearing about it.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-original-theorys-mistake--and-ours">The original theory&amp;rsquo;s mistake — and ours&lt;/h3>
&lt;/br>
&lt;p>Before we get to what to actually do about this, it&amp;rsquo;s worth pausing on where the original theory went wrong. Because data engineering is at risk of making exactly the same mistake.&lt;/p>
&lt;p>When Wilson and Kelling published their 1982 Atlantic article, the theory was nuanced. It was about signals and norms. It was explicitly a theory about community — about how residents and police could work together to maintain shared standards in public spaces. It was, in Kelling&amp;rsquo;s own framing, a theory of collective efficacy.&lt;/p>
&lt;p>What cities did with it was zero tolerance. Mass arrests for minor offences. Stop-and-frisk. 685,000 stops in New York City in a single year, more than 85% of them finding nothing at all.&lt;/p>
&lt;p>Kelling&amp;rsquo;s reaction, when he learned how comprehensively his theory had been misapplied: &amp;ldquo;Oh, shit.&amp;rdquo;&lt;/p>
&lt;p>The software world made a version of the same mistake when it imported broken windows thinking. &amp;ldquo;Don&amp;rsquo;t live with broken windows&amp;rdquo; became, for some teams, a linting rule for everything, a zero-tolerance policy for code smells, a culture of perfectionism that burned people out and produced beautiful codebases that never shipped.&lt;/p>
&lt;p>Adam Tornhill&amp;rsquo;s research at CodeScene offers the corrective: not all broken windows matter equally. In a 400,000-line codebase with 89 developers, his analysis found that 4% of the code was responsible for 72% of the defects. The broken windows that needed fixing weren&amp;rsquo;t distributed evenly. They clustered in hotspots — files that were both frequently changed and highly complex. Fix the hotspots. Let the cold, ugly, stable corner of the codebase sit.&lt;/p>
&lt;p>The principle for data engineering is the same. Zero-tolerance data quality enforcement — failing every pipeline on every test at severity error — produces fragile systems and tired teams. It&amp;rsquo;s the data equivalent of arresting everyone for jaywalking. The signal gets lost in the noise.&lt;/p>
&lt;p>What the original theory was actually pointing at was something more like: maintain the norm. Make it visible that people care. Ensure that the broken windows that matter — the ones that propagate, the ones in the high-traffic, high-trust parts of the system — get fixed promptly and publicly. Not every window. The ones that signal whether anyone&amp;rsquo;s paying attention.&lt;/p>
&lt;hr>
&lt;h3 id="what-collective-efficacy-looks-like-in-a-data-team">What collective efficacy looks like in a data team&lt;/h3>
&lt;/br>
&lt;p>Sampson, Raudenbush, and Earls — the Chicago sociologists who ran the most rigorous empirical test of broken windows theory — found that the variable that actually predicted neighbourhood safety wasn&amp;rsquo;t the presence or absence of disorder. It was &lt;em>collective efficacy&lt;/em>: the combination of social cohesion and shared willingness to intervene. Neighbours who knew each other, trusted each other, and were willing to act on behalf of each other&amp;rsquo;s wellbeing.&lt;/p>
&lt;p>The policy prescription that emerges from their work is very different from zero tolerance. It&amp;rsquo;s community investment. Shared ownership. Making the costs of non-intervention visible.&lt;/p>
&lt;p>The equivalent in data engineering starts with one thing, and if you do nothing else in this list, do this one:&lt;/p>
&lt;p>&lt;strong>Make propagation visible before anything else.&lt;/strong>&lt;/p>
&lt;p>Column-level data lineage — now available in most modern data observability platforms and increasingly in the open-source OpenLineage standard — lets you answer the question: if this column is wrong, what does it break? That question should be answerable in seconds, not hours. Teams that can visualise propagation chains respond to broken windows faster because they can see the radius of the damage before they decide whether to act.&lt;/p>
&lt;p>This matters beyond incident response. When you can show an engineer that the null column they&amp;rsquo;re tolerating in a staging model feeds seven downstream gold-layer tables, three dashboards, and a Snowflake share that two other teams consume — the calculus on whether to fix it changes. The broken window stops being an abstract code quality concern and becomes a blast radius. That&amp;rsquo;s a much more compelling argument for repair than &amp;ldquo;we should improve our data quality culture.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Treat ownership as load-bearing infrastructure, not housekeeping.&lt;/strong>&lt;/p>
&lt;p>Every pipeline, every table, every model should have a named owner. Not a team. A person. The Jira ticket that says &amp;ldquo;Known issue — downstream teams aware&amp;rdquo; with no assigned owner is the data equivalent of a broken window with an orange cone next to it. The cone acknowledges the hazard. Nobody&amp;rsquo;s fixing the glass.&lt;/p>
&lt;p>The counterargument is always resourcing — &amp;ldquo;we don&amp;rsquo;t have time to own everything properly.&amp;rdquo; That&amp;rsquo;s true, and worth taking seriously. But the right response isn&amp;rsquo;t to pretend you own things you don&amp;rsquo;t. It&amp;rsquo;s to make orphaned assets visible and have an honest conversation about whether the organisation can afford to run production pipelines with no accountable maintainer. Most of the time, when the question is asked that directly, the answer is no.&lt;/p>
&lt;p>&lt;strong>Calibrate your tolerance patterns deliberately — and actually revisit them.&lt;/strong>&lt;/p>
&lt;p>The dbt &lt;code>severity: warn&lt;/code> setting and Airflow&amp;rsquo;s &lt;code>soft_fail&lt;/code> are legitimate tools when they&amp;rsquo;re conscious decisions: &amp;ldquo;this condition is a signal worth tracking but not a pipeline-stopper, and here&amp;rsquo;s the threshold at which that changes.&amp;rdquo; The problem is that almost nobody uses them that way. They&amp;rsquo;re the path of least resistance to avoid a broken CI build — and six months later you audit your test results and discover forty tests set to warn that haven&amp;rsquo;t been at zero failures since the day they were written.&lt;/p>
&lt;p>Treat warn-severity tests the way you treat Jira tickets that never get triaged. Set a review cadence. If a test has been consistently warning for more than two weeks without an associated investigation, it&amp;rsquo;s either a bug that needs fixing or a threshold that needs changing. &amp;ldquo;Known issue&amp;rdquo; is not a status. It&amp;rsquo;s an admission.&lt;/p>
&lt;p>&lt;strong>Fix alert fatigue before it hollows out your monitoring culture.&lt;/strong>&lt;/p>
&lt;p>The Teams channel where every data quality alert lands is the digital equivalent of a neighbourhood where every broken window gets photographed and logged and nobody ever fixes one. The log is evidence that someone noticed. It&amp;rsquo;s not evidence that anyone will act.&lt;/p>
&lt;p>Set alert thresholds that produce actionable signals. The Google SRE principle applies directly: if an alert merits a robotic response, it shouldn&amp;rsquo;t be an alert. If your first instinct on seeing a particular monitor fire is to click acknowledge and move on, that monitor is producing noise, not signal. Change it or delete it. Grouping alerts by lineage — &amp;ldquo;these five monitors fired because of one upstream schema change&amp;rdquo; rather than five separate pings — reduces volume while making propagation visible at the moment of failure, which is exactly when you want it.&lt;/p>
&lt;p>&lt;strong>Make broken windows visible to leadership, because right now the cost is invisible.&lt;/strong>&lt;/p>
&lt;p>Data quality work is famously hard to demonstrate. Pipelines that run cleanly leave no artefact. A well-maintained model with a 0% null rate looks identical to a freshly built one. There&amp;rsquo;s no natural demo format for &amp;ldquo;nothing bad happened this week.&amp;rdquo;&lt;/p>
&lt;p>This invisibility is part of why broken windows accumulate. The cost of prevention is hidden in maintenance time that doesn&amp;rsquo;t get counted. The cost of failure gets absorbed by analysts who spend their Tuesdays reconciling numbers instead of answering strategic questions, by data engineers who spend their Fridays investigating stakeholder tickets, by business decisions made on incorrect information that can&amp;rsquo;t be traced back to a specific incident.&lt;/p>
&lt;p>Quantify the propagation radius when incidents do occur. How many downstream models were affected? How many stakeholders were exposed to incorrect data, and for how long? What was the resolution effort in hours? Those numbers, tracked consistently, build a case for investment that abstract arguments about data quality never will.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-window-was-broken-before-i-noticed">The window was broken before I noticed&lt;/h3>
&lt;/br>
&lt;p>What we ended up doing was building the fix into the operating rhythm. A scheduled data drift catch-up: a full snapshot weekly, or monthly, depending on the volatility of the Salesforce object — not to respond to an incident, but to systematically correct the accumulated drift before it became visible to anyone downstream. We stopped waiting for the escalation. We made the repair part of the architecture.&lt;/p>
&lt;p>The broken window was still there, technically. The delta logic still had its blind spots around hidden relationships. But we stopped waiting for it to propagate before we addressed it. Some broken windows aren&amp;rsquo;t things you permanently seal — they&amp;rsquo;re things you build a maintenance routine around. Knowing that is the fix.&lt;/p>
&lt;p>What stayed with me wasn&amp;rsquo;t the technical solution, which was straightforward enough once we committed to it. What stayed with me was the calendar predictability of the escalation cycle before the fix. The fact that we had a known issue, a known workaround, documentation of both — and still managed to have the same downstream discovery, the same investigation, the same remediation conversation, almost exactly twelve months apart.&lt;/p>
&lt;p>The window wasn&amp;rsquo;t hidden. It was visible in the delta logs if you knew where to look. We just hadn&amp;rsquo;t made it someone&amp;rsquo;s job to look, and we hadn&amp;rsquo;t made repair part of the routine. We&amp;rsquo;d fixed the glass, filed the paperwork, and assumed the problem was solved. Until the same signal appeared in the same downstream reports, a year later.&lt;/p>
&lt;hr>
&lt;p>What I keep coming back to, though, is something that was true of that experience and is true of most of the data quality failures I&amp;rsquo;ve seen since: the window wasn&amp;rsquo;t broken secretly. It wasn&amp;rsquo;t hidden. It was visible, it was acknowledged, and it was left.&lt;/p>
&lt;p>We had the monitoring. We had the test. We had the Jira ticket.&lt;/p>
&lt;p>We had, in other words, all the infrastructure of concern. What we didn&amp;rsquo;t have was the collective agreement that repair mattered — that the propagation radius of that one broken window was large enough, and trust-eroding enough, that it justified stopping what we were doing and fixing the glass.&lt;/p>
&lt;p>That&amp;rsquo;s the thing broken windows theory is actually about, underneath all the criminology and the code smells and the schema drift. It&amp;rsquo;s about the signal that unrepaired damage sends. Not to the criminals or the developers or the data consumers. To the people who are supposed to care about the system.&lt;/p>
&lt;p>When a broken window sits long enough in a data pipeline, it stops being a problem and starts being a norm. The new engineer doesn&amp;rsquo;t flag it — they work around it. The analyst doesn&amp;rsquo;t escalate it — they add a caveat to their report. The data engineer doesn&amp;rsquo;t fix it — they document it.&lt;/p>
&lt;p>And somewhere downstream, a business decision gets made on numbers that were broken before anyone thought to check.&lt;/p>
&lt;p>The window isn&amp;rsquo;t just in the pipeline. The window is in the standard you&amp;rsquo;re willing to keep.&lt;/p></content:encoded><category>Data Engineering</category><category>Data Quality</category><category>Data Quality</category><category>Data Pipelines</category><category>Technical Debt</category><category>Data Observability</category><category>dbt</category><category>Apache Airflow</category><category>Data Culture</category><category>Pipeline Architecture</category></item><item><title>Five Worlds of Data Engineering</title><link>https://ghostinthedata.info/posts/2026/2026-05-02-five-worlds-data-engineering/</link><pubDate>Sat, 02 May 2026 09:00:00 +1000</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-05-02-five-worlds-data-engineering/</guid><author>Chris Hillman</author><description>Not all data engineering is the same. The modern analytics shop, the enterprise legacy estate, the product engine, the regulated pipeline, and the internal platform each play by different rules — and most advice only applies to one of them.</description><content:encoded>&lt;p>You watch a conference talk about implementing data contracts, and nobody mentions that the advice assumes you have multiple teams producing data — which you don&amp;rsquo;t. You read a post declaring &amp;ldquo;if you&amp;rsquo;re still using stored procedures in 2026, you&amp;rsquo;re doing it wrong,&amp;rdquo; and the comments erupt. Half the people are nodding along. Half are furious. Both sides are right. They&amp;rsquo;re just living in different worlds and don&amp;rsquo;t realise it.&lt;/p>
&lt;p>That mismatch — smart people giving each other advice that doesn&amp;rsquo;t apply — is the thing that almost never gets named. And the reason it doesn&amp;rsquo;t get named is that most public data engineering discourse is produced by and for one particular world, while pretending to speak for all of them.&lt;/p>
&lt;p>I&amp;rsquo;ve worked across a few of these worlds myself — SQL Server migrations to Teradata, then Teradata to S3, regulatory reporting under APRA and ASIC, a data mesh initiative at scale, and now a university environment that runs closer to a startup than anything I expected. The advice that kept me out of trouble in one would have gotten me fired in the other. That experience is what&amp;rsquo;s behind this taxonomy.&lt;/p>
&lt;p>I think there are five worlds here. Sometimes they intersect. Often they don&amp;rsquo;t.&lt;/p>
&lt;br>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="world-1-the-modern-analytics-shop">World 1: The Modern Analytics Shop&lt;/h3>
&lt;br>
&lt;p>This is the world most people picture when they hear &amp;ldquo;data engineering.&amp;rdquo; A team of two to ten engineers at a startup, scale-up, or mid-size company. Cloud-native from day one, or recently migrated. The stack reads like a vendor sponsor list: Snowflake or BigQuery or Databricks, dbt for transformations, Fivetran or Airbyte for ingestion, Looker or Metabase for dashboards. Everything managed. Everything SaaS. GitHub Actions wiring it together.&lt;/p>
&lt;p>What matters here is speed. Getting value to stakeholders fast. Shipping a working dashboard before the quarterly review. Iterating on models without a change advisory board. The team is small enough that everyone knows the codebase, and requirements come from a Teams message, not a 40-page specification document.&lt;/p>
&lt;p>I find myself in this world more than I expected right now. The current role is an institution that by any measure is large and complex — but the data team is small, the autonomy is real, and the energy genuinely feels like a startup. New tools, fast decisions, the ability to actually make change. It&amp;rsquo;s a reminder that World 1 isn&amp;rsquo;t only about company size. It&amp;rsquo;s about how the team operates.&lt;/p>
&lt;p>This is also a great world to work in, especially early in your career. The tooling is mature, the feedback loops are tight, and the problems are tractable.&lt;/p>
&lt;p>Here&amp;rsquo;s the thing, though. This is also the world that roughly 80% of public data engineering content is written for and about. Not because it&amp;rsquo;s the most common world — it isn&amp;rsquo;t — but because it&amp;rsquo;s the most fundable. The vendor-funded conference talks, the sponsored podcasts, the developer advocate blog posts, the LinkedIn hot takes — they overwhelmingly reflect this world. Developer advocates write about the stack their employer sells (this is not a dig — it&amp;rsquo;s the job). Conference sponsors want talks that showcase their tools in the most flattering light. The technical depth suffers because the incentive is awareness, not education.&lt;/p>
&lt;p>None of that is wrong. But it creates a gravitational pull that distorts the whole discourse. When someone writes &amp;ldquo;the right way to do data engineering,&amp;rdquo; they almost always mean &lt;em>this&lt;/em> world. And if you&amp;rsquo;re in a different one and don&amp;rsquo;t recognise the mismatch, you end up feeling like you&amp;rsquo;re doing it wrong when you&amp;rsquo;re actually just solving a different problem.&lt;/p>
&lt;p>&lt;strong>Advice that works here but rarely travels:&lt;/strong> &amp;ldquo;Just use dbt.&amp;rdquo; &amp;ldquo;Schema-on-read is fine for now.&amp;rdquo; &amp;ldquo;You don&amp;rsquo;t need a data catalog yet.&amp;rdquo; &amp;ldquo;Start with a &lt;a href="https://ghostinthedata.info/posts/2025/2025-11-07-effective-data-modelling/" target="_blank" rel="noopener">star schema&lt;/a> and iterate.&amp;rdquo;&lt;/p>
&lt;br>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="world-2-the-enterprise-legacy-estate">World 2: The Enterprise Legacy Estate&lt;/h3>
&lt;br>
&lt;p>This is the world of large organisations — banks, insurers, manufacturers, utilities, healthcare systems, government agencies — with data infrastructure that predates the cloud. Often predates the people currently maintaining it. Teams of twenty to two hundred data professionals spread across business units that don&amp;rsquo;t always talk to each other.&lt;/p>
&lt;p>The stack tells a different story: Informatica, SSIS, Ab Initio, Teradata, Oracle, maybe an on-prem Hadoop cluster that someone championed in 2014 and nobody&amp;rsquo;s had the political capital to decommission. Perhaps some Snowflake or Databricks grafted on top, creating a hybrid that&amp;rsquo;s more complex than either system alone.&lt;/p>
&lt;p>I lived this migration arc twice — once from SQL Server to Teradata, and again from Teradata to S3 and Starburst. Each time, the temptation was to treat the old system as a problem to escape rather than a body of knowledge to decode. Each time, the engineers who did the most damage were the ones who arrived with the new stack and immediately started designing around the old one rather than understanding it first.&lt;/p>
&lt;p>What matters here is stability. Not breaking things. Migration plans that span years, not sprints. Governance and lineage aren&amp;rsquo;t aspirational — they&amp;rsquo;re audit requirements. And politics, because the data warehouse your predecessor built in 2011 is somebody&amp;rsquo;s empire (you know the one), and you can&amp;rsquo;t &amp;ldquo;just replace it&amp;rdquo; without navigating a web of organisational power dynamics that no architecture diagram captures.&lt;/p>
&lt;p>This is the world where &lt;a href="https://ghostinthedata.info/posts/2026/2026-03-09-data-model-not-broken-part-1/" target="_blank" rel="noopener">refactoring beats rebuilding&lt;/a> every time. That fact table with 200 columns? Those bridge tables nobody understands? The slowly-changing-dimension-within-a-slowly-changing-dimension? They&amp;rsquo;re not bugs — they&amp;rsquo;re reality encoded. Every weird modelling choice represents a business rule someone fought to understand. The &amp;ldquo;clean&amp;rdquo; data vault remodel will eventually end up with the same complexity, just distributed across more tables with more confusing names.&lt;/p>
&lt;p>Advice from World 1 can be actively dangerous here. &amp;ldquo;Just rewrite it&amp;rdquo; destroys institutional memory. &amp;ldquo;Adopt a lakehouse architecture&amp;rdquo; sounds great until you realise you have 4,000 stored procedures that encode fifteen years of business rules, and nobody documented them. The engineer in this world isn&amp;rsquo;t slow because they&amp;rsquo;re behind the curve. They&amp;rsquo;re careful because the cost of breaking something is measured in regulatory findings and executive phone calls, not a failed CI check.&lt;/p>
&lt;p>&lt;strong>Advice that works here but rarely travels:&lt;/strong> &amp;ldquo;Document everything before you touch it.&amp;rdquo; &amp;ldquo;Strangler fig, never big bang.&amp;rdquo; &amp;ldquo;Spend more time understanding &lt;em>why&lt;/em> it was built this way than planning what to replace it with.&amp;rdquo; &amp;ldquo;The weird WHERE clause isn&amp;rsquo;t a bug — it&amp;rsquo;s institutional memory.&amp;rdquo;&lt;/p>
&lt;br>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="world-3-the-product-engine">World 3: The Product Engine&lt;/h3>
&lt;br>
&lt;p>This is the world where data engineering directly powers an outcome the business delivers. That might be real-time personalisation, recommendation engines, or pricing algorithms. But it also includes building data products that drive campaigns, marketing attribution, and business decisions — cases where the engineer&amp;rsquo;s output flows into something customers or commercial teams act on directly, rather than landing in a dashboard that an analyst reviews on Monday morning.&lt;/p>
&lt;p>The real-time variant of this world has its own stack: Kafka or Kinesis for streaming, Spark or Flink for processing, feature stores for serving machine learning models. Often custom infrastructure because off-the-shelf tools can&amp;rsquo;t meet the latency requirements.&lt;/p>
&lt;p>But the defining characteristic isn&amp;rsquo;t milliseconds — it&amp;rsquo;s consequence. If a pipeline breaks in this world, someone notices immediately. A campaign fires with the wrong audience. A pricing decision gets made on stale data. A recommendation engine serves the same product to everyone because the features stopped updating overnight. The engineer is accountable to an outcome, not just a pipeline.&lt;/p>
&lt;p>I&amp;rsquo;ve worked in this space building data products that powered business campaigns and marketing decisions. Not real-time serving in the p99 latency sense, but consequential enough that a broken pipeline meant a broken business process. That accountability changes how you think about testing, monitoring, and what &amp;ldquo;done&amp;rdquo; actually means.&lt;/p>
&lt;p>The skills that matter here — operational thinking, system design, understanding how your data is consumed downstream — overlap more with software engineering and product thinking than with SQL and dbt. When someone says &amp;ldquo;data engineering is just SQL and orchestration,&amp;rdquo; someone in this world quietly closes the tab.&lt;/p>
&lt;p>&lt;strong>Advice that works here but rarely travels:&lt;/strong> &amp;ldquo;Treat pipelines like production services.&amp;rdquo; &amp;ldquo;Your tests need to run in CI, not in a notebook.&amp;rdquo; &amp;ldquo;Schema evolution is a deployment problem, not a modelling problem.&amp;rdquo; &amp;ldquo;The consumer of your data is your customer — know what breaks their day.&amp;rdquo;&lt;/p>
&lt;br>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="world-4-the-regulated-pipeline">World 4: The Regulated Pipeline&lt;/h3>
&lt;br>
&lt;p>This is the world defined by compliance. In Australia: APRA for prudential standards, ASIC for financial services conduct, AFCA for dispute resolution obligations, and the Privacy Act for anything touching personal information. Internationally: HIPAA, SOX, MiFID, GDPR. The defining characteristic isn&amp;rsquo;t the technology — it&amp;rsquo;s that regulatory requirements shape every architectural decision before the first line of code is written.&lt;/p>
&lt;p>The stack is whatever passed the security review. Often years behind the cutting edge because new tools need months of compliance evaluation before they&amp;rsquo;re approved. Data lineage tools aren&amp;rsquo;t nice-to-haves — they&amp;rsquo;re audit requirements. Encryption isn&amp;rsquo;t a best practice — it&amp;rsquo;s a legal mandate. Access controls aren&amp;rsquo;t &amp;ldquo;we should get around to that&amp;rdquo; — they&amp;rsquo;re the first thing you build.&lt;/p>
&lt;p>What matters is auditability. Can you answer &amp;ldquo;who accessed what, when, and why&amp;rdquo; for any record in the system? Can you prove data lineage from source to report? Can you demonstrate that a deletion request was honoured within the legally mandated timeframe? Your data deletion pipeline is as important as your ingestion pipeline, and it needs the same rigour.&lt;/p>
&lt;p>I spent significant time in this world — financial services, where regulatory reporting wasn&amp;rsquo;t a background concern but a core deliverable. APRA and ASIC don&amp;rsquo;t ask nicely. The audit wasn&amp;rsquo;t a hypothetical and the regulator&amp;rsquo;s question list arrived without warning. The engineers I worked alongside weren&amp;rsquo;t slow because they lacked ambition. They were deliberate because the cost of getting it wrong wasn&amp;rsquo;t a postmortem — it was a regulatory finding, a remediation programme, and occasionally a front-page story.&lt;/p>
&lt;p>The trap for engineers entering this world from World 1 is assuming that governance is bureaucracy. It isn&amp;rsquo;t. Governance &lt;em>is&lt;/em> architecture. The compliance requirements aren&amp;rsquo;t obstacles to good engineering — they&amp;rsquo;re constraints that shape what good engineering looks like. The best engineers I&amp;rsquo;ve worked with in regulated environments don&amp;rsquo;t fight the constraints. They design systems where compliance is a property of the architecture itself, not a layer bolted on top.&lt;/p>
&lt;p>&lt;strong>Advice that works here but rarely travels:&lt;/strong> &amp;ldquo;Governance is architecture, not bureaucracy.&amp;rdquo; &amp;ldquo;If you can&amp;rsquo;t prove lineage, you can&amp;rsquo;t ship it.&amp;rdquo; &amp;ldquo;The security review IS the sprint.&amp;rdquo; &amp;ldquo;Your data deletion pipeline is as important as your data ingestion pipeline.&amp;rdquo;&lt;/p>
&lt;br>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="world-5-the-internal-platform">World 5: The Internal Platform&lt;/h3>
&lt;br>
&lt;p>This is the world of organisations large enough that the data team has split into &amp;ldquo;platform&amp;rdquo; and &amp;ldquo;domain&amp;rdquo; functions. The platform team builds the infrastructure, tooling, and self-service capabilities that other data teams consume. Data mesh adopters. Companies with fifty or more data practitioners who realised that a centralised team can&amp;rsquo;t scale to serve every domain&amp;rsquo;s needs.&lt;/p>
&lt;p>The stack is about abstraction and enablement: Kubernetes, self-hosted Airflow or Dagster or Prefect, internal developer portals, data contracts, schema registries, internal PyPI packages. Often custom abstractions layered on top of cloud services, designed to give domain teams guardrails without bottlenecks.&lt;/p>
&lt;p>I experienced this world through ANZx — ANZ&amp;rsquo;s digital platform initiative — where data mesh concepts were applied at scale. Not as a theoretical framework on a conference slide, but as a real attempt to distribute data ownership across domains while maintaining coherence at the platform level. The challenge wasn&amp;rsquo;t the technology. It was getting domain teams to think like data product owners rather than data consumers. That shift is harder than any infrastructure problem.&lt;/p>
&lt;p>What matters here is adoption. Not how many pipelines &lt;em>you&lt;/em> build, but how many pipelines your consumers build without needing your help. Your success metric isn&amp;rsquo;t &amp;ldquo;pipelines shipped&amp;rdquo; — it&amp;rsquo;s &amp;ldquo;time to first pipeline for a new domain team.&amp;rdquo; Developer experience for internal consumers is your product, and if the experience is poor, your consumers will route around you. They&amp;rsquo;ll spin up their own Snowflake account, write their own ingestion scripts, and create exactly the kind of ungoverned sprawl your platform was supposed to prevent.&lt;/p>
&lt;p>If adopting your platform requires a Jira ticket and a two-week wait, you&amp;rsquo;ve already lost. The shadow pipelines will multiply, and nobody will tell you until the audit.&lt;/p>
&lt;p>&lt;strong>Advice that works here but rarely travels:&lt;/strong> &amp;ldquo;Treat internal teams as customers with choices.&amp;rdquo; &amp;ldquo;Self-service is the goal, not centralised delivery.&amp;rdquo; &amp;ldquo;Your documentation IS the product.&amp;rdquo; &amp;ldquo;Measure adoption, not output.&amp;rdquo;&lt;/p>
&lt;br>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="so-what">So What?&lt;/h3>
&lt;br>
&lt;p>Most things in data engineering are the same no matter which world you&amp;rsquo;re in. Data quality matters everywhere. Testing matters everywhere. Documentation matters everywhere. Understanding the business context behind the data — that&amp;rsquo;s universal, and I&amp;rsquo;d argue it&amp;rsquo;s the &lt;a href="https://ghostinthedata.info/posts/2025/2025-02-08-business-context-guide/" target="_blank" rel="noopener">most undervalued skill&lt;/a> in the profession.&lt;/p>
&lt;p>But not everything transfers. And when somebody tells you about methodology — at a conference, on LinkedIn, in a blog post (including mine) — it&amp;rsquo;s worth thinking about which world they&amp;rsquo;re coming from.&lt;/p>
&lt;p>Here&amp;rsquo;s something I&amp;rsquo;ve noticed when hiring vetran data engineers: the ones who stand out aren&amp;rsquo;t necessarily the deepest specialists in any one world. They&amp;rsquo;re the ones who&amp;rsquo;ve visited enough worlds to know the laws. They don&amp;rsquo;t have to have lived in each one for years — but they&amp;rsquo;ve spent enough time in the regulated environment to understand why governance isn&amp;rsquo;t bureaucracy. Enough time in the enterprise to know that &amp;ldquo;just rewrite it&amp;rdquo; destroys things. Enough time in the product engine to feel what accountability to an outcome actually means. That layered experience is what hardens an engineer. It&amp;rsquo;s what lets them walk into an unfamiliar environment and read it quickly — recognising the constraints, the trade-offs, the things that will matter before anyone tells them.&lt;/p>
&lt;p>There&amp;rsquo;s also something worth saying about what these worlds are not: interchangeable, or combinable. I&amp;rsquo;ve never seen an organisation that genuinely encompasses all five. You occasionally see attempts — usually during a data mesh initiative — and they tend to produce something more complex than any individual world, with the clarity of none of them. The worlds don&amp;rsquo;t collapse into a super-universe. They coexist, imperfectly, inside large organisations. A regulated pipeline team and a modern analytics shop can operate within the same company and have almost nothing in common in terms of how they work, what they value, and what good looks like.&lt;/p>
&lt;p>The tradeoff is real and worth naming plainly: the content that&amp;rsquo;s most abundant — the conference talks, the vendor posts, the LinkedIn hot takes — is calibrated for a world that isn&amp;rsquo;t yours if you&amp;rsquo;re in World 2, 3, 4, or 5. There&amp;rsquo;s more of it than ever, and it&amp;rsquo;s easier than ever to access. What you give up is the ability to consume it passively. Filtering for relevance is now part of the job, and nobody puts that in the job description.&lt;/p>
&lt;p>When you read advice — any advice, including everything on &lt;a href="https://ghostinthedata.info/posts/" target="_blank" rel="noopener">this blog&lt;/a> — ask yourself which world it&amp;rsquo;s coming from. If it doesn&amp;rsquo;t apply to yours, that&amp;rsquo;s not a failing on your part or theirs. It just means they&amp;rsquo;re in a different world.&lt;/p>
&lt;p>Now you know to notice.&lt;/p>
&lt;br></content:encoded><category>Data Engineering</category><category>Career Development</category><category>Data Engineering</category><category>Modern Data Stack</category><category>Enterprise</category><category>Data Architecture</category><category>Career Development</category><category>Leadership</category></item><item><title>Your Data Platform Costs More Than It Should</title><link>https://ghostinthedata.info/posts/2026/2026-04-25-cost-management/</link><pubDate>Sat, 25 Apr 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-04-25-cost-management/</guid><author>Chris Hillman</author><description>A practical guide to understanding, measuring, and reducing your Snowflake and AWS data platform costs — starting with the habits that actually move the needle.</description><content:encoded>&lt;p>Let me tell you about the moment I stopped treating cloud costs as someone else&amp;rsquo;s problem.&lt;/p>
&lt;p>We were three months into a Snowflake migration. Everything was humming. Pipelines were green, dashboards were fast, the analytics team was happier than I&amp;rsquo;d seen them before. I felt good about the work we&amp;rsquo;d done.&lt;/p>
&lt;p>Then finance forwarded me the invoice.&lt;/p>
&lt;p>The number wasn&amp;rsquo;t catastrophic. But it was significantly higher than what we&amp;rsquo;d budgeted, and when I started digging, I couldn&amp;rsquo;t explain where most of it was going. I knew we had warehouses running. I knew we had pipelines executing. But I couldn&amp;rsquo;t tell you which warehouse was responsible for what cost, which pipelines were the expensive ones, or whether the money was well spent. I had built a platform I was proud of — and I had no idea what it actually cost to operate.&lt;/p>
&lt;p>That&amp;rsquo;s the moment that changed how I think about data engineering. Not because of the dollar amount, but because of the realisation underneath it: &lt;strong>I had built something I couldn&amp;rsquo;t explain to the people paying for it.&lt;/strong> And if I couldn&amp;rsquo;t explain it, I couldn&amp;rsquo;t defend it. And if I couldn&amp;rsquo;t defend it, someone else would make the decisions for me — someone who didn&amp;rsquo;t understand why the platform mattered.&lt;/p>
&lt;br>
&lt;p>I&amp;rsquo;m telling you this because cost management is one of those things that sounds like a finance problem until you experience the consequences firsthand. It&amp;rsquo;s not about being cheap. It&amp;rsquo;s about being intentional. It&amp;rsquo;s about knowing that every credit you spend is buying something valuable — and being able to prove it when someone asks.&lt;/p>
&lt;p>The data engineers who understand their costs don&amp;rsquo;t just save money. They earn trust. They get budget for the projects that matter. They sleep better because they&amp;rsquo;ve eliminated the waste that eventually becomes someone else&amp;rsquo;s excuse to cut headcount or freeze hiring.&lt;/p>
&lt;p>This article is about building that understanding. Not with a vendor&amp;rsquo;s optimisation tool or a consultant&amp;rsquo;s audit — but with the habits, queries, and mental models that let you own your platform&amp;rsquo;s economics from the inside. Everything here is grounded in Snowflake and AWS, with specific code you can run today.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="you-cant-optimise-what-you-cant-see">You can&amp;rsquo;t optimise what you can&amp;rsquo;t see&lt;/h3>
&lt;br>
&lt;p>Before you touch a single warehouse configuration, you need to answer one question: &lt;strong>where is the money going?&lt;/strong>&lt;/p>
&lt;p>Most teams skip this step. They read a blog post about auto-suspend settings, change a few defaults, and call it optimisation. That&amp;rsquo;s like going on a diet by switching to diet soda while eating three pizzas a day. The soda wasn&amp;rsquo;t the problem.&lt;/p>
&lt;p>Here&amp;rsquo;s the query I run first on every Snowflake environment I touch. It tells you which warehouses are consuming the most credits over the last 30 days:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> estimated_cost_usd, &lt;span style="color:#75715e">-- adjust your credit price
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, start_time)) &lt;span style="color:#66d9ef">AS&lt;/span> active_days,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, start_time)), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> credits_per_day
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> snowflake.account_usage.warehouse_metering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Run that. Right now. I&amp;rsquo;ll wait.&lt;/p>
&lt;p>If you&amp;rsquo;re like most teams, 70–80% of your credits come from two or three warehouses. That&amp;rsquo;s your starting point. Not everything — just the expensive stuff.&lt;/p>
&lt;br>
&lt;p>Now do the same thing for queries. This one finds your top 20 most expensive queries by bytes scanned:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> query_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> query_text,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_elapsed_time &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> elapsed_seconds,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bytes_scanned &lt;span style="color:#f92672">/&lt;/span> POWER(&lt;span style="color:#ae81ff">1024&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> gb_scanned,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> partitions_scanned,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> partitions_total,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(partitions_scanned &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(partitions_total, &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> pct_partitions_scanned
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.query_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> bytes_scanned &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bytes_scanned &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LIMIT&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Pay attention to that &lt;code>pct_partitions_scanned&lt;/code> column. If you see queries scanning 90–100% of a table&amp;rsquo;s partitions, those queries aren&amp;rsquo;t benefiting from clustering or partition pruning. That&amp;rsquo;s where the big wins hide.&lt;/p>
&lt;p>This 15-minute exercise — top warehouses, top queries — tells you more about your cost profile than any dashboard. It&amp;rsquo;s the equivalent of checking your bank statement before creating a budget. Obvious in hindsight. Almost nobody does it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-idle-tax-is-your-single-biggest-waste">The idle tax is your single biggest waste&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s what nobody tells you about Snowflake billing: you pay for compute in 60-second minimums. Every time a warehouse resumes from suspension, you&amp;rsquo;re billed for at least one minute — regardless of whether the query takes 2 seconds or 58 seconds.&lt;/p>
&lt;p>This matters more than it sounds. Picture a BI tool like Metabase or Tableau hitting your warehouse with 20 small metadata queries over 15 minutes. If your warehouse auto-suspends after 5 minutes (Snowflake&amp;rsquo;s default for many setups), it might suspend and resume multiple times during that window. Each resume triggers another 60-second charge.&lt;/p>
&lt;p>Twenty queries that take 3 seconds each? That&amp;rsquo;s 60 seconds of actual compute. But if the warehouse suspends and resumes 4 times, you&amp;rsquo;re billed for 240 seconds. A 4x overhead.&lt;/p>
&lt;p>The fix is straightforward but requires thought:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- For transformation warehouses (predictable, bursty workloads)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE transform_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;MEDIUM&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- For BI/dashboard warehouses (frequent small queries)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE bi_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">300&lt;/span> &lt;span style="color:#75715e">-- 5 min keeps it warm between dashboard interactions
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SMALL&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- For ad-hoc/analyst warehouses (unpredictable, intermittent)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE adhoc_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;XSMALL&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The principle: &lt;strong>transformation warehouses should suspend aggressively&lt;/strong> because they run in defined windows with gaps between runs. BI warehouses should stay warm a bit longer because dashboard users generate clusters of queries with short pauses in between. Ad-hoc warehouses should be small and aggressive — analysts can tolerate a 1–2 second resume delay.&lt;/p>
&lt;p>Here&amp;rsquo;s how to find warehouses that are running but idle — the silent cost killer:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used_compute) &lt;span style="color:#66d9ef">AS&lt;/span> compute_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used_cloud_services) &lt;span style="color:#66d9ef">AS&lt;/span> cloud_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used_compute))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used), &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> pct_idle_cost
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.warehouse_metering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">HAVING&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pct_idle_cost &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>pct_idle_cost&lt;/code> is above 20% on any warehouse, you&amp;rsquo;re burning money on idle time. Tighten the auto-suspend, or investigate what&amp;rsquo;s keeping the warehouse awake between queries.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-most-expensive-query-is-the-one-that-scans-everything">The most expensive query is the one that scans everything&lt;/h3>
&lt;br>
&lt;p>Snowflake charges you for the compute time your queries consume, and nothing drives compute time like full table scans. The two most common culprits are queries that wrap filter columns in functions, and queries that select more columns than they need.&lt;/p>
&lt;p>Here&amp;rsquo;s what I mean. This query looks innocent:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expensive: function on the filter column disables pruning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id, customer_id, total_amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analytics.fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE(order_timestamp) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;2026-03-01&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But it&amp;rsquo;s quietly terrible. Wrapping &lt;code>order_timestamp&lt;/code> in &lt;code>DATE()&lt;/code> forces Snowflake to evaluate every row before filtering. The query planner can&amp;rsquo;t use micro-partition metadata to skip irrelevant partitions.&lt;/p>
&lt;p>This version does the same thing, but lets Snowflake prune:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Cheap: range filter on raw column enables pruning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id, customer_id, total_amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analytics.fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_timestamp &lt;span style="color:#f92672">&amp;gt;=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;2026-03-01&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> order_timestamp &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;2026-03-02&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The difference can be enormous on large tables — I&amp;rsquo;ve seen this single change reduce bytes scanned by 95% on billion-row fact tables.&lt;/p>
&lt;p>The other silent killer is &lt;code>SELECT *&lt;/code> in intermediate transformations. In a dbt project, I often find staging models that look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- stg_orders.sql (before)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;raw&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> (&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(_loaded_at) &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That &lt;code>SELECT *&lt;/code> pulls every column from the source — including columns nobody downstream ever uses. In a columnar store like Snowflake, you only pay to scan the columns you reference. Trimming to the columns you actually need is free performance:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- stg_orders.sql (after)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;raw&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> (&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(_loaded_at) &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This isn&amp;rsquo;t premature optimisation. It&amp;rsquo;s hygiene. Every &lt;code>SELECT *&lt;/code> in your transformation layer is a small tax you pay on every single run.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="full-refreshes-are-the-most-expensive-default-in-data-engineering">Full refreshes are the most expensive default in data engineering&lt;/h3>
&lt;br>
&lt;p>If you&amp;rsquo;re using dbt — and most teams on Snowflake are — the default materialisation is either &lt;code>view&lt;/code> or &lt;code>table&lt;/code>. Both have the same problem at scale: they rebuild everything, every time.&lt;/p>
&lt;p>A table materialisation on a 500-million-row fact table means Snowflake reads, transforms, and writes 500 million rows every run. Even if only 50,000 rows changed since yesterday. That&amp;rsquo;s a 10,000x overhead.&lt;/p>
&lt;p>Switching to incremental materialisation is the single highest-impact cost change most teams can make:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- fct_orders.sql
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> materialized&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;incremental&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unique_key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;order_id&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> incremental_strategy&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;merge&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> on_schema_change&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;append_new_columns&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updated_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;stg_orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> is_incremental() &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span> updated_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;hour&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>, &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> endif &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A few things worth noting here. The &lt;code>DATEADD('hour', -3, ...)&lt;/code> creates a 3-hour lookback window. This catches late-arriving data and handles clock skew between source systems. Without it, you&amp;rsquo;ll miss rows that arrive slightly out of order — and your numbers will silently drift.&lt;/p>
&lt;p>The &lt;code>unique_key&lt;/code> with &lt;code>incremental_strategy='merge'&lt;/code> means Snowflake will update existing rows and insert new ones. This is essential for tables where source records get modified after initial load (order status changes, for example).&lt;/p>
&lt;p>The rule of thumb: &lt;strong>if a table has more than a million rows and less than 20% changes per run, make it incremental.&lt;/strong> The cost savings are usually 80–95% on that model&amp;rsquo;s compute.&lt;/p>
&lt;p>But — and this is important — schedule a periodic full refresh to correct any drift. I typically set up a weekly &lt;code>dbt build --full-refresh --select fct_orders&lt;/code> via a separate Airflow DAG or GitHub Actions workflow. Belt and suspenders.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-hidden-costs-your-snowflake-dashboard-wont-show-you">The hidden costs your Snowflake dashboard won&amp;rsquo;t show you&lt;/h3>
&lt;br>
&lt;p>Most teams monitor warehouse credits because that&amp;rsquo;s what the Snowflake UI makes visible. But there are cost vectors that don&amp;rsquo;t show up in the obvious places.&lt;/p>
&lt;p>&lt;strong>Cloud services credits&lt;/strong> accrue when queries use Snowflake&amp;rsquo;s coordination layer — query compilation, metadata operations, result set caching. Normally this is covered by a 10% &amp;ldquo;free&amp;rdquo; adjustment against your compute credits. But if you have BI tools making thousands of small metadata queries, cloud services can exceed that adjustment and start costing real money.&lt;/p>
&lt;p>Find them:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, usage_date) &lt;span style="color:#66d9ef">AS&lt;/span> &lt;span style="color:#66d9ef">day&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_adjustment_cloud_services) &lt;span style="color:#66d9ef">AS&lt;/span> cloud_services_adjustment,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_adjustment_cloud_services) &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#66d9ef">ABS&lt;/span>(&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_adjustment_cloud_services))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> excess_cloud_services_cost
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.metering_daily_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> usage_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">day&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">day&lt;/span> &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Serverless feature credits&lt;/strong> — Snowpipe, automatic clustering, materialized view maintenance, search optimisation — all consume credits outside your warehouse billing. They don&amp;rsquo;t show up in &lt;code>warehouse_metering_history&lt;/code>. You need to check separately:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Snowpipe costs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pipe_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.pipe_usage_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pipe_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Automatic clustering costs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">table_name&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.automatic_clustering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">table_name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On the AWS side, the sneaky costs tend to be data transfer. Moving data between S3 regions, or from S3 out to the internet, adds up quietly. If your Snowflake account is in &lt;code>us-east-1&lt;/code> but your S3 landing zone is in &lt;code>ap-southeast-2&lt;/code>, every byte of ingestion carries a cross-region transfer charge. Check your AWS Cost Explorer with the &amp;ldquo;Data Transfer&amp;rdquo; service filter — the numbers are often surprising.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="tag-everything-or-youll-optimise-blind">Tag everything or you&amp;rsquo;ll optimise blind&lt;/h3>
&lt;br>
&lt;p>You cannot reduce costs you can&amp;rsquo;t attribute. The simplest and most underused tool in Snowflake is the query tag — a piece of metadata you attach to every query that tells you &lt;em>what&lt;/em> generated it.&lt;/p>
&lt;p>If you&amp;rsquo;re using dbt, this takes about five minutes to set up. Create a macro:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- macros/set_query_tag.sql
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> macro set_query_tag() &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> query_tag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_model&amp;#34;&lt;/span>: model.name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_schema&amp;#34;&lt;/span>: model.&lt;span style="color:#66d9ef">schema&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_materialized&amp;#34;&lt;/span>: model.config.materialized,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_invocation_id&amp;#34;&lt;/span>: invocation_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;environment&amp;#34;&lt;/span>: target.name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">}&lt;/span> &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">do&lt;/span> run_query(&lt;span style="color:#e6db74">&amp;#34;ALTER SESSION SET QUERY_TAG = &amp;#39;{}&amp;#39;&amp;#34;&lt;/span>.format(query_tag &lt;span style="color:#f92672">|&lt;/span> tojson)) &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> endmacro &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then add it as a pre-hook in your &lt;code>dbt_project.yml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># dbt_project.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">your_project&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">+pre-hook&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;{{ set_query_tag() }}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now every query dbt runs is tagged with the model name, materialisation type, and environment. You can query cost by model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARSE_JSON(query_tag):dbt_model::STRING &lt;span style="color:#66d9ef">AS&lt;/span> model_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARSE_JSON(query_tag):dbt_materialized::STRING &lt;span style="color:#66d9ef">AS&lt;/span> materialization,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> query_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(total_elapsed_time) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> total_seconds,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(bytes_scanned) &lt;span style="color:#f92672">/&lt;/span> POWER(&lt;span style="color:#ae81ff">1024&lt;/span>, &lt;span style="color:#ae81ff">4&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> tb_scanned
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.query_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> query_tag &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> TRY_PARSE_JSON(query_tag) &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model_name, materialization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tb_scanned &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LIMIT&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is how you find the one dbt model that&amp;rsquo;s responsible for 40% of your bill. Without tags, it&amp;rsquo;s a guessing game.&lt;/p>
&lt;p>For non-dbt workloads — Airflow tasks, Lambda functions, BI tools — set query tags at the session level in your connection configuration. In Python:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> snowflake.connector
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>conn &lt;span style="color:#f92672">=&lt;/span> snowflake&lt;span style="color:#f92672">.&lt;/span>connector&lt;span style="color:#f92672">.&lt;/span>connect(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> account&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your_account&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your_user&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> password&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your_password&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;transform_wh&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> session_parameters&lt;span style="color:#f92672">=&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;QUERY_TAG&amp;#39;&lt;/span>: json&lt;span style="color:#f92672">.&lt;/span>dumps({
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;pipeline&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;customer_ingestion&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;task&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;load_raw_customers&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;environment&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;production&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The goal is simple: &lt;strong>every query that runs on your platform should be attributable to a team, a pipeline, or a tool.&lt;/strong> Start with the big consumers and work outward.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="kill-the-zombies">Kill the zombies&lt;/h3>
&lt;br>
&lt;p>Every data platform accumulates dead weight. Tables nobody queries. Pipelines that run faithfully every morning, transforming data that no dashboard, no analyst, and no model has touched in months.&lt;/p>
&lt;p>These zombies cost you twice: once in compute (the pipeline that refreshes them) and once in storage (the data that sits there). Finding them is straightforward:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Tables with zero reads in the last 90 days
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.table_schema,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#66d9ef">table_name&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#66d9ef">row_count&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.bytes &lt;span style="color:#f92672">/&lt;/span> POWER(&lt;span style="color:#ae81ff">1024&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> size_gb,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.last_altered,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(ah.query_start_time) &lt;span style="color:#66d9ef">AS&lt;/span> last_queried
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.tables t
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> snowflake.account_usage.access_history ah
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> ah.base_objects_accessed &lt;span style="color:#66d9ef">LIKE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> t.&lt;span style="color:#66d9ef">table_name&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> ah.query_start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">90&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.table_schema &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;INFORMATION_SCHEMA&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> t.deleted &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> t.&lt;span style="color:#66d9ef">row_count&lt;/span> &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.table_schema, t.&lt;span style="color:#66d9ef">table_name&lt;/span>, t.&lt;span style="color:#66d9ef">row_count&lt;/span>, t.bytes, t.last_altered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">HAVING&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> last_queried &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_gb &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On the AWS side, check your S3 storage for orphaned data. Landing zones accumulate raw files that were loaded months ago and never cleaned up. A simple lifecycle policy handles this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Rules&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ID&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Archive raw data after 90 days&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Status&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Enabled&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Filter&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Prefix&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;raw-landing/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Transitions&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Days&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">90&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;StorageClass&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;GLACIER_INSTANT_RETRIEVAL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Expiration&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Days&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">365&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The cultural shift matters as much as the tooling. Make it a quarterly habit: pull the zombie table list, review it with the team, and deprecate what nobody uses. If someone screams, you can always restore from Time Travel. But in my experience, nobody screams. The data was already dead — you&amp;rsquo;re just acknowledging it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="cost-anomalies-will-find-you--or-you-can-find-them-first">Cost anomalies will find you — or you can find them first&lt;/h3>
&lt;br>
&lt;p>The scariest cost event isn&amp;rsquo;t the gradual creep. It&amp;rsquo;s the single bad query or misconfigured pipeline that doubles your weekly bill overnight. I&amp;rsquo;ve seen a single Snowflake query with a missing WHERE clause scan an entire 2TB table repeatedly inside a loop — burning through hundreds of credits in an hour.&lt;/p>
&lt;p>On Snowflake, set up resource monitors as a basic guardrail:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a resource monitor with alerts and hard stop
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> RESOURCE MONITOR monthly_budget
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WITH&lt;/span> CREDIT_QUOTA &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5000&lt;/span> &lt;span style="color:#75715e">-- adjust to your monthly budget
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FREQUENCY &lt;span style="color:#f92672">=&lt;/span> MONTHLY
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> START_TIMESTAMP &lt;span style="color:#f92672">=&lt;/span> IMMEDIATELY
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TRIGGERS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#ae81ff">75&lt;/span> PERCENT &lt;span style="color:#66d9ef">DO&lt;/span> &lt;span style="color:#66d9ef">NOTIFY&lt;/span> &lt;span style="color:#75715e">-- email alert at 75%
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#ae81ff">90&lt;/span> PERCENT &lt;span style="color:#66d9ef">DO&lt;/span> &lt;span style="color:#66d9ef">NOTIFY&lt;/span> &lt;span style="color:#75715e">-- email alert at 90%
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span> PERCENT &lt;span style="color:#66d9ef">DO&lt;/span> SUSPEND; &lt;span style="color:#75715e">-- hard stop at 100%
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Apply it to a warehouse
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE transform_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> RESOURCE_MONITOR &lt;span style="color:#f92672">=&lt;/span> monthly_budget;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On AWS, enable Cost Anomaly Detection in the AWS Cost Management console. It uses ML to detect unusual spending patterns and sends alerts via SNS. For Snowflake-specific monitoring, a lightweight approach is a scheduled task that checks daily credit consumption against a rolling average:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a simple anomaly detection view
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">VIEW&lt;/span> monitoring.daily_cost_anomalies &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> daily_usage &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, start_time) &lt;span style="color:#66d9ef">AS&lt;/span> usage_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> daily_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.warehouse_metering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> usage_date, warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>averages &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AVG&lt;/span>(daily_credits) &lt;span style="color:#66d9ef">AS&lt;/span> avg_daily_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> STDDEV(daily_credits) &lt;span style="color:#66d9ef">AS&lt;/span> stddev_daily_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_usage
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> usage_date &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.usage_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.daily_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a.avg_daily_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND((du.daily_credits &lt;span style="color:#f92672">-&lt;/span> a.avg_daily_credits) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(a.stddev_daily_credits, &lt;span style="color:#ae81ff">0&lt;/span>), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> z_score
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_usage du
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">JOIN&lt;/span> averages a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> du.warehouse_name &lt;span style="color:#f92672">=&lt;/span> a.warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.usage_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> (du.daily_credits &lt;span style="color:#f92672">-&lt;/span> a.avg_daily_credits) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(a.stddev_daily_credits, &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> z_score &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A z-score above 2 means today&amp;rsquo;s spend is more than two standard deviations above the 60-day average. That&amp;rsquo;s worth investigating. Pipe this into a Teams or Slack alert via an AWS Lambda function and you&amp;rsquo;ve got same-day cost anomaly detection for the price of a few lines of SQL.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-your-data-model-costs-you">What your data model costs you&lt;/h3>
&lt;br>
&lt;p>This one&amp;rsquo;s subtle but powerful. The way you model your data directly affects how much compute you burn on every query.&lt;/p>
&lt;p>In columnar warehouses like Snowflake, wide denormalised tables query faster than star schemas with multiple JOINs — because each JOIN has overhead, and Snowflake is optimised for scanning columns from flat structures. But wide tables cost more to &lt;em>maintain&lt;/em> because updating a single dimension attribute means rewriting many rows in the fact table.&lt;/p>
&lt;p>The practical approach is a layered architecture:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Staging models&lt;/strong> (views or ephemeral): Minimal transformation, column selection, type casting. Cheap to run, no storage cost.&lt;/li>
&lt;li>&lt;strong>Intermediate models&lt;/strong> (tables or incremental): Business logic, deduplication, SCD handling. Materialised because they&amp;rsquo;re referenced by multiple downstream models.&lt;/li>
&lt;li>&lt;strong>Mart models&lt;/strong> (incremental or table): Wide, denormalised, optimised for consumer queries. These are what analysts and BI tools actually hit.&lt;/li>
&lt;/ul>
&lt;p>The cost trap is materialising too much too early. Every table materialisation means Snowflake stores and maintains that data. Every &lt;code>dbt run&lt;/code> rebuilds it. If an intermediate model is only referenced by one downstream model, make it ephemeral or a view — let Snowflake inline it at query time.&lt;/p>
&lt;p>Check your dbt DAG for this pattern: a staging model materialised as a table, referenced by a single intermediate model, which is also materialised as a table, referenced by a single mart. That&amp;rsquo;s three materialisations where one (the mart) would suffice. The staging and intermediate models can be views or ephemeral — the compute happens once when the mart builds, not three separate times.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="coming-back-to-why-this-matters">Coming back to why this matters&lt;/h3>
&lt;br>
&lt;p>I started this article with a story about not being able to explain my own platform&amp;rsquo;s costs. That wasn&amp;rsquo;t a technical failure. It was a leadership failure. I&amp;rsquo;d built something valuable and then neglected the part that kept it funded.&lt;/p>
&lt;p>The data engineers I respect most aren&amp;rsquo;t the ones who build the most elegant pipelines. They&amp;rsquo;re the ones who can walk into a budget conversation and say: &amp;ldquo;Here&amp;rsquo;s what we spend. Here&amp;rsquo;s what we get for it. Here&amp;rsquo;s what I&amp;rsquo;d cut, and here&amp;rsquo;s what I&amp;rsquo;d invest more in.&amp;rdquo; That&amp;rsquo;s the kind of clarity that earns trust — and trust is what keeps data teams alive when the inevitable cost-cutting conversations happen.&lt;/p>
&lt;p>You don&amp;rsquo;t need a FinOps certification or an expensive monitoring tool to get there. You need the queries in this article, a quarterly habit of reviewing them, and the willingness to kill the zombies nobody wants to admit are dead.&lt;/p>
&lt;p>Start with the 15-minute audit. Tag your queries. Set up a resource monitor. Do those three things this week and you&amp;rsquo;ll know more about your platform&amp;rsquo;s economics than most data teams learn in a year.&lt;/p>
&lt;p>The cheapest query is the one you never need to run. But the most valuable skill is knowing which queries are worth paying for.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Cloud Architecture</category><category>Snowflake</category><category>AWS</category><category>Cost Optimization</category><category>FinOps</category><category>dbt</category><category>Data Platform</category></item><item><title>Why Your Pipeline Finishes Later Every Month</title><link>https://ghostinthedata.info/posts/2026/2026-04-18-pipeline-optimization/</link><pubDate>Sat, 18 Apr 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-04-18-pipeline-optimization/</guid><author>Chris Hillman</author><description>A practical guide to diagnosing pipeline bottlenecks, fixing unnecessary dependencies, and getting data to consumers faster — with Snowflake and AWS patterns you can apply today.</description><content:encoded>&lt;p>Let me tell you about a graph that changed how I think about data engineering.&lt;/p>
&lt;p>A junior engineer on my team — let&amp;rsquo;s call her Priya — had been tracking something nobody asked her to track. Every morning for two months, she&amp;rsquo;d noted the timestamp when our main analytics pipeline completed. She wasn&amp;rsquo;t trying to make a point. She was just curious, because the finance team kept mentioning their dashboards weren&amp;rsquo;t ready when they arrived at 8 AM anymore.&lt;/p>
&lt;p>One afternoon she pulled me aside and showed me a scatter plot on her laptop. Pipeline completion time, plotted daily over several months. The trend was unmistakable: a slow, steady drift to the right. What used to finish at 5:47 AM was now finishing at 7:23 AM. And the slope wasn&amp;rsquo;t flattening.&lt;/p>
&lt;p>&amp;ldquo;If this keeps going,&amp;rdquo; she said, &amp;ldquo;we&amp;rsquo;ll miss the 9 AM SLA in about six weeks.&amp;rdquo;&lt;/p>
&lt;p>She was right. And nobody else on the team — including me — had noticed. We were watching for failures. Green DAGs, clean logs, no alerts. But the pipeline wasn&amp;rsquo;t failing. It was &lt;em>slowing&lt;/em>. And slow is harder to see than broken, because slow doesn&amp;rsquo;t trigger an alert. Slow just quietly erodes trust until one day someone in finance builds their own spreadsheet and stops asking you for anything.&lt;/p>
&lt;br>
&lt;p>That&amp;rsquo;s the moment I understood that pipeline health isn&amp;rsquo;t about pass/fail. It&amp;rsquo;s about &lt;em>trajectory&lt;/em>. A pipeline that runs successfully but takes 5% longer every month is a ticking clock. And the people who notice first aren&amp;rsquo;t the engineers watching the DAG — they&amp;rsquo;re the consumers waiting for their data.&lt;/p>
&lt;p>I&amp;rsquo;m telling you this because pipeline optimisation sounds like a performance engineering problem, and it is. But underneath the technical work, it&amp;rsquo;s really about a commitment: the commitment to deliver data when you said you would, every single day. That&amp;rsquo;s what builds trust between data teams and the rest of the organisation. Not fancy architectures. Not real-time everything. Just showing up on time, reliably.&lt;/p>
&lt;p>This article is about diagnosing why your pipelines get slower, identifying the bottlenecks that actually matter, and fixing the patterns that cause data to arrive later than it should. Everything is grounded in Snowflake, AWS, Airflow, and dbt — with specific patterns you can apply immediately.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="measure-the-pipeline-in-stages-not-as-a-single-number">Measure the pipeline in stages, not as a single number&lt;/h3>
&lt;br>
&lt;p>When a pipeline is slow, the instinct is to look at the longest-running task. That&amp;rsquo;s often the wrong place to start.&lt;/p>
&lt;p>A pipeline is a chain of stages: extract, transform, load, test. The total runtime is the sum of all stages on the critical path — the longest chain of dependent tasks. But the &lt;em>bottleneck&lt;/em> might not be the longest task. It might be a 30-second task that blocks five parallel branches from starting.&lt;/p>
&lt;p>Before you optimise anything, instrument your pipeline to measure each stage independently. In Airflow, the task instance metadata already captures this.&lt;/p>
&lt;p>Two numbers matter here: &lt;code>duration_seconds&lt;/code> (how long the task actually ran) and &lt;code>queue_wait_seconds&lt;/code> (how long it waited before it could run). If queue wait is high, your problem isn&amp;rsquo;t the task — it&amp;rsquo;s resource contention. Too many tasks competing for too few Airflow worker slots, or too many queries competing for the same Snowflake warehouse.&lt;/p>
&lt;p>For dbt runs specifically, the &lt;code>run_results.json&lt;/code> that dbt generates after every invocation is a goldmine:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;target/run_results.json&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> results &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>load(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Find your slowest models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>models &lt;span style="color:#f92672">=&lt;/span> sorted(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [r &lt;span style="color:#66d9ef">for&lt;/span> r &lt;span style="color:#f92672">in&lt;/span> results[&lt;span style="color:#e6db74">&amp;#39;results&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">if&lt;/span> r[&lt;span style="color:#e6db74">&amp;#39;status&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;success&amp;#39;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">lambda&lt;/span> x: x[&lt;span style="color:#e6db74">&amp;#39;execution_time&amp;#39;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reverse&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> m &lt;span style="color:#f92672">in&lt;/span> models[:&lt;span style="color:#ae81ff">10&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>m[&lt;span style="color:#e6db74">&amp;#39;unique_id&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">60s&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>m[&lt;span style="color:#e6db74">&amp;#39;execution_time&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">8.1f&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">s&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Run that after your next &lt;code>dbt build&lt;/code>. You&amp;rsquo;ll immediately see which models dominate your pipeline runtime. In my experience, 3–5 models account for 60–80% of total execution time. Those are the only models worth optimising.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="your-dag-probably-has-dependencies-it-doesnt-need">Your DAG probably has dependencies it doesn&amp;rsquo;t need&lt;/h3>
&lt;br>
&lt;p>This is the most common structural problem in data pipelines, and it comes in two forms. One is obvious once you know to look for it. The other is sneaky, because it starts as a completely reasonable engineering decision.&lt;/p>
&lt;p>&lt;strong>The first form: phantom dependencies.&lt;/strong>&lt;/p>
&lt;p>An engineer builds Model C and declares it depends on Model B — because Model B produces a table that Model C reads. Fair enough. But six months later, someone refactors Model C and it no longer reads from Model B. The &lt;code>ref()&lt;/code> gets removed from the SQL. But the dependency in the Airflow DAG? That stays. Or in dbt, someone adds a &lt;code>ref()&lt;/code> to a model they don&amp;rsquo;t actually need, just to &amp;ldquo;make sure it runs first.&amp;rdquo;&lt;/p>
&lt;p>The result is a DAG with phantom dependencies — tasks that wait for other tasks to complete even though they don&amp;rsquo;t use those tasks&amp;rsquo; outputs. Every phantom dependency adds serial wait time to your pipeline.&lt;/p>
&lt;p>&lt;strong>The second form: dependency monsters.&lt;/strong>&lt;/p>
&lt;p>This one is trickier, because it starts with good intentions.&lt;/p>
&lt;p>The customer team needs three new enrichment attributes on &lt;code>dim_customers&lt;/code> — regional segment codes sourced from a CRM export, tenure tier derived from a subscription history table, and a propensity score from a data science model. All reasonable requests. Each one approved and added without much ceremony.&lt;/p>
&lt;p>But &lt;code>dim_customers&lt;/code> is upstream of 40+ models across your DAG. Adding those three attributes means pulling in the CRM extract, joining the subscription history table — which has its own upstream dependencies — and waiting on the propensity score model to complete before any of those 40 downstream models can start. Your pipeline used to finish at 9 AM. Eighteen months and a dozen enrichment requests later, it finishes at 3 PM.&lt;/p>
&lt;p>Each addition was individually justified. Nobody modelled what they&amp;rsquo;d cost collectively. That&amp;rsquo;s how you build a dependency monster.&lt;/p>
&lt;p>The fix isn&amp;rsquo;t to refuse enrichment requests — it&amp;rsquo;s to stop embedding enrichment into your core spine model. Keep &lt;code>dim_customers&lt;/code> lean: identifiers, names, status, the attributes that nearly every consumer genuinely needs. Build a separate &lt;code>dim_customers_extended&lt;/code> model that joins in the expensive enrichment for the consumers who actually need it. Most of your downstream models will never touch the propensity score. There&amp;rsquo;s no reason to make them wait for it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- dim_customers.sql: lean spine — runs fast, unblocks the DAG
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> email,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> created_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;stg_customers&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- dim_customers_extended.sql: enrichment for consumers who need it
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- runs independently, doesn&amp;#39;t block the main pipeline
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">c&lt;/span>.customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">c&lt;/span>.customer_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">c&lt;/span>.status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> crm.regional_segment,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sub.tenure_tier,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ps.propensity_score
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;dim_customers&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span> &lt;span style="color:#66d9ef">c&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;stg_crm_segments&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span> crm
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">on&lt;/span> crm.customer_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">c&lt;/span>.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;int_subscription_tenure&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span> sub
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">on&lt;/span> sub.customer_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">c&lt;/span>.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;ml_propensity_scores&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span> ps
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">on&lt;/span> ps.customer_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">c&lt;/span>.customer_id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now &lt;code>dim_customers_extended&lt;/code> and its expensive upstream dependencies sit on their own branch of the DAG. Consumers who need the enrichment reference the extended model. The rest of the pipeline is unblocked.&lt;/p>
&lt;p>A useful governance rule of thumb before adding a field to a core model: if fewer than half your consumers will ever query that attribute, it probably doesn&amp;rsquo;t belong there. The team who needs it should own the enrichment themselves, as a downstream model they maintain.&lt;/p>
&lt;br>
&lt;p>&lt;strong>Auditing for both problems&lt;/strong>&lt;/p>
&lt;p>In dbt, the &lt;code>dbt_project_evaluator&lt;/code> package surfaces structural issues systematically. Install it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># packages.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">packages&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">package&lt;/span>: &lt;span style="color:#ae81ff">dbt-labs/dbt_project_evaluator&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">version&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;&amp;gt;=0.8.0 &amp;lt;1.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dbt build --select package:dbt_project_evaluator
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It will flag models with fan-out issues (one model referenced by too many downstream models), unnecessary dependencies, and models that could be parallelised but aren&amp;rsquo;t. It won&amp;rsquo;t catch dependency monsters directly — those require a human asking &amp;ldquo;does every consumer of this model actually use every attribute in it?&amp;rdquo; — but it&amp;rsquo;s a good starting point for the structural audit.&lt;/p>
&lt;p>For Airflow DAGs, the visual DAG view tells you a lot at a glance. A healthy DAG looks like a tree with wide parallel branches. An unhealthy DAG looks like a chain — or worse, a funnel where everything converges through a single overloaded task before it can branch out again. That funnel shape is the visual signature of a dependency monster: many expensive upstreams flowing into one hub model, with dozens of downstream models queued behind it.&lt;/p>
&lt;p>If your DAG looks like a chain, ask this question for every dependency edge: &lt;strong>does downstream task B actually read data produced by upstream task A?&lt;/strong> If the answer is no, remove the dependency. Let them run in parallel.&lt;/p>
&lt;p>I once audited a DAG with 47 tasks running in a strict serial chain. After removing phantom dependencies and restructuring, 31 of those tasks could run in parallel. The pipeline went from 2 hours 15 minutes to 38 minutes. Same tasks, same compute, same data. Just fewer unnecessary wait states.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-critical-path-is-the-only-thing-worth-optimising">The critical path is the only thing worth optimising&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s a concept from project management that applies directly to pipeline engineering: the &lt;strong>critical path&lt;/strong>.&lt;/p>
&lt;p>The critical path is the longest chain of dependent tasks through your DAG. It determines your pipeline&amp;rsquo;s minimum possible runtime. Every other path through the DAG has &amp;ldquo;float&amp;rdquo; — slack time where tasks can be delayed without affecting the overall completion time.&lt;/p>
&lt;p>This means: &lt;strong>optimising a task that isn&amp;rsquo;t on the critical path has zero impact on your pipeline&amp;rsquo;s end-to-end runtime.&lt;/strong> You could make a non-critical task 10x faster and your pipeline would finish at exactly the same time.&lt;/p>
&lt;p>Finding the critical path requires knowing each task&amp;rsquo;s duration and dependency structure. For a dbt project, you can approximate it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Using dbt&amp;#39;s run results stored in Snowflake (if you log them)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Or query your orchestrator&amp;#39;s task instance table
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">WITH&lt;/span> task_durations &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> task_id &lt;span style="color:#66d9ef">AS&lt;/span> model_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AVG&lt;/span>(&lt;span style="color:#66d9ef">EXTRACT&lt;/span>(EPOCH &lt;span style="color:#66d9ef">FROM&lt;/span> (end_date &lt;span style="color:#f92672">-&lt;/span> start_date))) &lt;span style="color:#66d9ef">AS&lt;/span> avg_duration
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> task_instance
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> dag_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;dbt_daily&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> &lt;span style="color:#66d9ef">state&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;success&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> execution_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span> &lt;span style="color:#f92672">-&lt;/span> INTERVAL &lt;span style="color:#e6db74">&amp;#39;7 days&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> task_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(avg_duration, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> avg_seconds,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(avg_duration &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> avg_minutes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> task_durations
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> avg_duration &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LIMIT&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The longest tasks are &lt;em>candidates&lt;/em> for the critical path, but only if they&amp;rsquo;re on the dependency chain that determines total runtime. A 20-minute model that runs in parallel with a 45-minute model isn&amp;rsquo;t the bottleneck — the 45-minute model is.&lt;/p>
&lt;p>Once you&amp;rsquo;ve identified the critical path, you have three options for shortening it: make the slow tasks faster (query optimisation, incremental models), reduce the number of tasks on the path (remove unnecessary dependencies), or parallelise sequential tasks (split a monolithic model into independent pieces).&lt;/p>
&lt;hr>
&lt;h3 id="shifting-right-diagnose-it-before-it-breaks-your-sla">Shifting right: diagnose it before it breaks your SLA&lt;/h3>
&lt;br>
&lt;p>Remember Priya&amp;rsquo;s scatter plot? That pattern — pipeline completion drifting later and later — has a name: &lt;strong>shifting right&lt;/strong>. And it has predictable causes.&lt;/p>
&lt;p>Track it with a simple query against your orchestrator&amp;rsquo;s metadata:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Airflow: track pipeline completion time drift
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> execution_date::DATE &lt;span style="color:#66d9ef">AS&lt;/span> run_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(end_date)::TIME &lt;span style="color:#66d9ef">AS&lt;/span> completion_time,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EXTRACT&lt;/span>(EPOCH &lt;span style="color:#66d9ef">FROM&lt;/span> (&lt;span style="color:#66d9ef">MAX&lt;/span>(end_date) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">MIN&lt;/span>(start_date))) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> total_runtime_minutes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> task_instance
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dag_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;daily_analytics&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> &lt;span style="color:#66d9ef">state&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;success&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> execution_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span> &lt;span style="color:#f92672">-&lt;/span> INTERVAL &lt;span style="color:#e6db74">&amp;#39;60 days&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run_date;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Plot &lt;code>completion_time&lt;/code> over &lt;code>run_date&lt;/code>. If it trends upward, you&amp;rsquo;re shifting right. The four root causes, in order of how often I see them:&lt;/p>
&lt;p>&lt;strong>1. Data volume growth.&lt;/strong> Your table had 10 million rows when you built the model. Now it has 200 million. That JOIN that took 8 seconds now takes 3 minutes. This is the most common cause and the easiest to fix — switch to incremental materialisation and the growth stops mattering.&lt;/p>
&lt;p>&lt;strong>2. Resource contention.&lt;/strong> You&amp;rsquo;ve added more pipelines and they all run in the same window, competing for the same Snowflake warehouse. In Snowflake, you can see this directly:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Find warehouse queueing (queries waiting for compute)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;hour&amp;#39;&lt;/span>, start_time) &lt;span style="color:#66d9ef">AS&lt;/span> hour,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> total_queries,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> queued_overload_time &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> queued_queries,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AVG&lt;/span>(queued_overload_time) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> avg_queue_seconds
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.query_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> warehouse_name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;TRANSFORM_WH&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name, hour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">HAVING&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queued_queries &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hour;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>queued_queries&lt;/code> is non-zero during your pipeline window, queries are waiting for warehouse compute. Either scale up the warehouse during that window, split workloads across dedicated warehouses, or stagger pipeline start times to reduce concurrency.&lt;/p>
&lt;p>&lt;strong>3. Dependency chain lengthening.&lt;/strong> Someone added a new intermediate model between your staging and mart layers. That model takes 4 minutes. But because it&amp;rsquo;s on the critical path, the entire pipeline now finishes 4 minutes later. This is death by a thousand cuts — each addition is small, but they accumulate.&lt;/p>
&lt;p>&lt;strong>4. Upstream source delays.&lt;/strong> Your pipeline starts at 4 AM because the source system&amp;rsquo;s extract used to land in S3 by 3:45 AM. But the source system has grown too, and now the extract doesn&amp;rsquo;t land until 4:30 AM. Your pipeline sensors wait, and everything shifts right.&lt;/p>
&lt;p>For upstream delays in an AWS environment, replace time-based scheduling with event-driven triggers. Instead of scheduling your Airflow DAG at 4 AM and hoping the data is there, trigger it when the data actually arrives:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Airflow DAG: trigger on S3 file landing using AWS sensor&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> airflow.providers.amazon.aws.sensors.s3 &lt;span style="color:#f92672">import&lt;/span> S3KeySensor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> airflow &lt;span style="color:#f92672">import&lt;/span> DAG
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> datetime &lt;span style="color:#f92672">import&lt;/span> datetime
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> DAG(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;daily_analytics&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_date&lt;span style="color:#f92672">=&lt;/span>datetime(&lt;span style="color:#ae81ff">2026&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> schedule_interval&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">None&lt;/span>, &lt;span style="color:#75715e"># triggered externally, not on a schedule&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> catchup&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">as&lt;/span> dag:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wait_for_source &lt;span style="color:#f92672">=&lt;/span> S3KeySensor(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> task_id&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;wait_for_orders_extract&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bucket_name&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your-data-lake&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bucket_key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;raw/orders/dt={{ ds }}/orders.parquet&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aws_conn_id&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;aws_default&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;reschedule&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># frees the worker slot while waiting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> poke_interval&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">300&lt;/span>, &lt;span style="color:#75715e"># check every 5 minutes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> timeout&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">7200&lt;/span>, &lt;span style="color:#75715e"># fail after 2 hours if file never arrives&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>mode='reschedule'&lt;/code> is critical. Without it, the sensor occupies a worker slot for the entire time it&amp;rsquo;s waiting. With &lt;code>reschedule&lt;/code>, it checks, releases the slot, and checks again later. This prevents the classic deadlock where all your worker slots are consumed by sensors and no actual work can run.&lt;/p>
&lt;p>Even better: use S3 event notifications to trigger a Lambda function that kicks off the DAG via Airflow&amp;rsquo;s REST API. Zero polling, zero wasted slots:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Lambda function triggered by S3 PutObject event&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> boto3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> requests
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">handler&lt;/span>(event, context):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bucket &lt;span style="color:#f92672">=&lt;/span> event[&lt;span style="color:#e6db74">&amp;#39;Records&amp;#39;&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;s3&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;bucket&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;name&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key &lt;span style="color:#f92672">=&lt;/span> event[&lt;span style="color:#e6db74">&amp;#39;Records&amp;#39;&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;s3&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;object&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;key&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Trigger Airflow DAG via REST API&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> airflow_url &lt;span style="color:#f92672">=&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>environ[&lt;span style="color:#e6db74">&amp;#39;AIRFLOW_API_URL&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>post(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>airflow_url&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/api/v1/dags/daily_analytics/dagRuns&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> json&lt;span style="color:#f92672">=&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;conf&amp;#34;&lt;/span>: {&lt;span style="color:#e6db74">&amp;#34;source_bucket&amp;#34;&lt;/span>: bucket, &lt;span style="color:#e6db74">&amp;#34;source_key&amp;#34;&lt;/span>: key}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> auth&lt;span style="color:#f92672">=&lt;/span>(os&lt;span style="color:#f92672">.&lt;/span>environ[&lt;span style="color:#e6db74">&amp;#39;AIRFLOW_USER&amp;#39;&lt;/span>], os&lt;span style="color:#f92672">.&lt;/span>environ[&lt;span style="color:#e6db74">&amp;#39;AIRFLOW_PASSWORD&amp;#39;&lt;/span>]),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> headers&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#e6db74">&amp;#34;Content-Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;application/json&amp;#34;&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> {&lt;span style="color:#e6db74">&amp;#34;statusCode&amp;#34;&lt;/span>: response&lt;span style="color:#f92672">.&lt;/span>status_code}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This eliminates the &amp;ldquo;safety margin&amp;rdquo; scheduling pattern entirely. Your pipeline runs as soon as the data is available — not 30 minutes after you &lt;em>hope&lt;/em> it will be available.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="partition-and-cluster-to-eliminate-full-table-scans">Partition and cluster to eliminate full table scans&lt;/h3>
&lt;br>
&lt;p>The single most effective performance optimisation in Snowflake is making sure your queries only read the data they need. Snowflake&amp;rsquo;s micro-partition pruning does this automatically — but only if your data is physically organised in a way that aligns with your query patterns.&lt;/p>
&lt;p>Clustering keys tell Snowflake how to organise data within micro-partitions. If you consistently filter on &lt;code>order_date&lt;/code>, clustering on that column means queries with a &lt;code>WHERE order_date = '2026-03-01'&lt;/code> clause scan a tiny fraction of the table instead of the whole thing.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Add clustering to a large fact table
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> analytics.fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CLUSTER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> (order_date, customer_segment);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Choose clustering keys based on how the table is actually queried, not how it&amp;rsquo;s loaded. The best candidates are columns that appear in WHERE clauses, JOIN conditions, and range filters. Limit yourself to 2–3 keys — more than that and Snowflake can&amp;rsquo;t maintain effective clustering.&lt;/p>
&lt;p>Check whether your existing tables benefit from clustering:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Check clustering depth (lower is better, 0 is perfect)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">table_name&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> clustering_key,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_constant_partition_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_partition_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> average_overlaps,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> average_depth
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.table_storage_metrics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> table_catalog &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;YOUR_DATABASE&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> clustering_key &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> active_bytes &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> average_depth &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>An &lt;code>average_depth&lt;/code> above 5 means your clustering isn&amp;rsquo;t effective — queries are still scanning more partitions than they should. Either the clustering key doesn&amp;rsquo;t match query patterns, or the table has had so many small writes that the clustering has degraded. Running &lt;code>ALTER TABLE ... RECLUSTER&lt;/code> manually or relying on automatic clustering (which costs credits) addresses the latter.&lt;/p>
&lt;p>On the AWS side, if you&amp;rsquo;re using S3 as a data lake with Parquet or Iceberg, the equivalent is &lt;strong>partition layout&lt;/strong>. Partition your S3 data by the columns you filter most:&lt;/p>



&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 312 121"
 >
 &lt;g transform='translate(8,16)'>
&lt;polygon points='176.000000,80.000000 164.000000,74.400002 164.000000,85.599998' fill='currentColor' transform='rotate(90.000000, 168.000000, 80.000000)'>&lt;/polygon>
&lt;text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>=&lt;/text>
&lt;text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>6&lt;/text>
&lt;text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>=&lt;/text>
&lt;text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>=&lt;/text>
&lt;text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='208' y='84' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='208' y='100' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='216' y='84' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='224' y='84' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='232' y='84' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='240' y='84' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='248' y='84' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div>
&lt;p>When Snowflake external tables or AWS Athena query this structure with a date filter, they skip entire directories. A query for March 15th reads two files instead of scanning the entire &lt;code>events/&lt;/code> prefix. The savings compound with data volume — at a billion rows, proper partitioning can reduce query times from minutes to seconds.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="make-every-task-idempotent-or-debugging-becomes-a-nightmare">Make every task idempotent or debugging becomes a nightmare&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s a pattern I see constantly: a pipeline fails midway through, the engineer reruns it, and now there are duplicate rows in the target table. Because the first run inserted half the data before failing, and the rerun inserted &lt;em>all&lt;/em> the data — including the half that already existed.&lt;/p>
&lt;p>Idempotency means a task produces the same result whether it runs once or ten times. This isn&amp;rsquo;t just a nice-to-have — it&amp;rsquo;s the foundation that makes everything else in pipeline engineering possible. Without it, you can&amp;rsquo;t safely retry. You can&amp;rsquo;t backfill. You can&amp;rsquo;t debug.&lt;/p>
&lt;p>In dbt, incremental models with a &lt;code>unique_key&lt;/code> are idempotent by default — the MERGE statement handles duplicates:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> materialized&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;incremental&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unique_key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;event_id&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> incremental_strategy&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;merge&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> event_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> event_type,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> event_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;stg_events&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> is_incremental() &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> event_timestamp &lt;span style="color:#f92672">&amp;gt;=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>, &lt;span style="color:#66d9ef">MAX&lt;/span>(event_timestamp))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> endif &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For non-dbt loads — say, a Lambda function loading data from an API into Snowflake — use MERGE instead of INSERT:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>MERGE &lt;span style="color:#66d9ef">INTO&lt;/span> raw.api_customers &lt;span style="color:#66d9ef">AS&lt;/span> target
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">USING&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>:id::STRING &lt;span style="color:#66d9ef">AS&lt;/span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>:name::STRING &lt;span style="color:#66d9ef">AS&lt;/span> customer_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>:email::STRING &lt;span style="color:#66d9ef">AS&lt;/span> email,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>:updated_at::TIMESTAMP_NTZ &lt;span style="color:#66d9ef">AS&lt;/span> updated_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#f92672">@&lt;/span>raw.s3_stage&lt;span style="color:#f92672">/&lt;/span>customers&lt;span style="color:#f92672">/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (FILE_FORMAT &lt;span style="color:#f92672">=&amp;gt;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;json_format&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">AS&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ON&lt;/span> target.customer_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> MATCHED &lt;span style="color:#66d9ef">AND&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>.updated_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> target.updated_at &lt;span style="color:#66d9ef">THEN&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UPDATE&lt;/span> &lt;span style="color:#66d9ef">SET&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>.customer_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> email &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>.email,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updated_at &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>.updated_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> MATCHED &lt;span style="color:#66d9ef">THEN&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">INSERT&lt;/span> (customer_id, customer_name, email, updated_at)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#66d9ef">source&lt;/span>.customer_id, &lt;span style="color:#66d9ef">source&lt;/span>.customer_name, &lt;span style="color:#66d9ef">source&lt;/span>.email, &lt;span style="color:#66d9ef">source&lt;/span>.updated_at);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>WHEN MATCHED AND source.updated_at &amp;gt; target.updated_at&lt;/code> condition prevents overwriting newer data with older data during reruns. This matters when you&amp;rsquo;re replaying historical loads or running overlapping backfills.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="most-we-need-real-time-requests-actually-need-we-need-faster-batch">Most &amp;ldquo;we need real-time&amp;rdquo; requests actually need &amp;ldquo;we need faster batch&amp;rdquo;&lt;/h3>
&lt;br>
&lt;p>Before you reach for Kafka, Kinesis, or any streaming infrastructure, have this conversation with your stakeholders: &amp;ldquo;When you say real-time, what do you actually mean?&amp;rdquo;&lt;/p>
&lt;p>In my experience, the answers fall into three categories:&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;I want data from today, not yesterday.&amp;rdquo;&lt;/strong> That&amp;rsquo;s daily batch with a morning refresh. You already have this. Maybe you need to move the refresh earlier.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;I want data that&amp;rsquo;s at most an hour old.&amp;rdquo;&lt;/strong> That&amp;rsquo;s micro-batch — running your existing pipeline every 15–60 minutes instead of once a day. No new infrastructure required. Your existing SQL, dbt, and Airflow tools work unchanged at shorter intervals.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;I need to see events within seconds of them happening.&amp;rdquo;&lt;/strong> &lt;em>This&lt;/em> is actual real-time. And it&amp;rsquo;s genuinely rare. Fraud detection, safety monitoring, real-time bidding — these need streaming. Your internal sales dashboard almost certainly does not.&lt;/p>
&lt;p>For the micro-batch pattern in Airflow, it&amp;rsquo;s a scheduling change:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> DAG(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;micro_batch_analytics&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> schedule_interval&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;*/15 * * * *&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># every 15 minutes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> catchup&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_active_runs&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#75715e"># prevent overlap&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">as&lt;/span> dag:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># ...tasks here&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>max_active_runs=1&lt;/code> is important. Without it, if a run takes longer than 15 minutes, the next run starts before the previous one finishes. They compete for the same warehouse, both run slower, and you&amp;rsquo;ve created the resource contention problem from the shifting-right section.&lt;/p>
&lt;p>In Snowflake, pair this with Snowpipe for continuous S3 ingestion:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a pipe for continuous loading from S3
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> PIPE raw.orders_pipe
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_INGEST &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COPY&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> raw.orders_stream
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#f92672">@&lt;/span>raw.s3_orders_stage
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FILE_FORMAT &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">TYPE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PARQUET&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MATCH_BY_COLUMN_NAME &lt;span style="color:#f92672">=&lt;/span> CASE_INSENSITIVE;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Snowpipe loads files within minutes of them landing in S3. Your micro-batch dbt pipeline then transforms whatever arrived since the last run. The combination delivers data freshness measured in minutes — not seconds, but close enough for the vast majority of business use cases — without any streaming infrastructure.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="measure-time-to-consumer-not-just-pipeline-runtime">Measure time-to-consumer, not just pipeline runtime&lt;/h3>
&lt;br>
&lt;p>Pipeline runtime tells you how long your DAG takes. Time-to-consumer tells you how long a business event takes to reach a human decision-maker. These are different numbers, and the second one is what your stakeholders actually care about.&lt;/p>
&lt;p>Time-to-consumer decomposes into stages:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Source extraction delay&lt;/strong>: Time between the event occurring and the data landing in your lake&lt;/li>
&lt;li>&lt;strong>Pipeline processing time&lt;/strong>: Your DAG runtime (the part you control most directly)&lt;/li>
&lt;li>&lt;strong>Warehouse serving time&lt;/strong>: Query execution time when a dashboard or report reads the data&lt;/li>
&lt;li>&lt;strong>Cache/refresh delay&lt;/strong>: How often the BI tool refreshes its cache&lt;/li>
&lt;/ul>
&lt;p>Track it by stamping timestamps at each handoff:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Build a freshness tracking model in dbt
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- mart_data_freshness.sql
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;fct_orders&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> model_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(order_timestamp) &lt;span style="color:#66d9ef">AS&lt;/span> latest_source_event,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(_loaded_at) &lt;span style="color:#66d9ef">AS&lt;/span> latest_load_time,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>() &lt;span style="color:#66d9ef">AS&lt;/span> measured_at,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TIMESTAMPDIFF(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;minute&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(order_timestamp),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> minutes_since_latest_event,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TIMESTAMPDIFF(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;minute&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(_loaded_at),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> minutes_since_latest_load
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;fct_orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Schedule this model to run after every pipeline completion. Over time, you&amp;rsquo;ll see whether &lt;code>minutes_since_latest_event&lt;/code> is stable, improving, or drifting. If it&amp;rsquo;s drifting, you can pinpoint which stage is responsible by comparing the event timestamp, load timestamp, and measurement timestamp.&lt;/p>
&lt;p>In dbt, you can also use source freshness tests to alert when upstream data is stale:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># models/staging/_sources.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">sources&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">raw&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">database&lt;/span>: &lt;span style="color:#ae81ff">raw_db&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">schema&lt;/span>: &lt;span style="color:#ae81ff">public&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">loaded_at_field&lt;/span>: &lt;span style="color:#ae81ff">_loaded_at&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">freshness&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">warn_after&lt;/span>: { &lt;span style="color:#f92672">count: 2, period&lt;/span>: &lt;span style="color:#ae81ff">hour }&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">error_after&lt;/span>: { &lt;span style="color:#f92672">count: 6, period&lt;/span>: &lt;span style="color:#ae81ff">hour }&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tables&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">orders&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">events&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Running &lt;code>dbt source freshness&lt;/code> checks whether the upstream data is as fresh as you expect. If &lt;code>raw.orders&lt;/code> hasn&amp;rsquo;t received new rows in 6 hours, the test fails — and that&amp;rsquo;s a signal that the source system&amp;rsquo;s extract is delayed, not that your pipeline is broken.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-view-on-view-problem-the-compound-interest-of-bad-performance">The view-on-view problem: the compound interest of bad performance&lt;/h3>
&lt;br>
&lt;p>This is the architectural anti-pattern I see most often in Snowflake environments, and it&amp;rsquo;s one of those problems that starts small and compounds quietly.&lt;/p>
&lt;p>A view in Snowflake doesn&amp;rsquo;t store any data — it re-executes its SQL every time it&amp;rsquo;s queried. That&amp;rsquo;s fine for a simple view. But when View C references View B, which references View A, which scans a large table — every query against View C triggers the entire chain from scratch.&lt;/p>
&lt;p>I&amp;rsquo;ve seen environments where analysts querying a &amp;ldquo;simple&amp;rdquo; dashboard view were unknowingly triggering five layers of nested views, each with its own JOINs and aggregations. The query took 4 minutes. After materialising the two most expensive intermediate layers as tables (refreshed daily), the same query took 3 seconds.&lt;/p>
&lt;p>The fix is to audit your materialisation strategy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Find views that reference other views (potential nesting)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> referencing_object_name &lt;span style="color:#66d9ef">AS&lt;/span> downstream_view,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> referenced_object_name &lt;span style="color:#66d9ef">AS&lt;/span> upstream_object,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> referenced_object_type
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.object_dependencies
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> referencing_object_type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;VIEW&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> referenced_object_type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;VIEW&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> downstream_view;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If this query returns results, you have view-on-view nesting. Not all of it is bad — a simple renaming view on top of a complex view is fine. But if you find three or more layers, or if any of the intermediate views involve heavy JOINs or aggregations, materialise the expensive layers as tables.&lt;/p>
&lt;p>In dbt terms, the decision framework is:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ephemeral&lt;/strong>: Simple CTEs, column renaming, type casting. Zero cost.&lt;/li>
&lt;li>&lt;strong>View&lt;/strong>: Light transformations queried infrequently by a few consumers.&lt;/li>
&lt;li>&lt;strong>Table&lt;/strong>: Complex transformations queried frequently. Rebuilt every run.&lt;/li>
&lt;li>&lt;strong>Incremental&lt;/strong>: Large tables where only a fraction of rows change. The right choice for most fact tables.&lt;/li>
&lt;/ul>
&lt;p>If a model takes more than 30 seconds to build and is queried more than once between builds, it should be a table or incremental — not a view.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="coming-back-to-priyas-scatter-plot">Coming back to Priya&amp;rsquo;s scatter plot&lt;/h3>
&lt;br>
&lt;p>Six weeks after Priya showed me that graph, we&amp;rsquo;d fixed the three root causes: an intermediate model that had grown from 5 million to 180 million rows without being switched to incremental, a phantom dependency chain that added 12 minutes of serial wait time, and a warehouse that was being shared between transformation and BI queries during the morning refresh window.&lt;/p>
&lt;p>Pipeline completion moved from 7:23 AM back to 5:15 AM. The finance team got their dashboards before their morning coffee. And Priya? She built a monitoring dashboard that tracked completion time drift automatically — the thing I should have built from the start.&lt;/p>
&lt;p>Here&amp;rsquo;s what I learned from that experience, and what I hope you take from this article: &lt;strong>pipeline performance isn&amp;rsquo;t a problem you solve once.&lt;/strong> It&amp;rsquo;s a trajectory you manage. Data grows. Teams add models. Source systems change. The pipeline that&amp;rsquo;s fast today will be slow in six months unless someone is watching the trend line.&lt;/p>
&lt;p>The engineers who build reliable data platforms aren&amp;rsquo;t the ones who build the fastest pipeline on day one. They&amp;rsquo;re the ones who notice when completion time drifts by 3 minutes per week — and fix it before anyone else has to ask.&lt;/p>
&lt;p>Track your completion times. Audit your dependencies quarterly. Profile your critical path after every significant change. And teach your team that a green DAG doesn&amp;rsquo;t mean a healthy pipeline. Sometimes green just means it hasn&amp;rsquo;t failed &lt;em>yet&lt;/em>.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Cloud Architecture</category><category>Snowflake</category><category>AWS</category><category>Airflow</category><category>Pipeline Optimization</category><category>dbt</category><category>Data Freshness</category></item><item><title>Stop Building Salesforce Integrations From Scratch</title><link>https://ghostinthedata.info/posts/2026/2026-04-04-snowflake-openflow/</link><pubDate>Sat, 04 Apr 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-04-04-snowflake-openflow/</guid><author>Chris Hillman</author><description>A hands-on guide to Snowflake's OpenFlow Salesforce connector — why managed connectors beat custom code, how to set one up step by step, and the schema evolution feature that makes it all worth it.</description><content:encoded>&lt;p>Let me tell you about Marcus.&lt;/p>
&lt;/br>
&lt;p>Marcus was on a team I led a few years back. Sharp, motivated, the kind of engineer who actually read documentation before writing code. When the business asked us to get Salesforce data into our warehouse, Marcus volunteered. He&amp;rsquo;d done API work before. He figured a few weeks, tops.&lt;/p>
&lt;p>He scoped it carefully. Built a Python service that authenticated via OAuth, pulled Account, Contact, and Opportunity objects through the Bulk API, flattened the nested JSON into relational tables, handled pagination, managed rate limits. Wrote solid tests. Documented everything. The kind of work you&amp;rsquo;d point to in a code review and say &lt;em>this is how it&amp;rsquo;s done&lt;/em>.&lt;/p>
&lt;p>It worked beautifully. For about three months.&lt;/p>
&lt;/br>
&lt;p>Then our Salesforce admin added a custom field to the Opportunity object — &lt;code>Renewal_Likelihood__c&lt;/code> — and nobody told the data team. The pipeline didn&amp;rsquo;t fail. That&amp;rsquo;s the insidious part. It kept running, kept landing data. It just quietly dropped the new field on the floor.&lt;/p>
&lt;p>When Marcus tracked it down, he added the field. Then he realised there were &lt;em>eleven&lt;/em> other custom fields that had been added since go-live that the pipeline was silently ignoring. And the compound address fields — &lt;code>BillingAddress&lt;/code>, &lt;code>ShippingAddress&lt;/code> — had never worked with the Bulk API in the first place. He&amp;rsquo;d been extracting the component parts (&lt;code>BillingStreet&lt;/code>, &lt;code>BillingCity&lt;/code>) as a workaround, but the workaround had a bug that truncated postal codes for international addresses.&lt;/p>
&lt;p>Marcus spent three weeks patching all of it. Three weeks of a talented engineer doing work that a managed connector handles automatically.&lt;/p>
&lt;/br>
&lt;p>When I watched a good engineer spend the best part of a month on a problem that shouldn&amp;rsquo;t exist. Marcus wasn&amp;rsquo;t learning anything. He wasn&amp;rsquo;t growing. He was hand-coding schema detection logic for the fourth time because Salesforce ships three major releases a year and our sales ops team adds custom fields like they&amp;rsquo;re decorating a Christmas tree.&lt;/p>
&lt;p>I&amp;rsquo;m telling you this because &lt;strong>Snowflake OpenFlow&lt;/strong> is one of those things that sounds like &amp;ldquo;just another integration tool&amp;rdquo; until you&amp;rsquo;ve lived through the alternative. What I want to show you today is how to set it up with Salesforce — step by step, with enough detail that you could do it tomorrow — and more importantly, &lt;em>why&lt;/em> the schema evolution feature alone justifies the switch from custom code.&lt;/p>
&lt;p>If you&amp;rsquo;ve ever maintained a custom Salesforce integration, you already know why this matters.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-problem-nobody-warns-you-about">The Problem Nobody Warns You About&lt;/h3>
&lt;/br>
&lt;p>Here&amp;rsquo;s what the &amp;ldquo;just build it yourself&amp;rdquo; crowd doesn&amp;rsquo;t tell you: the Salesforce API isn&amp;rsquo;t one API. It&amp;rsquo;s an ecosystem of overlapping interfaces, each with its own quirks, limits, and failure modes.&lt;/p>
&lt;p>The REST API handles small, synchronous requests well — but try pulling a million Opportunity records through it and you&amp;rsquo;ll burn through your rate limits before lunch. The Bulk API 2.0 is designed for volume — it can handle up to 150 million records in a 24-hour window — but it doesn&amp;rsquo;t support compound fields like &lt;code>BillingAddress&lt;/code> or &lt;code>MailingAddress&lt;/code>. Those silently return nothing. Not an error. Nothing.&lt;/p>
&lt;p>Then there&amp;rsquo;s SOQL, Salesforce&amp;rsquo;s proprietary query language, which looks enough like SQL to trick you into thinking you understand it. Until you hit the 2,000-record offset limit, or try to resolve a polymorphic relationship where a &lt;code>WhoId&lt;/code> field on a Task could reference either a Contact or a Lead depending on the record. That&amp;rsquo;s not a data modelling problem — that&amp;rsquo;s a &amp;ldquo;your pipeline logic needs to branch based on the referenced object type&amp;rdquo; problem.&lt;/p>
&lt;p>And rate limits. Enterprise Edition gives you 100,000 API calls per 24 hours, plus 1,000 per user licence. Sounds generous until you remember that your BI tool, marketing automation platform, customer support system, and your data pipeline are all drinking from the same well.&lt;/p>
&lt;/br>
&lt;p>But schema evolution is the silent killer.&lt;/p>
&lt;p>Every Salesforce org is different because every business is different. Your Salesforce admin creates custom objects and fields to match how your company actually works. That&amp;rsquo;s the point — it&amp;rsquo;s a customisation platform. But every custom field added to Salesforce is a potential break point for any integration that doesn&amp;rsquo;t handle schema changes automatically.&lt;/p>
&lt;p>And those changes happen constantly. Research suggests schemas in enterprise SaaS tools change roughly every three days on average. In Salesforce specifically, between admin-driven customisation and Salesforce&amp;rsquo;s own three-times-a-year release cycle (Spring, Summer, Winter), the schema you built against last quarter may not be the schema you&amp;rsquo;re ingesting from today.&lt;/p>
&lt;p>This is the trap. Building the initial integration isn&amp;rsquo;t hard. Any competent engineer can get Salesforce data into a warehouse. &lt;strong>Keeping it working&lt;/strong> — across schema changes, API version deprecations, authentication token rotations, and rate limit adjustments — is where it eats your life.&lt;/p>
&lt;p>I&amp;rsquo;ve seen teams eliminate two full days of monthly engineering maintenance by migrating off custom connectors. Two days, every month, spent on work that a managed tool does automatically. That&amp;rsquo;s not engineering — it&amp;rsquo;s janitorial work wearing an engineering costume.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-is-snowflake-openflow">What Is Snowflake OpenFlow?&lt;/h3>
&lt;/br>
&lt;p>OpenFlow is Snowflake&amp;rsquo;s native, fully managed data integration service. It&amp;rsquo;s not a partnership or a marketplace app — it&amp;rsquo;s a first-party feature built into the platform. If you&amp;rsquo;re already running Snowflake, OpenFlow runs inside your existing environment.&lt;/p>
&lt;p>The backstory matters. In November 2024, Snowflake acquired a company called &lt;strong>Datavolo&lt;/strong> for roughly $110 million. Datavolo was founded by Joe Witt, who co-created Apache NiFi back when it was an NSA project. That NiFi heritage is the foundation of OpenFlow — curated, versioned connector definitions powered by NiFi&amp;rsquo;s flow engine, wrapped in a managed Snowflake experience.&lt;/p>
&lt;p>Architecturally, it splits into two pieces. The &lt;strong>control plane&lt;/strong> lives inside Snowflake and gives you pipeline management, scheduling, and monitoring through the Snowsight UI. The &lt;strong>data plane&lt;/strong> handles the actual work and can run in two modes: &lt;strong>Snowflake Deployments&lt;/strong> (running on Snowpark Container Services within Snowflake&amp;rsquo;s own infrastructure) or &lt;strong>BYOC&lt;/strong> (Bring Your Own Cloud, running as a Kubernetes cluster in your VPC). For most teams starting out, the SPCS option is simpler — it went generally available in November 2025 on AWS and Azure.&lt;/p>
&lt;p>Data flows into Snowflake primarily through Snowpipe Streaming for the initial load, with merge queries handling incremental updates. The result is structured tables in your Snowflake database — not staged JSON files, not semi-structured VARIANT columns. Actual, queryable tables with proper column types.&lt;/p>
&lt;/br>
&lt;p>The connector catalogue currently covers about 20 curated sources: databases (PostgreSQL, MySQL, SQL Server, Oracle), SaaS platforms (Salesforce, Workday, Jira, LinkedIn Ads, Meta Ads, Google Ads), cloud storage (Google Drive, SharePoint, Box), and streaming sources (Kafka, Kinesis). It&amp;rsquo;s not Fivetran&amp;rsquo;s 500+ connector library, and nobody&amp;rsquo;s pretending it is. But the sources it does cover are the ones that account for the vast majority of enterprise data movement.&lt;/p>
&lt;p>One important caveat up front: the &lt;strong>Salesforce Bulk API connector is still in preview&lt;/strong> as of March 2026. It works. Preview in Snowflake terms means the feature is functional but not yet covered by production support SLAs. Keep that in your mental model as we walk through the setup.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="setting-up-the-salesforce-connector-a-walkthrough">Setting Up the Salesforce Connector: A Walkthrough&lt;/h3>
&lt;/br>
&lt;p>This section is the step-by-step. I&amp;rsquo;m going to walk through the full setup — Salesforce configuration, Snowflake preparation, and connector deployment — with enough detail that you could follow along on your own org.&lt;/p>
&lt;/br>
&lt;h4 id="phase-1-salesforce-configuration">Phase 1: Salesforce Configuration&lt;/h4>
&lt;p>The connector uses JWT Bearer Flow for authentication, which means you need an RSA key pair and a Connected App configured in Salesforce.&lt;/p>
&lt;p>&lt;strong>Generate your RSA key pair.&lt;/strong> Open a terminal and run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Generate 2048-bit RSA private key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>openssl genrsa -out salesforce_private_key.pem &lt;span style="color:#ae81ff">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Generate the corresponding public certificate (valid 1 year)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>openssl req -new -x509 -key salesforce_private_key.pem &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -out salesforce_certificate.pem -days &lt;span style="color:#ae81ff">365&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Keep that private key safe. You&amp;rsquo;ll need it when configuring the connector in Snowflake.&lt;/p>
&lt;p>&lt;strong>Create the External Client App in Salesforce.&lt;/strong> Navigate to Setup → Apps → App Manager → New External Client App. Enable OAuth and add two scopes: &lt;code>api&lt;/code> and &lt;code>refresh_token&lt;/code> (sometimes labelled &lt;code>offline_access&lt;/code>). Enable the JWT Bearer Flow toggle and upload your &lt;code>salesforce_certificate.pem&lt;/code> public certificate. Save the app and record the &lt;strong>Consumer Key&lt;/strong> and &lt;strong>Consumer Secret&lt;/strong> that Salesforce generates.&lt;/p>
&lt;p>&lt;strong>Configure access.&lt;/strong> On the app&amp;rsquo;s Manage settings, set the Permitted Users policy to &amp;ldquo;Admin approved users are pre-authorized.&amp;rdquo; Then assign the appropriate user profiles or permission sets. The user account the connector will authenticate as needs read access to every object you intend to sync.&lt;/p>
&lt;/br>
&lt;h4 id="phase-2-snowflake-preparation">Phase 2: Snowflake Preparation&lt;/h4>
&lt;p>Before deploying the connector, you need a database, schema, role, and warehouse ready for it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a dedicated database for Salesforce data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">DATABASE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> SALESFORCE_RAW;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> SALESFORCE_RAW.OPENFLOW;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a connector-specific role
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Grant the role permissions to write to the destination
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">USAGE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">DATABASE&lt;/span> SALESFORCE_RAW &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">USAGE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> SALESFORCE_RAW.OPENFLOW &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> SALESFORCE_RAW.OPENFLOW &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a warehouse for merge operations
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> WAREHOUSE &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_WH
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SMALL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">USAGE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> WAREHOUSE OPENFLOW_WH &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> OPERATE &lt;span style="color:#66d9ef">ON&lt;/span> WAREHOUSE OPENFLOW_WH &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For SPCS deployments, you also need a &lt;strong>network rule&lt;/strong> allowing the connector to reach your Salesforce instance. OpenFlow runs inside Snowflake&amp;rsquo;s container environment, and by default it can&amp;rsquo;t reach external endpoints without explicit permission:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Allow egress to your Salesforce instance
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> NETWORK &lt;span style="color:#66d9ef">RULE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_SF_EGRESS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">TYPE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> HOST_PORT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MODE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> EGRESS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> VALUE_LIST &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;yourinstance.salesforce.com:443&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Wrap it in an external access integration
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">EXTERNAL&lt;/span> &lt;span style="color:#66d9ef">ACCESS&lt;/span> INTEGRATION &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_SF_ACCESS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALLOWED_NETWORK_RULES &lt;span style="color:#f92672">=&lt;/span> (OPENFLOW_SF_EGRESS)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ENABLED &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Replace &lt;code>yourinstance.salesforce.com&lt;/code> with your actual Salesforce instance hostname. If you&amp;rsquo;re not sure what that is, log in to Salesforce and look at the URL bar — it&amp;rsquo;s the domain before &lt;code>.salesforce.com&lt;/code>.&lt;/p>
&lt;/br>
&lt;h4 id="phase-3-deploying-the-connector">Phase 3: Deploying the Connector&lt;/h4>
&lt;p>Now the fun part. Navigate to &lt;strong>Snowsight → Data → Openflow&lt;/strong> in the left sidebar. Find the &amp;ldquo;Openflow connector for Salesforce Bulk API&amp;rdquo; tile and add it to your runtime.&lt;/p>
&lt;p>This opens the NiFi canvas — a visual flow editor where the connector&amp;rsquo;s pre-built process groups appear. Right-click the Salesforce process group and select &lt;strong>Configure Parameters&lt;/strong>. Here&amp;rsquo;s where you&amp;rsquo;ll enter:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Salesforce Instance URL&lt;/strong> — e.g., &lt;code>https://yourinstance.salesforce.com&lt;/code>&lt;/li>
&lt;li>&lt;strong>Consumer Key&lt;/strong> — from the Connected App you created&lt;/li>
&lt;li>&lt;strong>Consumer Secret&lt;/strong> — same source&lt;/li>
&lt;li>&lt;strong>Private Key&lt;/strong> — the contents of &lt;code>salesforce_private_key.pem&lt;/code> (the full PEM text, headers included)&lt;/li>
&lt;li>&lt;strong>Username&lt;/strong> — the Salesforce user the connector authenticates as&lt;/li>
&lt;li>&lt;strong>Filter&lt;/strong> — the objects to sync, by API name&lt;/li>
&lt;/ul>
&lt;p>The &lt;strong>Filter&lt;/strong> parameter is where you define what to pull. List standard objects by name (&lt;code>Account&lt;/code>, &lt;code>Contact&lt;/code>, &lt;code>Opportunity&lt;/code>, &lt;code>Lead&lt;/code>, &lt;code>Task&lt;/code>, &lt;code>Event&lt;/code>) and custom objects using their API name with the &lt;code>__c&lt;/code> suffix:&lt;/p>



&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 720 25"
 >
 &lt;g transform='translate(8,16)'>
&lt;text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='456' y='4' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='464' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='472' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='480' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='4' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='496' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='504' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='512' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='520' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='528' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='536' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='544' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='560' y='4' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='568' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='576' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='584' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='592' y='4' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='600' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='608' y='4' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='616' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='624' y='4' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='632' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='640' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='648' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='656' y='4' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='664' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='672' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='680' y='4' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='688' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='696' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='704' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div>
&lt;p>There&amp;rsquo;s also a &lt;strong>Special Objects Filter&lt;/strong> for objects that require different handling (like &lt;code>User&lt;/code> or &lt;code>RecordType&lt;/code>).&lt;/p>
&lt;p>Once the parameters are configured, enable all controller services in the process group, then start the connector. The initial sync will pull a full snapshot of every object in your filter list. Subsequent runs perform incremental syncs using Salesforce&amp;rsquo;s &lt;code>SystemModstamp&lt;/code> field to identify changed records.&lt;/p>
&lt;/br>
&lt;h4 id="what-you-get">What You Get&lt;/h4>
&lt;p>Once the connector runs, you&amp;rsquo;ll find one table per Salesforce object in &lt;code>SALESFORCE_RAW.OPENFLOW&lt;/code>. The tables are &lt;strong>flattened and structured&lt;/strong> — every Salesforce field becomes a column with an appropriate Snowflake data type. No VARIANT columns to parse. No nested JSON to unpack.&lt;/p>
&lt;p>An &lt;code>Account&lt;/code> table will have columns like &lt;code>ID&lt;/code>, &lt;code>NAME&lt;/code>, &lt;code>INDUSTRY&lt;/code>, &lt;code>ANNUALREVENUE&lt;/code>, &lt;code>BILLINGSTREET&lt;/code>, &lt;code>BILLINGCITY&lt;/code>, &lt;code>BILLINGSTATE&lt;/code>, &lt;code>BILLINGPOSTALCODE&lt;/code>, &lt;code>CREATEDDATE&lt;/code>, &lt;code>LASTMODIFIEDDATE&lt;/code>, and so on. Custom fields like &lt;code>Renewal_Likelihood__c&lt;/code> appear as &lt;code>RENEWAL_LIKELIHOOD__C&lt;/code>.&lt;/p>
&lt;p>Two metadata columns are added automatically: &lt;code>ISDELETED&lt;/code> (tracking Salesforce soft deletes) and the system timestamp fields. This is important — &lt;strong>hard deletes in Salesforce are not reflected&lt;/strong>. If a record is permanently deleted in Salesforce, it won&amp;rsquo;t disappear from your Snowflake table. You&amp;rsquo;ll need to handle that in your downstream dbt models if it matters for your use case.&lt;/p>
&lt;/br>
&lt;h4 id="a-note-on-multiple-sync-frequencies">A Note on Multiple Sync Frequencies&lt;/h4>
&lt;p>Not every Salesforce object changes at the same pace. Your &lt;code>Opportunity&lt;/code> table might need hourly syncs while &lt;code>Account&lt;/code> is fine with daily. OpenFlow handles this by letting you deploy &lt;strong>multiple connector instances in the same runtime&lt;/strong> — each with its own filter list and schedule — at no additional compute cost beyond the shared runtime.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="schema-evolution-where-openflow-earns-its-keep">Schema Evolution: Where OpenFlow Earns Its Keep&lt;/h3>
&lt;/br>
&lt;p>This is the feature that matters most, and the one that would have saved Marcus three weeks.&lt;/p>
&lt;p>When someone adds a new custom field to a Salesforce object — say the sales ops team creates &lt;code>Deal_Confidence_Score__c&lt;/code> on the Opportunity object — the OpenFlow connector &lt;strong>automatically detects the new field and adds a corresponding column&lt;/strong> to the Snowflake destination table on the next sync. No configuration change. No redeployment. No Teams message at 7am asking the data team to &amp;ldquo;add that new field we made yesterday.&amp;rdquo;&lt;/p>
&lt;p>The column appears, data starts flowing into it, and your dbt models can reference it whenever you&amp;rsquo;re ready.&lt;/p>
&lt;/br>
&lt;p>For field renames, OpenFlow takes a conservative approach: the old column stays in place (with stale data from the last sync before the rename), and a new column is created under the new field name. This means you&amp;rsquo;ll have both &lt;code>Old_Field_Name__c&lt;/code> and &lt;code>New_Field_Name__c&lt;/code> in your table for a period. That&amp;rsquo;s actually useful for audit purposes — you can see exactly when the rename happened by comparing timestamps — but it does mean your downstream queries need updating.&lt;/p>
&lt;p>Here&amp;rsquo;s the honest gap: &lt;strong>new custom objects are not auto-discovered&lt;/strong>. If someone creates an entirely new custom object in Salesforce, you have to manually add it to the connector&amp;rsquo;s Filter parameter. Schema evolution handles field-level changes automatically, but object-level discovery is still a manual step.&lt;/p>
&lt;p>And field type changes — say someone changes a text field to a picklist, or a number to a formula — aren&amp;rsquo;t comprehensively documented in the current OpenFlow materials. In practice, this is a rare enough occurrence that it hasn&amp;rsquo;t been a problem in my experience, but it&amp;rsquo;s worth knowing the edge case exists.&lt;/p>
&lt;/br>
&lt;p>Compare this to what Marcus had to maintain. His custom integration used a hardcoded list of fields per object. Every time a field was added, renamed, or retyped, someone had to update the Python code, test it, deploy it, and verify the data. Usually that &amp;ldquo;someone&amp;rdquo; was Marcus, usually at an inconvenient time, and usually because nobody told him the change was coming.&lt;/p>
&lt;p>The value of automatic schema evolution isn&amp;rsquo;t technical elegance. It&amp;rsquo;s that &lt;strong>your engineers stop spending time on schema babysitting and start spending it on work that actually matters&lt;/strong> — building models, improving data quality, answering the questions the business is actually asking.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="a-word-about-fivetran">A Word About Fivetran&lt;/h3>
&lt;/br>
&lt;p>I&amp;rsquo;d be doing you a disservice if I wrote about managed Salesforce connectors without mentioning Fivetran. For most of the last decade, Fivetran has been the gold standard here, and their Salesforce connector is genuinely excellent.&lt;/p>
&lt;p>Fivetran&amp;rsquo;s schema evolution handling is more mature and more configurable than OpenFlow&amp;rsquo;s current offering. They offer three schema change policies: &lt;code>ALLOW_ALL&lt;/code> (sync everything automatically, including new tables), &lt;code>ALLOW_COLUMNS&lt;/code> (auto-add new columns but not new tables), and &lt;code>BLOCK_ALL&lt;/code> (manual opt-in for everything). They also generate a &lt;code>LOG&lt;/code> table that records every schema change event — &lt;code>create_table&lt;/code>, &lt;code>alter_table&lt;/code>, &lt;code>drop_table&lt;/code> — giving you an audit trail that&amp;rsquo;s useful for debugging and compliance. Field type widening (like &lt;code>INT&lt;/code> to &lt;code>LONG&lt;/code>) happens automatically; narrowing changes are blocked to prevent data loss.&lt;/p>
&lt;p>Their pricing model is fundamentally different from OpenFlow. Fivetran charges per Monthly Active Row (MAR) — the count of distinct rows synced per connection per month. A unique primary key counts once regardless of how many times it&amp;rsquo;s updated. Approximate pricing clusters around $500 per million MAR on the Standard plan, with a $12,000 annual minimum commitment. Since early 2025, MAR is calculated per connection rather than account-wide, which eliminated the bulk discount that previously benefited multi-connector setups.&lt;/p>
&lt;/br>
&lt;p>OpenFlow, by contrast, charges for compute — SPCS credits for the container runtime, Snowpipe Streaming costs for ingestion, and warehouse costs for merge operations. There&amp;rsquo;s no per-row fee and no separate licence. It&amp;rsquo;s included with Snowflake. The trade-off is that your management compute pool runs continuously while a deployment exists, which means you&amp;rsquo;re paying a base cost even when no data is flowing. One estimate I&amp;rsquo;ve seen puts idle costs at roughly $10 per day, though this varies with configuration.&lt;/p>
&lt;p>For a single large Salesforce org, OpenFlow&amp;rsquo;s compute-based model may end up cheaper than Fivetran&amp;rsquo;s per-row pricing — especially if your Salesforce data doesn&amp;rsquo;t churn heavily. For environments where you need 30+ connectors across different source types, Fivetran&amp;rsquo;s breadth and maturity is hard to argue with. They have 500+ connectors. OpenFlow has about 20.&lt;/p>
&lt;p>The honest framing: &lt;strong>OpenFlow is the better choice if you&amp;rsquo;re already deeply invested in Snowflake, value platform consolidation, and your primary sources are in the current connector catalogue.&lt;/strong> Fivetran is the safer choice if you need broad connector coverage, battle-tested reliability, and you&amp;rsquo;d rather pay a premium for someone else to handle the ops.&lt;/p>
&lt;p>They&amp;rsquo;re not mutually exclusive, either. I&amp;rsquo;ve seen teams run Fivetran for the long tail of small connectors while using OpenFlow for their highest-volume sources where compute-based pricing wins.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-zero-copy-alternative">The Zero-Copy Alternative&lt;/h3>
&lt;/br>
&lt;p>There&amp;rsquo;s a third path I haven&amp;rsquo;t mentioned yet, and it&amp;rsquo;s worth understanding even if it&amp;rsquo;s not right for every team: &lt;strong>zero-copy data federation&lt;/strong>.&lt;/p>
&lt;p>Salesforce Data Cloud offers a zero-copy integration with Snowflake that flips the entire model on its head. Instead of extracting data from Salesforce and loading it into your warehouse, zero-copy gives you direct read access to Salesforce data &lt;em>without moving it at all&lt;/em>. Salesforce&amp;rsquo;s query pushdown engine handles the work — when you query the federated data, Salesforce pushes the query to the source, filters and aggregates there, and returns only the results you need. No pipelines to build. No schemas to manage. No sync schedules to configure.&lt;/p>
&lt;p>It works bidirectionally, too. You can share data from Snowflake back into Salesforce Data Cloud, letting sales reps see warehouse-enriched signals directly on Account and Opportunity pages without anyone exporting a CSV or building a reverse ETL pipeline. The integration runs on Apache Iceberg under the hood, which at least means the underlying format is open.&lt;/p>
&lt;/br>
&lt;p>For handling new custom objects — the gap I flagged with OpenFlow — zero-copy sidesteps the problem entirely. There&amp;rsquo;s nothing to discover because there&amp;rsquo;s nothing to sync. The data stays where it is, and you access it in place. As your Salesforce org evolves, the federated view evolves with it.&lt;/p>
&lt;p>That&amp;rsquo;s genuinely appealing. For proof-of-concept work, rapid prototyping, or use cases where you need real-time access to Salesforce data without building infrastructure, zero-copy is fast and effective.&lt;/p>
&lt;/br>
&lt;p>But here&amp;rsquo;s where I get cautious.&lt;/p>
&lt;p>&lt;strong>It comes at a premium.&lt;/strong> Salesforce Data Cloud runs on a consumption credit model — credits are purchased in bundles (roughly $500 per 100,000 credits at list price), and every action burns them. Zero-copy federation queries consume about 70 credits per million records, which sounds reasonable until you&amp;rsquo;re running it at scale across multiple business units. The pricing model has simplified since late 2025 — Salesforce consolidated multiple credit types into a single fungible credit and made ingestion from core Salesforce products free — but it&amp;rsquo;s still a consumption model with real costs that compound quickly if you&amp;rsquo;re not watching.&lt;/p>
&lt;p>More importantly, &lt;strong>zero-copy creates a deep platform dependency&lt;/strong>. Your data access is mediated entirely through Salesforce and Snowflake&amp;rsquo;s partnership. If Salesforce changes their terms, adjusts their pricing (which they&amp;rsquo;ve done repeatedly), or if you decide to move off Snowflake to Databricks or BigQuery, that zero-copy integration doesn&amp;rsquo;t come with you. You&amp;rsquo;d need to build actual data movement infrastructure — the thing you avoided — under time pressure and with no existing pipeline to fall back on.&lt;/p>
&lt;/br>
&lt;p>I value keeping infrastructure portable. Not because I&amp;rsquo;m paranoid about vendor lock-in — I&amp;rsquo;m pragmatic about it. I&amp;rsquo;ve been in this industry long enough to see what happens when a new CEO arrives and wants to renegotiate every vendor contract, or when a cloud provider changes their pricing structure overnight, or when the company you depend on gets acquired and the roadmap shifts. Having your data physically in your own warehouse, in formats you control, with pipelines you can redirect to a different destination — that&amp;rsquo;s insurance. Not glamorous insurance, but the kind you&amp;rsquo;re grateful for when you need it.&lt;/p>
&lt;p>The balance is real, though. You always have to weigh portability against the time you spend building and maintaining pipelines. If your team is drowning in pipeline maintenance, zero-copy might buy you breathing room while you stand up proper infrastructure. Just go in with your eyes open about what you&amp;rsquo;re trading for that convenience.&lt;/p>
&lt;/br>
&lt;p>There&amp;rsquo;s a more fundamental concern, though, and it&amp;rsquo;s the one that won&amp;rsquo;t appear in any vendor comparison chart.&lt;/p>
&lt;p>&lt;strong>Zero-copy makes it dangerously easy to skip the data warehouse entirely.&lt;/strong> When you give analysts and report builders direct read access to raw Salesforce data, the temptation to build dashboards straight from it is enormous. And for a quick proof of concept or a one-off analysis, fine. But the moment those PoC dashboards become &amp;ldquo;the dashboard the VP checks every Monday,&amp;rdquo; you&amp;rsquo;ve got a problem.&lt;/p>
&lt;p>Raw source data doesn&amp;rsquo;t tell you which &lt;code>CustomerNo&lt;/code> field to use — the one from the legacy migration, the one from the 2023 CRM consolidation, or the one from the current integration. It doesn&amp;rsquo;t encode the business rule that says &amp;ldquo;Closed Won&amp;rdquo; opportunities in APAC exclude training deals under $5,000 because those are tracked separately. It doesn&amp;rsquo;t flag that the &lt;code>Last_Activity_Date&lt;/code> field is unreliable for accounts owned by the partner team because they log activities in a different system.&lt;/p>
&lt;/br>
&lt;p>That knowledge lives in your data warehouse layer. It lives in the dbt models that your team built over months of conversations with stakeholders, through change management cycles, through debugging sessions where someone finally said &amp;ldquo;oh, we stopped using that field in Q3 because&amp;hellip;&amp;rdquo; That&amp;rsquo;s not transformation logic — it&amp;rsquo;s &lt;em>institutional memory encoded as code&lt;/em>. It connects raw data across domains and gives the business a full landscape picture instead of a narrow, single-source view. It catches the behavioural anomalies unique to your organisation — the sales rep who bulk-updates 500 records every Friday afternoon, the integration that occasionally double-fires during DST transitions, the custom object that gets repurposed every time the business restructures.&lt;/p>
&lt;p>So by all means, evaluate zero-copy for the use cases where it shines. Real-time signals on Account pages for sales reps? Great fit. Federated access for a specific analytics team that knows the source data intimately? Reasonable. But don&amp;rsquo;t let it replace the warehouse layer. That layer is where the hard-won understanding of your data lives, and it&amp;rsquo;s harder to recreate than any pipeline.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-honest-trade-offs">The Honest Trade-Offs&lt;/h3>
&lt;/br>
&lt;p>I&amp;rsquo;ve been positive about OpenFlow because I think it solves a real problem well. But I&amp;rsquo;d be violating my own writing principles if I didn&amp;rsquo;t lay out the limitations clearly.&lt;/p>
&lt;p>&lt;strong>The Salesforce connector is still in preview.&lt;/strong> It works, but it&amp;rsquo;s not covered by production support SLAs. If you&amp;rsquo;re running a mission-critical pipeline that your CFO stares at every Monday morning, that matters.&lt;/p>
&lt;p>&lt;strong>Custom Salesforce domains aren&amp;rsquo;t supported.&lt;/strong> If your org uses a vanity URL (like &lt;code>mycompany.my.salesforce.com&lt;/code> with a custom domain), check the documentation carefully before committing.&lt;/p>
&lt;p>&lt;strong>Hard deletes aren&amp;rsquo;t tracked.&lt;/strong> The &lt;code>ISDELETED&lt;/code> column catches soft deletes, but records that are permanently purged from Salesforce will persist in your Snowflake tables indefinitely. You&amp;rsquo;ll need a reconciliation process if that matters for your use case.&lt;/p>
&lt;p>&lt;strong>Certain field types are silently dropped.&lt;/strong> &lt;code>location&lt;/code>, &lt;code>address&lt;/code> (compound), and &lt;code>base64&lt;/code> fields are not synced. The connector doesn&amp;rsquo;t error on these — it just ignores them. This is the kind of thing that bites you three months in when someone asks why geographic data isn&amp;rsquo;t in the warehouse.&lt;/p>
&lt;p>&lt;strong>Formula fields require full refresh.&lt;/strong> Because formula field values are computed server-side and don&amp;rsquo;t update &lt;code>SystemModstamp&lt;/code>, they can&amp;rsquo;t be synced incrementally. You need a separate connector instance running full refreshes to capture formula field changes. That&amp;rsquo;s not a bug — it&amp;rsquo;s how Salesforce works — but it&amp;rsquo;s a gotcha if you&amp;rsquo;re not expecting it.&lt;/p>
&lt;p>&lt;strong>No relationship traversal.&lt;/strong> You can&amp;rsquo;t configure the connector to follow lookups and pull related objects automatically. Each object is pulled independently. Joining them is your job in the transformation layer, which is where it belongs anyway if you&amp;rsquo;re running dbt.&lt;/p>
&lt;/br>
&lt;p>None of these are dealbreakers. But every one of them is the kind of thing that bites an engineer at 4pm on a Friday if they weren&amp;rsquo;t expecting it. Now you&amp;rsquo;re expecting it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="getting-started">Getting Started&lt;/h3>
&lt;/br>
&lt;p>If you want to try this on your own Salesforce org, here&amp;rsquo;s my recommended order of operations:&lt;/p>
&lt;p>Start with a &lt;strong>non-production Salesforce sandbox&lt;/strong> and a Snowflake trial account if you don&amp;rsquo;t have a dev environment handy. Pick two or three standard objects — &lt;code>Account&lt;/code>, &lt;code>Contact&lt;/code>, &lt;code>Opportunity&lt;/code> — and one custom object if your org has them. Run through the three-phase setup I described above. Get data flowing, verify the table structures, and then add a custom field to one of the objects in Salesforce. Wait for the next sync. Watch the column appear automatically in Snowflake.&lt;/p>
&lt;p>That&amp;rsquo;s the moment it clicks. That&amp;rsquo;s the moment you stop thinking about managed connectors as a luxury and start thinking about custom API code as technical debt you&amp;rsquo;re choosing to carry.&lt;/p>
&lt;p>Once you&amp;rsquo;ve validated the core flow, layer on your dbt models. OpenFlow lands raw data — it doesn&amp;rsquo;t transform it. Your staging models handle naming conventions, type casting, soft delete filtering, and the join logic that stitches related objects together. This is the same pattern you&amp;rsquo;d use with Fivetran or any other EL tool: land it raw, transform it in the warehouse.&lt;/p>
&lt;/br>
&lt;p>For teams already running Fivetran for Salesforce, I&amp;rsquo;m not suggesting you rip it out tomorrow. If it&amp;rsquo;s working and the cost is acceptable, that&amp;rsquo;s a solved problem. But the next time you&amp;rsquo;re adding a new high-volume source that&amp;rsquo;s in OpenFlow&amp;rsquo;s catalogue — especially if you&amp;rsquo;re already paying for Snowflake — it&amp;rsquo;s worth running the numbers. You might find that native integration with compute-based pricing is the better deal.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-real-why">The Real Why&lt;/h3>
&lt;/br>
&lt;p>I started this article with Marcus&amp;rsquo;s story because it illustrates something I feel strongly about: &lt;strong>your engineers&amp;rsquo; time is the most expensive resource in your data organisation, and spending it on solved problems is a leadership failure&lt;/strong>.&lt;/p>
&lt;p>Building a custom Salesforce integration isn&amp;rsquo;t impressive. It was impressive in 2016, when the tooling didn&amp;rsquo;t exist. Today, it&amp;rsquo;s a choice to take on maintenance burden that a managed service handles automatically. It&amp;rsquo;s choosing to babysit schema changes instead of building the models and analyses that actually move the business forward.&lt;/p>
&lt;p>Marcus is a senior engineer now. He builds dimensional models and designs data products that directly influence how the sales team allocates resources. He doesn&amp;rsquo;t write API integration code anymore. Not because he can&amp;rsquo;t — because his time is worth more than that.&lt;/p>
&lt;/br>
&lt;p>But here&amp;rsquo;s what I keep coming back to. The tools keep getting better — OpenFlow, Fivetran, zero-copy, and yes, AI-assisted pipeline generation. Every year there&amp;rsquo;s a new thing that promises to automate another piece of the data engineering workflow. And many of them genuinely do.&lt;/p>
&lt;p>What they don&amp;rsquo;t automate is the part that actually matters.&lt;/p>
&lt;p>No tool — not OpenFlow, not Fivetran, not an AI agent — is going to sit in a room with your head of sales and ask &amp;ldquo;what keeps you up at night?&amp;rdquo; No connector is going to notice that the way your company defines &amp;ldquo;active customer&amp;rdquo; has quietly drifted from what the CRM tracks. No pipeline is going to push back and say &amp;ldquo;we could build that dashboard, but the metric you&amp;rsquo;re asking for doesn&amp;rsquo;t answer the question you actually have.&amp;rdquo;&lt;/p>
&lt;/br>
&lt;p>That&amp;rsquo;s the connective tissue between raw data and business value. It&amp;rsquo;s a person who understands the domain, who&amp;rsquo;s been present through the change management cycles, who&amp;rsquo;s built relationships with the stakeholders and knows which &lt;code>CustomerNo&lt;/code> field to use because they were in the room when the decision was made three years ago. It&amp;rsquo;s someone who asks clarifying questions instead of presuming they already know the answer.&lt;/p>
&lt;p>AI will keep getting better at the mechanical parts of data engineering. Schema detection, pipeline code generation, anomaly detection — those are well-defined problems that automation is suited for. But the assumption that any tool can skip the human step — the clarification, the context, the judgment about what&amp;rsquo;s actually worth building — that&amp;rsquo;s where I&amp;rsquo;ve seen projects go sideways. Not because the technology failed, but because nobody asked the right questions before building.&lt;/p>
&lt;/br>
&lt;p>OpenFlow isn&amp;rsquo;t perfect. The connector catalogue is young, the Salesforce connector is in preview, and Fivetran still wins on breadth and battle-hardened maturity. But OpenFlow represents something important: the data platform taking responsibility for data movement, not just data storage and compute. It&amp;rsquo;s Snowflake saying &amp;ldquo;we&amp;rsquo;ll handle getting the data in — you focus on making it useful.&amp;rdquo;&lt;/p>
&lt;p>That last part — &lt;em>making it useful&lt;/em> — is still your job. And it&amp;rsquo;s the part that no tool can do for you. The best thing a managed connector gives you isn&amp;rsquo;t fewer lines of code. It&amp;rsquo;s time back. Time to spend on the work that actually requires a human: understanding the business, building the right models, and making sure the data tells an accurate story.&lt;/p>
&lt;p>The next time someone on your team says &amp;ldquo;I&amp;rsquo;ll just build a quick Salesforce integration&amp;rdquo; — send them this article. And the next time someone says &amp;ldquo;AI can just handle the data pipeline&amp;rdquo; — ask them who&amp;rsquo;s going to sit with the stakeholders and figure out what the pipeline should actually deliver.&lt;/p>
&lt;p>That&amp;rsquo;s still you. Make sure you have the time for it.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Cloud Architecture</category><category>Data Engineering</category><category>Snowflake</category><category>OpenFlow</category><category>Salesforce</category><category>API Integration</category><category>Schema Evolution</category><category>Fivetran</category><category>Data Pipelines</category></item><item><title>Your Data Model Isn't Broken, Part II: The Refactoring Playbook</title><link>https://ghostinthedata.info/posts/2026/2026-03-28-your-data-model-isnt-broken-part-2/</link><pubDate>Sat, 28 Mar 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-03-28-your-data-model-isnt-broken-part-2/</guid><author>Chris Hillman</author><description>Strangler Figs, Write-Audit-Publish, and the art of replacing a data warehouse one piece at a time without anyone noticing. The practical sequel to why you shouldn't rebuild from scratch.</description><content:encoded>&lt;p>In [Part I], I made the case that your legacy data model isn&amp;rsquo;t the disaster it looks like. That the strange WHERE clauses, the bridge tables nobody can explain, and the slowly-changing-dimension-within-a-slowly-changing-dimension aren&amp;rsquo;t bugs — they&amp;rsquo;re business rules earned through years of production reality. I argued that big-bang rebuilds fail at alarming rates, that the complexity you&amp;rsquo;re fighting is mostly essential rather than accidental, and that the impulse to &amp;ldquo;start from scratch&amp;rdquo; is driven more by cognitive bias than by engineering judgment.&lt;/p>
&lt;p>A few people messaged me afterward and said, roughly: &amp;ldquo;Okay, I&amp;rsquo;m convinced. But what am I supposed to &lt;em>do&lt;/em> about it?&amp;rdquo;&lt;/p>
&lt;p>Fair question. Let&amp;rsquo;s talk about the playbook.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-vine-that-eats-the-tree">The vine that eats the tree&lt;/h3>
&lt;br>
&lt;p>In 2004, Martin Fowler was visiting a rainforest in Queensland when he noticed something remarkable about the strangler fig trees. These vines start as seeds deposited in the upper branches of a host tree by birds. They grow downward, wrapping around the trunk, eventually establishing their own root system in the soil. Over years — sometimes decades — the fig gradually replaces the host tree. The original tree decays inside the fig&amp;rsquo;s lattice until there&amp;rsquo;s nothing left of it. But at no point during this process does the canopy collapse. The forest continues to function the entire time.&lt;/p>
&lt;p>Fowler looked at those trees and thought: that&amp;rsquo;s how you should replace software systems.&lt;/p>
&lt;p>The Strangler Fig pattern is, in my experience, the single most useful idea in all of software architecture — and it maps onto data warehouse migrations with almost uncomfortable precision. The concept is deceptively simple: instead of replacing the old system all at once, you build the new system around it. You intercept one piece of functionality at a time, route it through the new implementation, verify it works, and move on. The old system slowly shrinks. The new system slowly grows. And at no point does the business lose access to anything.&lt;/p>
&lt;p>Fowler updated his essay on this pattern as recently as August 2024, and his framing is worth internalising. He described the alternative — the big-bang rewrite — and noted that he&amp;rsquo;d watched it fail most of the time. The strangler fig approach, by contrast, gives value steadily and allows you to monitor progress more carefully through frequent releases.&lt;/p>
&lt;p>Now, here&amp;rsquo;s where it gets interesting for data teams specifically. Application systems have clean interfaces — APIs, endpoints, well-defined contracts. You can intercept a request, route it to the new system, and nobody downstream knows the difference. Data systems are messier. Elliott Cordo, who documented migrating three legacy data warehouses using this pattern, identified the core challenge: analytics systems have porous boundaries and poorly defined interfaces with consumers. You don&amp;rsquo;t always know who&amp;rsquo;s querying what, or how.&lt;/p>
&lt;p>Cordo&amp;rsquo;s solution — and this is the variant I&amp;rsquo;ve seen work best — is what he calls the Legacy Façade. You build a new interface that mimics the legacy system&amp;rsquo;s contract. Same table names, same column names, same output format. Behind the façade, you&amp;rsquo;ve plumbed in the new logic. Consumers don&amp;rsquo;t change anything. They don&amp;rsquo;t even need to know a migration is happening. And this, critically, eliminates the organisational bottleneck that kills most migrations: getting twenty different teams to simultaneously update their queries, dashboards, and downstream pipelines to point at the new system.&lt;/p>
&lt;p>Cordo&amp;rsquo;s experience is particularly telling because one of the three warehouses he migrated existed &lt;em>precisely because&lt;/em> a previous attempt to replace one of the others had failed — mainly due to change management. The strangler fig approach bypasses that failure mode entirely by asking consumers to do little or nothing to adopt the new system.&lt;/p>
&lt;p>I think this matters so much: the majority of data warehouse migrations don&amp;rsquo;t fail for technical reasons. They fail because you can&amp;rsquo;t convince the entire organisation to cut over on the same Tuesday. The Legacy Façade removes that dependency. You migrate at your pace, not at the pace of every team that consumes your data.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="stop-publishing-before-youve-checked">Stop publishing before you&amp;rsquo;ve checked&lt;/h3>
&lt;br>
&lt;p>I&amp;rsquo;m continually surprised by how few people have heard of this pattern. When Robin Moffatt polled data engineers on Reddit about Write-Audit-Publish, most hadn&amp;rsquo;t even encountered the term — let alone used it. That&amp;rsquo;s a problem, because WAP solves the single scariest moment in any refactoring effort: the moment you push changed logic to production and hope you didn&amp;rsquo;t break anything.&lt;/p>
&lt;p>Netflix introduced WAP at the DataWorks Summit in 2017, and the concept is elegant in its simplicity. Instead of the typical pipeline flow — transform data, write it to the production table, then maybe run some checks afterward — you split the process into three distinct stages:&lt;/p>
&lt;p>&lt;strong>Write&lt;/strong> to an isolated staging area. Not production. Not anywhere consumers can see it.&lt;/p>
&lt;p>&lt;strong>Audit&lt;/strong> the output against your quality checks. Row counts. Schema validation. Business rule assertions. Whatever gives you confidence.&lt;/p>
&lt;p>&lt;strong>Publish&lt;/strong> only if the audit passes. If it doesn&amp;rsquo;t, production never sees the bad data. Nobody&amp;rsquo;s dashboard breaks. Nobody&amp;rsquo;s quarterly report includes garbage. The corrupted data dies quietly in staging where it belongs.&lt;/p>
&lt;p>The reason this matters for refactoring specifically is that refactoring &lt;em>requires&lt;/em> you to change transformation logic while producing identical output. You&amp;rsquo;re restructuring the internals — breaking a 400-line SQL query into modular CTEs, moving logic from stored procedures into dbt models, replacing hardcoded values with reference table lookups. Each of these changes is supposed to be behaviour-preserving. WAP gives you the safety net to verify that it actually was.&lt;/p>
&lt;p>Julien Hurault described the common anti-pattern — what he calls Write-(Publish)-Audit — where if an error is detected during testing, it&amp;rsquo;s already too late because the corrupted data has already been released to downstream systems. I&amp;rsquo;ve seen this play out more times than I want to admit. Someone refactors a model, the tests pass in the development environment, the change gets deployed, and then a stakeholder notices that revenue is off by 12% in a dashboard three hours later. By that point, the bad data has propagated through six downstream models and someone&amp;rsquo;s already screenshot the dashboard for a presentation.&lt;/p>
&lt;p>WAP prevents this entirely. And with Apache Iceberg now supporting it natively through branch-based isolation, the tooling has finally caught up with the idea. You write to an audit branch, run your validations, and only fast-forward merge to main when everything checks out. It&amp;rsquo;s the data engineering equivalent of a pull request with automated tests — and honestly, it&amp;rsquo;s bizarre that we&amp;rsquo;ve accepted as normal the practice of deploying data transformations straight to production without a verification step in between.&lt;/p>
&lt;p>If you&amp;rsquo;re on Snowflake, the implementation is straightforward using CLONE and SWAP operations. Clone the production table, run your refactored pipeline against the clone, validate the output, and swap the clone into the production role. If validation fails, the clone gets dropped and production is untouched. I&amp;rsquo;ve written about this pattern &lt;a href="https://ghostinthedata.info/blog/write-audit-publish-with-iceberg">in more detail here&lt;/a> — but the key point for this article is simpler: WAP turns refactoring from a high-wire act into a methodical, reversible process.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-archaeology-of-sql">The archaeology of SQL&lt;/h3>
&lt;br>
&lt;p>How does refactoring a data model actually works in practice, because it&amp;rsquo;s less dramatic than people expect. That&amp;rsquo;s sort of the point.&lt;/p>
&lt;p>dbt Labs published a refactoring course that formalises the approach, and having used variants of this workflow on multiple teams, I think they&amp;rsquo;ve got it right. The core philosophy is one sentence: while refactoring, you&amp;rsquo;ll be moving around a lot of logic, but ideally you won&amp;rsquo;t be changing the logic.&lt;/p>
&lt;p>Read that again. You are not improving business rules during a refactor. You are not fixing data quality issues during a refactor. You are not adding new features during a refactor. You are rearranging the existing logic into a better structure while proving, at every step, that the output hasn&amp;rsquo;t changed. The improvements come later, once the structure is clean enough to support them safely.&lt;/p>
&lt;p>The workflow goes something like this. First, you take the legacy SQL — the 800-line stored procedure, the five nested subqueries, the transformation that nobody wants to touch because the last person who edited it left the company — and you port it into a dbt model &lt;em>unchanged&lt;/em>. You don&amp;rsquo;t clean it up. You don&amp;rsquo;t refactor it. You bring it over exactly as it is, warts and all, and you verify that it produces identical output.&lt;/p>
&lt;p>This is the step most people want to skip, and it&amp;rsquo;s the most important one. That ugly SQL is your baseline. It&amp;rsquo;s your source of truth. Until you&amp;rsquo;ve proved your refactored version matches it row-for-row, column-for-column, you don&amp;rsquo;t have a refactor — you have a rewrite wearing a refactor&amp;rsquo;s clothing.&lt;/p>
&lt;p>Once you&amp;rsquo;ve got your baseline, you start decomposing. Extract the source references into dbt sources. Break the monolithic query into CTEs, each one doing a single logical operation. Move shared logic into staging models. Separate business rules from data cleaning from aggregation. At each step — and this is non-negotiable — you use something like dbt&amp;rsquo;s &lt;code>audit_helper&lt;/code> package to compare the output of your refactored model against the original. If the outputs match, you move on. If they don&amp;rsquo;t, you figure out what you changed and fix it before going further.&lt;/p>
&lt;p>Tristan Handy talks about his formative experience at Casper in 2016, where his team refactored all of their existing pipelines and brought them over to dbt in a single week, delivering work at least ten times faster than they would have been able to do otherwise. That speed came not from heroics but from methodology: migrate unchanged, then decompose with verification at every step.&lt;/p>
&lt;p>I&amp;rsquo;ve watched a team at refactor over 200 dbt models with complex logic in three days using &lt;code>audit_helper&lt;/code>. Three days. Not three months. Not the eighteen-month rebuild timeline that some stakeholder approved in a steering committee. Three days, because the tooling verified every change automatically and the team could move fast &lt;em>with confidence&lt;/em>.&lt;/p>
&lt;p>That&amp;rsquo;s the real payoff of refactoring over rebuilding. Not that it&amp;rsquo;s safer — though it is. Not that it preserves business logic — though it does. The payoff is that it&amp;rsquo;s &lt;em>faster&lt;/em>. Faster to start delivering value. Faster to reach a clean architecture. Faster to build the confidence you need to actually make meaningful improvements.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="expand-then-contract">Expand, then contract&lt;/h3>
&lt;br>
&lt;p>There&amp;rsquo;s a pattern for handling breaking schema changes that I don&amp;rsquo;t see data teams use nearly enough, and it deserves more attention. Fowler calls it Parallel Change. Others call it Expand and Contract. The name doesn&amp;rsquo;t matter.&lt;/p>
&lt;p>Imagine you need to rename a column in a fact table that thirty dashboards reference. In a rebuild mindset, you&amp;rsquo;d design the new schema, build it, and then coordinate a cutover where every consumer updates their queries simultaneously. Good luck with that. In my experience, &amp;ldquo;simultaneously&amp;rdquo; means &amp;ldquo;over the course of several painful weeks, with a growing list of things that are broken in the meantime.&amp;rdquo;&lt;/p>
&lt;p>Expand and Contract takes a different approach, in three phases:&lt;/p>
&lt;p>In the &lt;strong>Expand&lt;/strong> phase, you add the new column alongside the old one. Both exist. Both contain the same data. Nothing breaks, because nothing has been removed.&lt;/p>
&lt;p>In the &lt;strong>Migrate&lt;/strong> phase, you update consumers one at a time. Dashboard A switches to the new column. Pipeline B switches. Report C switches. Each migration is independent, low-risk, and reversible. There&amp;rsquo;s no coordination required between teams.&lt;/p>
&lt;p>In the &lt;strong>Contract&lt;/strong> phase — and only after every consumer has migrated — you drop the old column. By this point, nobody is using it, so removing it is a non-event.&lt;/p>
&lt;p>Does this take more deployments? Yes. it might require up to five deployments before the change is fully in effect. But each deployment is safe. Each deployment is reversible. And at no point does anyone lose access to their data because you renamed a column in a way they weren&amp;rsquo;t expecting.&lt;/p>
&lt;p>For data models specifically, Expand and Contract shines when you&amp;rsquo;re restructuring dimensions, splitting fact tables, or changing grain. Add the new structure alongside the old one. Migrate consumers gradually. Remove the old structure once it&amp;rsquo;s no longer referenced. It&amp;rsquo;s boring. It&amp;rsquo;s methodical. It works.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-ship-that-replaced-itself">The ship that replaced itself&lt;/h3>
&lt;br>
&lt;p>If you want a single case study that demonstrates what successful incremental replacement looks like at scale, look at what Slack did with their desktop client in 2019.&lt;/p>
&lt;p>Slack&amp;rsquo;s engineering team faced a classic dilemma. Their desktop application — built on jQuery and a custom framework — had accumulated years of complexity. It was slow. It used too much memory. The architecture made certain improvements difficult or impossible. Every instinct said: rewrite it in React.&lt;/p>
&lt;p>Their engineering blog explicitly acknowledged the risk: running code knows things. Hard-won knowledge gained through billions of hours of cumulative usage and tens of thousands of bug fixes.&lt;/p>
&lt;p>So instead of a big-bang rewrite, they did something much harder and much smarter. Over two years, they replaced every component of the desktop client while shipping continuously. They called it modernising &amp;ldquo;bit by bit&amp;rdquo; — enforcing strict interfaces between existing and modern code, shipping every change to production as it was completed, and never asking users to endure a disruptive cutover.&lt;/p>
&lt;p>The results: 50% memory reduction, 33% load time improvement. Users never experienced a broken release. Had they waited until the entire application was rewritten before releasing it, their users would have had a worse experience for years while the team worked in a vacuum.&lt;/p>
&lt;p>Now, Slack is an application, not a data warehouse. But the principles translate directly. Strict interfaces between old and new code? That&amp;rsquo;s the Legacy Façade. Shipping continuously? That&amp;rsquo;s WAP combined with Expand and Contract. Never asking users to endure a disruption? That&amp;rsquo;s the entire point.&lt;/p>
&lt;p>The ancient Greeks had a thought experiment about this: if you replace every plank of a ship over time, is it still the same ship? Philosophers argue about it. Engineers don&amp;rsquo;t care. The ship still floats. The passengers still get where they&amp;rsquo;re going.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-numbers-for-the-sceptics">The numbers, for the sceptics&lt;/h3>
&lt;br>
&lt;p>I held back on the quantitative evidence in Part I because I wanted to make the argument from experience and conviction. But some people want receipts, so here they are.&lt;/p>
&lt;p>McKinsey and Oxford University studied 5,400 IT projects with initial budgets exceeding $15 million. On average, large projects ran 45% over budget and 7% over time, while delivering 56% less value than predicted. Seventeen percent were &amp;ldquo;black swans&amp;rdquo; — cost overruns exceeding 200% that threatened the company&amp;rsquo;s viability. One retailer abandoned a $1.4 billion IT modernisation, failed on a $600 million follow-up, and filed for bankruptcy.&lt;/p>
&lt;p>The Standish Group has been tracking IT project outcomes since 1994. In their original CHAOS report, only 16.2% of projects succeeded on time, on budget, with specified features. Average cost overrun: 189%. By 2020, the overall success rate had improved to 31% — but here&amp;rsquo;s the statistic that matters most: small projects succeeded approximately 90% of the time. Large projects succeeded less than 10% of the time.&lt;/p>
&lt;p>Read that again. Small projects: 90%. Large projects: under 10%.&lt;/p>
&lt;p>That&amp;rsquo;s not a technology problem. That&amp;rsquo;s a scope problem. And the single most reliable way to reduce scope is to stop rebuilding entire systems and start refactoring them one piece at a time.&lt;/p>
&lt;p>For data systems specifically, Gartner reports that more than 50% of data warehouse projects fail to reach user acceptance. Other analyses put the number closer to 80%. CDInsights reports that 70% of data warehouse modernisation projects exceed budget or fail. Integrate.io found that approximately 80% of data migration projects exceed timelines or budgets, with large-scale projects showing 50% higher failure rates than incremental approaches.&lt;/p>
&lt;p>I don&amp;rsquo;t know how much clearer the data can be. Big-bang rewrites of data platforms fail most of the time. Incremental approaches succeed most of the time. The methodology I&amp;rsquo;ve described in this article — strangler fig, WAP, dbt refactoring, expand and contract — isn&amp;rsquo;t just safer or more philosophically sound. It&amp;rsquo;s what the evidence says actually works.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="refactoring-as-a-practice-not-a-project">Refactoring as a practice, not a project&lt;/h3>
&lt;br>
&lt;p>Refactoring should not be a project. It should not have a start date and an end date and a steering committee and a Gantt chart. Refactoring should be a &lt;em>practice&lt;/em> — something your team does continuously, as a natural part of how you work with your data models.&lt;/p>
&lt;p>Every time you touch a model to add a feature or fix a bug, you leave it a little cleaner than you found it. You extract a hardcoded value into a reference table. You add a test that didn&amp;rsquo;t exist before. You rename a column from &lt;code>col_7&lt;/code> to something a human can understand. You break a 200-line CTE into two smaller, focused ones.&lt;/p>
&lt;p>None of these changes are dramatic. None of them require a business case or a migration plan. They&amp;rsquo;re small, safe, incremental improvements that compound over time. A year from now, the model is cleaner, better-tested, and easier to understand — not because you stopped everything to rebuild it, but because you improved it a little bit every time you were in the neighbourhood.&lt;/p>
&lt;p>This is what Fowler meant when he defined refactoring as changing internal structure without modifying observable behaviour. It&amp;rsquo;s not a phase. It&amp;rsquo;s a discipline. And it&amp;rsquo;s the discipline that separates data teams that thrive from data teams that periodically torch everything and start over every three years, wondering why they never seem to make lasting progress.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-closing-argument">The closing argument&lt;/h3>
&lt;br>
&lt;p>In Part I, I said your data model isn&amp;rsquo;t broken — it&amp;rsquo;s battle-scarred, and those scars are knowledge. In Part II, I&amp;rsquo;ve tried to show you what the alternative to demolition looks like: patient, methodical, verified improvement. Strangler figs instead of bulldozers. Write-Audit-Publish instead of deploy-and-pray. Expand and contract instead of coordinate-and-hope.&lt;/p>
&lt;p>It&amp;rsquo;s not glamorous work. Nobody&amp;rsquo;s going to invite you to give a conference talk about the time you renamed some columns and extracted a few staging models. You won&amp;rsquo;t get a promotion for migrating a legacy fact table so smoothly that nobody noticed it happened.&lt;/p>
&lt;p>But that&amp;rsquo;s exactly the point. The best infrastructure work is invisible. The best migration is the one where the business never had to care. The best data model is the one that quietly absorbed twenty years of business complexity and still answers questions accurately at 7am on a Monday when a Department Head needs numbers for a board meeting.&lt;/p>
&lt;p>I wrote Part I of this series last week. The technology has changed — Netscape to Snowflake, CGI scripts to dbt — but the lesson hasn&amp;rsquo;t. Old code that works is more valuable than new code that doesn&amp;rsquo;t exist yet. Embedded knowledge is harder to create than clean architecture. And the patient, unglamorous work of incremental improvement will always outperform the seductive fantasy of starting fresh.&lt;/p>
&lt;p>Your data model isn&amp;rsquo;t broken. Stop trying to replace it. Start making it better.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Modelling</category><category>Data Engineering</category><category>Refactoring</category><category>Data Warehousing</category><category>dbt</category><category>Snowflake</category><category>Apache Iceberg</category><category>Write-Audit-Publish</category><category>Strangler Fig</category><category>Data Quality</category></item><item><title>You Don't Need Permission to Fix Your Data</title><link>https://ghostinthedata.info/posts/2026/2026-03-21-data-quality-when-your-a-junior/</link><pubDate>Sat, 21 Mar 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-03-21-data-quality-when-your-a-junior/</guid><author>Chris Hillman</author><description>Five battle-tested tactics junior data engineers can use to improve data quality without waiting for authority, backed by real case studies from Airbnb, Google, and Warner Bros. Discovery.</description><content:encoded>&lt;p>Let me tell you about a junior engineer called Sam.&lt;/p>
&lt;p>Sam had been on the team about four months when I noticed something in a pull request. Tucked between two routine model changes was a new &lt;code>schema.yml&lt;/code> entry — five &lt;code>accepted_values&lt;/code> tests on a column called &lt;code>customer_status&lt;/code> that had been silently accumulating fourteen different spellings of &amp;ldquo;active&amp;rdquo; for the better part of a year.&lt;/p>
&lt;p>Nobody asked Sam to do this. It wasn&amp;rsquo;t in a sprint. There was no Jira ticket. Sam had just been working in that part of the warehouse, noticed the mess, and decided to clean it up on the way through.&lt;/p>
&lt;p>I left a comment on the PR: &amp;ldquo;Nice catch.&amp;rdquo; Sam&amp;rsquo;s reply was almost apologetic — &amp;ldquo;Hope it&amp;rsquo;s okay that I added these, I wasn&amp;rsquo;t sure if I was supposed to.&amp;rdquo;&lt;/p>
&lt;p>That response stuck with me. &lt;em>I wasn&amp;rsquo;t sure if I was supposed to.&lt;/em>&lt;/p>
&lt;p>Here was someone who&amp;rsquo;d spotted a real problem, built a real solution, tested it, and shipped it — and their instinct was to wonder whether they had permission. Not whether the fix was correct. Not whether the tests were well-written. Whether they were &lt;em>allowed&lt;/em>.&lt;/p>
&lt;p>I&amp;rsquo;ve thought about that moment a lot since, because it captures something I see in almost every data team I work with. The people closest to the problems — the ones running the queries, staring at the nulls, fielding the Microsoft Teams messages when dashboards look wrong — are often the last to believe they can do anything about it. Not because they lack skill. Because somewhere along the way, they absorbed the idea that fixing things is someone else&amp;rsquo;s job.&lt;/p>
&lt;p>That belief is expensive. And it&amp;rsquo;s wrong.&lt;/p>
&lt;p>I&amp;rsquo;m telling you this because data quality — the real, unglamorous, column-by-column work of making data trustworthy — is one of those things that sounds like a senior engineering problem until you see a four-month-old team member fix something a dozen experienced engineers walked past every day. It doesn&amp;rsquo;t take authority. It takes the willingness to act before you&amp;rsquo;re told to.&lt;/p>
&lt;p>This article is about how to be more like Sam. Five tactics you can start this week — no permission required — backed by real case studies from companies like Airbnb, Google, and Warner Bros. Discovery that figured this out the hard way.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-129-million-reason-your-quality-problem-isnt-too-small-to-fix">The $12.9 million reason your quality problem isn&amp;rsquo;t &amp;ldquo;too small&amp;rdquo; to fix&lt;/h3>
&lt;br>
&lt;p>Before we get into tactics, let&amp;rsquo;s talk about why your &amp;ldquo;small&amp;rdquo; fix matters more than you think.&lt;/p>
&lt;p>Gartner pegs the average annual cost of poor data quality at &lt;strong>$12.9 million per organisation&lt;/strong>. IBM and Harvard Business Review put the aggregate U.S. cost at $3.1 trillion annually. These numbers feel abstract until you realise what they actually represent: knowledge workers spending roughly half their time hunting for data, correcting errors, and searching for confirmatory sources instead of doing their actual jobs.&lt;/p>
&lt;p>Monte Carlo&amp;rsquo;s 2024 State of Data Quality survey found bad data now impacts &lt;strong>31% of company revenue&lt;/strong> on average — up from 26% just two years earlier. Their 2022 survey found data engineers spend &lt;strong>40% of their time&lt;/strong> evaluating or checking data quality. Soda&amp;rsquo;s research put that number even higher: 61% of data engineers spend half or more of their time handling data issues.&lt;/p>
&lt;p>And the human cost? A DataKitchen survey of 600 data engineers found &lt;strong>87% are frequently blamed&lt;/strong> when things go wrong with data. 78% wish their job came with a therapist.&lt;/p>
&lt;p>The implication for you: the problem is so large and so costly that any measurable improvement you create has real dollar value attached to it. That null check you added last Thursday? That&amp;rsquo;s not a minor fix — it&amp;rsquo;s a tiny piece of a multi-million-dollar problem that nobody else bothered to address.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="tactic-1-write-tests-for-your-own-models-without-asking-anyone">Tactic 1: write tests for your own models without asking anyone&lt;/h3>
&lt;br>
&lt;p>The single best case study for this tactic comes from Airbnb, and it didn&amp;rsquo;t start with a VP mandate or an OKR. It started with one engineer.&lt;/p>
&lt;p>Airbnb&amp;rsquo;s engineering blog documents how they went from &amp;ldquo;an engineering culture in which most new code shipped without a single test&amp;rdquo; to one where proposing a change without tests got called out immediately. The transformation wasn&amp;rsquo;t top-down. As the blog explains: &amp;ldquo;One approach is to rule by edict, but given our culture of engineer autonomy this would have been very poorly received. The other approach is to lead by example and build a movement.&amp;rdquo;&lt;/p>
&lt;p>That single champion&amp;rsquo;s playbook was remarkably tactical. They spoke in engineering meetings. They held office hours showing teammates how to write tests. They published a &lt;strong>weekly newsletter highlighting well-written test specs&lt;/strong> — giving public credit to engineers who included tests. They revamped the testing bootcamp for new hires, effectively making each new hire a champion. They improved tooling, reducing CI build times from over an hour to roughly six minutes.&lt;/p>
&lt;p>The result? Airbnb later applied the same grassroots philosophy to data quality, building a DQ Score on a 0–100 scale across their entire warehouse. Their anomaly detection system now prevents quality issues in new pipelines before they reach production. The Minerva metrics platform manages 12,000+ metrics and 4,000+ dimensions with 200+ data producers.&lt;/p>
&lt;p>One engineer. No mandate. A newsletter and some office hours.&lt;/p>
&lt;h4 id="what-this-looks-like-for-you">What this looks like for you&lt;/h4>
&lt;p>If you&amp;rsquo;re working with dbt, you already have the tools. dbt ships with four built-in generic tests — &lt;code>not_null&lt;/code>, &lt;code>unique&lt;/code>, &lt;code>accepted_values&lt;/code>, &lt;code>relationships&lt;/code> — that you define in YAML alongside your model definitions. No framework to build. No proposal to write. Just add a few lines to your &lt;code>schema.yml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">dim_customers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">not_null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">unique&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_status&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">accepted_values&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">values&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;active&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;inactive&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;churned&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;suspended&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">email&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">not_null&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. Next time the pipeline runs, those tests execute automatically. If someone pushes a change that breaks uniqueness on &lt;code>customer_id&lt;/code>, the pipeline catches it before it reaches production.&lt;/p>
&lt;p>The &lt;code>dbt-expectations&lt;/code> package goes further, porting Great Expectations&amp;rsquo; advanced tests into dbt. Unit tests — introduced in dbt v1.8 — let you validate SQL logic with static mock inputs before transformations run. Tests integrate directly into CI/CD, blocking PRs with failures from reaching production.&lt;/p>
&lt;p>Not on dbt? You can still write assertion queries. Here&amp;rsquo;s a completeness check you can run against any SQL database — no framework required:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Quick completeness check: what percentage of each critical
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- field is actually populated?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> total_records,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- How many emails do we actually have?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ROUND(&lt;span style="color:#ae81ff">100&lt;/span>.&lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(email) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>), &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> email_pct,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- How many phone numbers?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ROUND(&lt;span style="color:#ae81ff">100&lt;/span>.&lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(phone) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>), &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> phone_pct,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- How many addresses?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ROUND(&lt;span style="color:#ae81ff">100&lt;/span>.&lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(address_line_1) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>), &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> address_pct,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Flag: are we below the 95% threshold on anything critical?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(email) &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>.&lt;span style="color:#ae81ff">95&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;BELOW THRESHOLD&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;OK&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> email_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main.customers;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Schedule that to run daily. Store the results in a logging table. You&amp;rsquo;ve just built a monitoring system — and you didn&amp;rsquo;t ask anyone&amp;rsquo;s permission.&lt;/p>
&lt;h4 id="the-numbers-back-this-up">The numbers back this up&lt;/h4>
&lt;p>Monte Carlo&amp;rsquo;s 2022 survey found that organisations conducting at least three types of data tests weekly experienced &lt;strong>46 incidents per month&lt;/strong> compared to &lt;strong>61 incidents per month&lt;/strong> for less rigorous testers — a 25% reduction. The Forrester Total Economic Impact study of data observability platforms (April 2025) found 358% ROI, 6,500+ data personnel hours reclaimed annually, and $1.5M+ in avoided lost revenue from reduced data downtime.&lt;/p>
&lt;p>Daniel Beach of Data Engineering Central offers a pragmatic path for getting started: begin with end-to-end tests first when inheriting a pipeline with no tests, then add data quality checks, then unit tests. You don&amp;rsquo;t need to boil the ocean. You need to start.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="tactic-2-document-one-thing-every-time-you-touch-it">Tactic 2: document one thing every time you touch it&lt;/h3>
&lt;br>
&lt;p>The numbers on missing documentation are genuinely staggering. According to DX, developers spend &lt;strong>3–10 hours per week&lt;/strong> searching for information that should be documented. For a 100-person engineering team, that equals 300–1,000 hours weekly — the equivalent of 8–25 full-time engineers doing nothing but hunting for answers. Stack Overflow&amp;rsquo;s developer survey found 62% of developers spend over 30 minutes daily on poorly documented issues. And 38% listed poor documentation among their top reasons for leaving a company.&lt;/p>
&lt;p>One practitioner tracked &amp;ldquo;Time to First Production Commit&amp;rdquo; at &lt;strong>28 days&lt;/strong> for new engineers — versus an industry benchmark of 3–5 days. Engineers on that team spent 30% of their time rediscovering what previous engineers had already learned. 40% of sprint tickets required inter-team clarification, averaging 12 clarifications per ticket.&lt;/p>
&lt;p>This is what documentation debt looks like. Not a gap in a wiki — a tax on every single person who touches the codebase.&lt;/p>
&lt;h4 id="documentation-fridays-dont-work-this-does">&amp;ldquo;Documentation Fridays&amp;rdquo; don&amp;rsquo;t work. This does.&lt;/h4>
&lt;p>Multiple sources confirm that big-bang documentation efforts fail. As one case study puts it: &amp;ldquo;&amp;lsquo;Okay team, let&amp;rsquo;s spend Friday afternoon documenting our processes!&amp;rsquo; Nobody wants to do that. And it doesn&amp;rsquo;t work.&amp;rdquo;&lt;/p>
&lt;p>The successful pattern is document-as-you-go. Every time you encounter an issue, you write it down. Shopify Engineering treats documentation as a product, explicitly encouraging new team members to fill documentation gaps from day one. Their philosophy: &amp;ldquo;The most effective way to encourage others to write is to actually write.&amp;rdquo;&lt;/p>
&lt;p>For data teams specifically, the approach is even simpler. If you&amp;rsquo;re using dbt, column descriptions live in &lt;code>schema.yml&lt;/code> alongside your model definitions. dbt&amp;rsquo;s column-level lineage means passthrough and renamed columns &lt;strong>automatically inherit descriptions from upstream models&lt;/strong>. Write a description once, and it flows downstream through every model that references that column.&lt;/p>
&lt;p>Here&amp;rsquo;s what this looks like in practice:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">dim_customers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &amp;gt;&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> One row per customer. Grain: customer_id.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Source: CRM extract via Fivetran, refreshed daily at 03:00 UTC.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Owner: data-eng-team@company.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Primary key. Sourced from CRM.contacts.id. Unique, never null.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_status&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &amp;gt;&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Current lifecycle status. Valid values: active, inactive, 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> churned, suspended. Updated by CRM webhook on status change.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Known issue: legacy records from pre-2023 may contain 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#39;Active&amp;#39; (capitalised) — normalised in this model.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">lifetime_value&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &amp;gt;&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Cumulative revenue attributed to this customer, in AUD.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Calculated as SUM(order_total) from fct_orders.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Excludes refunded orders. Updated daily.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That &amp;ldquo;Known issue&amp;rdquo; note on &lt;code>customer_status&lt;/code>? That&amp;rsquo;s worth its weight in gold to the next person who touches this model. It took thirty seconds to write and it&amp;rsquo;ll save hours of confusion.&lt;/p>
&lt;p>Microsoft&amp;rsquo;s engineering blogs describe the alternative bluntly: &amp;ldquo;When tribal knowledge holders leave, reverse engineering is the only way to understand what was done.&amp;rdquo; Enboarder&amp;rsquo;s 2025 research found &lt;strong>47% of organisations&lt;/strong> cite institutional knowledge loss as their top offboarding challenge. Atlan&amp;rsquo;s Modern Data Survey found data professionals waste &lt;strong>one full day per week&lt;/strong> simply trying to figure out what data to use.&lt;/p>
&lt;p>You don&amp;rsquo;t need to document the entire warehouse. You need to document the thing you touched today. Tomorrow, document the next thing. In three months, you&amp;rsquo;ll have built something genuinely valuable — and you&amp;rsquo;ll have done it one commit at a time.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="tactic-3-build-a-quality-dashboard-nobody-asked-for">Tactic 3: build a quality dashboard nobody asked for&lt;/h3>
&lt;br>
&lt;p>At Warner Bros. Discovery, the DICE team built data quality management frameworks and created a &amp;ldquo;Data Quality Forum&amp;rdquo; that became a bridge between teams. During the Olympics livestream, they used custom SQL checks to detect missing content metadata — fixing issues before they broke reporting. The forum evolved into what Pam Zirpoli called a &amp;ldquo;cultural flywheel.&amp;rdquo; Patterns surfaced in the forum were incorporated into onboarding, translated into new monitors, and used to refine their priority matrix. Teams shifted from reactive — reacting after dashboards broke — to proactive.&lt;/p>
&lt;p>At Tempus, a data engineer documented how adopting Elementary (an open-source dbt package) transformed their monitoring. Before: &amp;ldquo;test alerting relied on a dashboard in Data Studio built on top of a log sink, and the &amp;lsquo;alerts&amp;rsquo; were a daily email that you actually had to open.&amp;rdquo; After: Microsoft Teams alerts that &amp;ldquo;@-ed a user and continued to do so until they&amp;rsquo;re turned off.&amp;rdquo; The key outcome: tests no longer fail silently.&lt;/p>
&lt;h4 id="the-six-metrics-that-actually-matter">The six metrics that actually matter&lt;/h4>
&lt;p>Drawing from IBM, dbt Labs, Databricks, and Alation, the consensus metrics for data quality monitoring are:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Freshness&lt;/strong>: time since last data update. If your orders table hasn&amp;rsquo;t been updated since yesterday, something&amp;rsquo;s broken.&lt;/li>
&lt;li>&lt;strong>Volume&lt;/strong>: expected versus actual row counts. Did today&amp;rsquo;s load bring in 50,000 rows when you normally get 48,000–52,000? Fine. Did it bring 12? Problem.&lt;/li>
&lt;li>&lt;strong>Schema changes&lt;/strong>: added, deleted, or altered columns. These should never surprise you.&lt;/li>
&lt;li>&lt;strong>Completeness&lt;/strong>: null rates per column. Simple &lt;code>COUNT(*)&lt;/code> vs &lt;code>COUNT(column)&lt;/code> tells you a lot.&lt;/li>
&lt;li>&lt;strong>Distribution shifts&lt;/strong>: statistical changes in data values. If the average order value suddenly doubles, that&amp;rsquo;s worth investigating.&lt;/li>
&lt;li>&lt;strong>Test pass/fail rates&lt;/strong>: the percentage of your quality assertions that are passing. Track this over time.&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s a freshness check you can run right now — no tools, no frameworks, just SQL:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- How stale is each of your critical tables?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Run this daily and store results in a monitoring table
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;fct_orders&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> &lt;span style="color:#66d9ef">table_name&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at) &lt;span style="color:#66d9ef">AS&lt;/span> last_record_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> checked_at,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EXTRACT&lt;/span>(EPOCH &lt;span style="color:#66d9ef">FROM&lt;/span> (&lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at))) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">3600&lt;/span>.&lt;span style="color:#ae81ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> hours_since_update,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at) &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CRITICAL - No data&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at) &lt;span style="color:#f92672">&amp;gt;&lt;/span> INTERVAL &lt;span style="color:#e6db74">&amp;#39;24 hours&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;STALE - Exceeds 24hr SLA&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at) &lt;span style="color:#f92672">&amp;gt;&lt;/span> INTERVAL &lt;span style="color:#e6db74">&amp;#39;12 hours&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;WARNING - Approaching SLA&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FRESH&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> freshness_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> analytics.fct_orders;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Store that in a &lt;code>data_quality_log&lt;/code> table, connect it to Looker Studio or Metabase (both free), and you&amp;rsquo;ve got a monitoring dashboard. That&amp;rsquo;s it. No Kubernetes cluster. No proposal document.&lt;/p>
&lt;h4 id="visibility-changes-behaviour">Visibility changes behaviour&lt;/h4>
&lt;p>Anna Geller documented how sending data quality alerts to a shared Microsoft Teams channel transforms team behaviour. Her core insight: &amp;ldquo;As social creatures, we are more motivated to fix issues if other people can see the effort we put into it.&amp;rdquo; Data owners reply with root cause analysis, add checkmarks, or link tickets — creating a social proof loop that issues are no longer ignored.&lt;/p>
&lt;p>This isn&amp;rsquo;t just anecdotal. Research on the Hawthorne effect shows healthcare hand hygiene compliance increases by &lt;strong>up to 70%&lt;/strong> when workers know they&amp;rsquo;re observed, and employees increase productivity by an average of 13% when activity is monitored. Making data quality visible doesn&amp;rsquo;t just inform people — it changes their behaviour.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="tactic-4-create-a-data-bugs-channel">Tactic 4: create a #data-bugs channel&lt;/h3>
&lt;br>
&lt;p>MIT Sloan Management Review&amp;rsquo;s HelloFresh case study documents one of the best examples of how visibility catalyses organisational change. HelloFresh transitioned through three modes of data quality management. In Mode 1, issues were dealt with reactively by whoever happened to be affected. In Mode 2, a central engineering team handled cleanup but lacked understanding of how data was actually used — &amp;ldquo;data customers grew increasingly frustrated as they continued to spend large fractions of their workdays dealing with data that did not meet their needs.&amp;rdquo;&lt;/p>
&lt;p>The transition to Mode 3 — proactive, collaborative quality management — happened precisely because &amp;ldquo;quality issues became increasingly visible.&amp;rdquo; Not because someone wrote a strategy doc. Not because a VP mandated it. Because the problems became impossible to ignore.&lt;/p>
&lt;p>You can accelerate that transition. Create a Microsoft Teams channel. Call it &lt;code>#data-bugs&lt;/code> or &lt;code>#data-quality&lt;/code> or whatever feels natural. Start posting issues you find — with specifics. Not &amp;ldquo;the data looks wrong&amp;rdquo; but &amp;ldquo;fct_orders has 3,247 rows with null customer_id from the 2026-02-15 load, affecting the weekly revenue dashboard.&amp;rdquo;&lt;/p>
&lt;p>That&amp;rsquo;s it. You&amp;rsquo;ve just created the visibility mechanism that HelloFresh spent years arriving at.&lt;/p>
&lt;h4 id="the-broken-windows-effect-is-real--and-its-been-measured">The broken windows effect is real — and it&amp;rsquo;s been measured&lt;/h4>
&lt;p>The Pragmatic Programmer introduced the &amp;ldquo;broken windows&amp;rdquo; software analogy decades ago: &amp;ldquo;Don&amp;rsquo;t leave &amp;lsquo;broken windows&amp;rsquo; (bad designs, wrong decisions, or poor code) unrepaired. Fix each one as soon as it is discovered.&amp;rdquo;&lt;/p>
&lt;p>This isn&amp;rsquo;t just metaphor anymore. A 2024 arXiv paper by Diomidis Spinellis et al. analysed &lt;strong>2 million code commits&lt;/strong> across &lt;strong>122 projects&lt;/strong> comprising &lt;strong>5.5 million lines of code&lt;/strong>. The key finding: &amp;ldquo;History matters — developers behave differently depending on some aspects of the code quality they encounter.&amp;rdquo; Developers tailor the quality of their commits based on the quality of the file they&amp;rsquo;re committing to. Low quality begets lower quality. High quality attracts higher quality.&lt;/p>
&lt;p>Mat Ryer extends this to everyday engineering: &amp;ldquo;If you work on a project that has flaky tests, then you&amp;rsquo;re more likely to add more flaky tests. If there is a hacky design, you&amp;rsquo;re more likely to hack more in.&amp;rdquo;&lt;/p>
&lt;p>Fixing one visible data quality issue — or even just making it visible — can interrupt the decay spiral.&lt;/p>
&lt;h4 id="why-juniors-dont-speak-up-and-why-thats-exactly-the-problem">Why juniors don&amp;rsquo;t speak up (and why that&amp;rsquo;s exactly the problem)&lt;/h4>
&lt;p>Here&amp;rsquo;s the uncomfortable part. A Culture Shift UK survey found &lt;strong>junior colleagues were twice as likely (54%) as senior leaders (27%)&lt;/strong> to say speaking up about issues is &amp;ldquo;pointless.&amp;rdquo; 37% said speaking up &amp;ldquo;isn&amp;rsquo;t worth the personal risk.&amp;rdquo; A Blind survey found 58% of tech workers experience imposter syndrome.&lt;/p>
&lt;p>Google&amp;rsquo;s Project Aristotle studied 180+ teams over two years and found psychological safety was &amp;ldquo;by far the most important&amp;rdquo; of five dynamics for team effectiveness. Amy Edmondson&amp;rsquo;s foundational research contains a striking finding: while studying hospital teams, she expected high-performing teams to report fewer errors. Instead, &lt;strong>better teams reported MORE errors&lt;/strong>. Her insight: &amp;ldquo;Maybe the good teams don&amp;rsquo;t make more mistakes, maybe they report more.&amp;rdquo;&lt;/p>
&lt;p>This is the paradox. The teams that look like they have the most data quality issues are often the healthiest — because they&amp;rsquo;ve created an environment where problems surface instead of festering.&lt;/p>
&lt;p>Etsy pioneered blameless postmortems under CTO John Allspaw. Engineers send company-wide emails confessing mistakes. Etsy even gives out an annual &amp;ldquo;three-armed sweater&amp;rdquo; award to the employee who made the most surprising error. As a Hadley Wickham–curated Stanford reading list notes, while blameless postmortems are standard in DevOps, they &amp;ldquo;have not yet infiltrated standard data science practices.&amp;rdquo; That&amp;rsquo;s a gap you can help fill.&lt;/p>
&lt;p>Creating a &lt;code>#data-bugs&lt;/code> channel isn&amp;rsquo;t just about tracking issues. It&amp;rsquo;s about establishing the norm that &lt;strong>finding problems is good work&lt;/strong>, not a sign that something&amp;rsquo;s wrong with you.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="tactic-5-ship-an-example-dont-write-a-proposal">Tactic 5: ship an example, don&amp;rsquo;t write a proposal&lt;/h3>
&lt;br>
&lt;p>Marcus Blankenship&amp;rsquo;s article on learned helplessness in software engineering — 96,000+ reads, 900+ Reddit comments — contains a story about a coworker called Milind. Milind wasn&amp;rsquo;t a manager. He didn&amp;rsquo;t have a fancy title. But he was an informal leader through pure action. He asked questions about why particular approaches were taken. He admitted ignorance in group settings. He insisted the team understand root causes before moving on.&lt;/p>
&lt;p>Blankenship&amp;rsquo;s observation: &amp;ldquo;Milind changed our environment by his actions&amp;hellip; He showed me that I had much more power than I thought, if I would only stop expecting to be spoon-fed everything. His actions showed me that &lt;strong>true leadership isn&amp;rsquo;t granted, it&amp;rsquo;s grasped.&lt;/strong>&amp;rdquo;&lt;/p>
&lt;p>A junior engineer on Irina Stanescu&amp;rsquo;s newsletter shared something similar: &amp;ldquo;Even as a junior engineer, I remember influencing my chief architect and brought Terraform into my organisation 7 years ago by simply pointing out a common pain point and how much longer things took without managing our infrastructure in code.&amp;rdquo; They added: &amp;ldquo;Being able to influence without authority is a very underrated skill, and I believe this is what helped me get promoted to senior engineer even more than my technical skills.&amp;rdquo;&lt;/p>
&lt;p>The pattern is consistent. Don&amp;rsquo;t propose a data quality framework. Don&amp;rsquo;t write a 15-page RFC. Build the thing. Make it work. Show people.&lt;/p>
&lt;h4 id="why-working-examples-spread-faster-than-proposals">Why working examples spread faster than proposals&lt;/h4>
&lt;p>Everett Rogers&amp;rsquo; Diffusion of Innovations theory identifies five characteristics that determine how fast an idea spreads: relative advantage, compatibility, complexity (lower is better), &lt;strong>trialability&lt;/strong>, and &lt;strong>observability&lt;/strong>. A working example maximises both trialability and observability — the two factors most under your control as a junior engineer.&lt;/p>
&lt;p>A proposal says &amp;ldquo;we should do this.&amp;rdquo; A working example says &amp;ldquo;look, I already did this, and here&amp;rsquo;s what happened.&amp;rdquo; One requires people to imagine the benefit. The other puts the benefit directly in front of them.&lt;/p>
&lt;p>Addy Osmani, after 14 years at Google, captured the complementary lesson: &amp;ldquo;Early in my career, I believed great work would speak for itself. I was wrong. Code sits silently in a repository.&amp;rdquo; You need to build the example &lt;em>and&lt;/em> make its impact visible. Post in Microsoft Teams. Show the before and after. Quantify what changed.&lt;/p>
&lt;h4 id="the-junior-advantage-nobody-talks-about">The junior advantage nobody talks about&lt;/h4>
&lt;p>Ant Weiss&amp;rsquo;s widely-read article on organisational learned helplessness identifies common symptoms: &amp;ldquo;This is how we were told to work,&amp;rdquo; &amp;ldquo;These are the limitations of the system,&amp;rdquo; &amp;ldquo;We don&amp;rsquo;t have the resources.&amp;rdquo; His core observation: &amp;ldquo;No engineer starts their career willing to be unproductive, pessimistic and stressed out. But the companies they work at teach them to.&amp;rdquo;&lt;/p>
&lt;p>The cure, drawn from Seligman&amp;rsquo;s original research, is critical: &amp;ldquo;Threats, rewards, and observed demonstrations had no effect on the &amp;lsquo;helpless&amp;rsquo; dogs. The dogs had to be physically moved through the escape action at least twice before they would do it themselves.&amp;rdquo; You can&amp;rsquo;t just tell engineers things can be better. You have to walk them through the experience of making something better.&lt;/p>
&lt;p>Here&amp;rsquo;s the part that should give you confidence. JazzTeam reports a remarkable finding: &amp;ldquo;To prove that any problem can be solved, our team many times gave a task to Junior and even Intern Engineers who did not have psychological fears and tunnel thinking.&amp;rdquo; Juniors, unencumbered by years of &amp;ldquo;that&amp;rsquo;s impossible&amp;rdquo; conditioning, were able to tackle problems seniors had declared unsolvable.&lt;/p>
&lt;p>Your fresh eyes aren&amp;rsquo;t a weakness. They&amp;rsquo;re an asset. You haven&amp;rsquo;t yet learned that &amp;ldquo;it can&amp;rsquo;t be done.&amp;rdquo; So go do it.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="putting-it-all-together">Putting it all together&lt;/h3>
&lt;br>
&lt;p>None of these tactics require a committee meeting. None require architectural approval. None require you to be senior.&lt;/p>
&lt;p>Here&amp;rsquo;s what they do require: the willingness to act before you&amp;rsquo;re told to.&lt;/p>
&lt;p>Write a test this week. Add a column description tomorrow. Build a freshness check and schedule it. Create a Slack channel and post the first issue. Turn one of your quality checks into a reusable example your team can copy.&lt;/p>
&lt;p>DORA&amp;rsquo;s research — surveying 39,000+ professionals — finds that with focused effort and the right tooling, teams typically see measurable improvements within &lt;strong>1–3 months&lt;/strong>. A GitHub engineer who went from junior to mid-level in 2.5 years described the compound effect: &amp;ldquo;If I got stuck on some undocumented functionality, I made sure to update the docs and let the team know. Tackling a tricky bug requiring cross-team collaboration? I&amp;rsquo;d summarise everything we discovered so that it would be easier for others later. Post about it in Microsoft Teams and highlight its impact.&amp;rdquo;&lt;/p>
&lt;p>Kevin Workman, a former Google engineer, captured the mindset that makes all of this work: &amp;ldquo;Realising that I had agency and even a responsibility to advocate for my ideas is one of the most important lessons I learned on my journey to becoming a &amp;lsquo;senior&amp;rsquo; software engineer&amp;hellip; Some of the most interesting and most successful moments of my career have come from advocating for changes I thought nobody would agree to.&amp;rdquo;&lt;/p>
&lt;p>I think about Sam sometimes — that four-month-old team member whose instinct was to apologise for fixing something. The tests Sam added that day caught three data quality issues in the following month. Other engineers started adding their own tests alongside model changes. Six months later, the team had 200+ automated quality checks running on every pipeline, and the weekly &amp;ldquo;data looks wrong&amp;rdquo; Microsoft Teams messages had dropped to nearly zero.&lt;/p>
&lt;p>Sam never got a mandate. Never wrote a proposal. Never waited for Q3.&lt;/p>
&lt;p>The data quality problem in your organisation is a $12.9 million issue wearing a &amp;ldquo;we&amp;rsquo;ll get to it in Q3&amp;rdquo; disguise. And you — yes, you, the person who&amp;rsquo;s been here five months and noticed fourteen spellings of &amp;ldquo;active&amp;rdquo; — are exactly the right person to start fixing it.&lt;/p>
&lt;p>Not because someone gave you permission. Because the data doesn&amp;rsquo;t care about your title.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Quality</category><category>Career Development</category><category>Data Quality</category><category>dbt</category><category>SQL</category><category>Testing</category><category>Documentation</category><category>Junior Engineer</category><category>Career Growth</category><category>Psychological Safety</category></item><item><title>Your Friends Will Be There for You. Your Work Won't.</title><link>https://ghostinthedata.info/posts/2026/2026-03-18-friendship/</link><pubDate>Wed, 18 Mar 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-03-18-friendship/</guid><author>Chris Hillman</author><description>The longest study of adult happiness found relationships matter more than career achievement. Yet most of us keep cancelling on our friends. Here's what that costs — and what being a good friend actually looks like.</description><content:encoded>&lt;p>There&amp;rsquo;s a table in a rented house in Queenscliff — nothing fancy, just whatever furniture came with the place — with a hand-drawn map spread across it, dice scattered at the edges, and snacks slowly migrating toward the centre of the board.&lt;/p>
&lt;p>Once a year, Steve, one of our friends, organises for us to make a trip down to Queenscliff to play tabletop RPGs for a weekend. More recently, we&amp;rsquo;ve started playing monthly or fortnightly at home too. Nothing glamorous about any of it. We&amp;rsquo;re not Glass Cannon Network or Critical Role — no cameras, no production values, no audience watching us roleplay with solemn gravity.&lt;/p>
&lt;p>We&amp;rsquo;re just a bunch of friends. Laughing at the missed sword swing. Losing our minds when someone makes a completely unreasonable jump and somehow lands the killing blow on a copper golem with a natural twenty. Rolling dice and arguing about the rules and eating too much while the world outside keeps spinning without us.&lt;/p>
&lt;p>I&amp;rsquo;ve been thinking about why it matters so much to me.&lt;/p>
&lt;p>It&amp;rsquo;s not the game, really — or at least, the game isn&amp;rsquo;t the point. It&amp;rsquo;s what the game creates. A ritual. An excuse to sit in the same room for a weekend and actually be present with people I care about. A context where you ask &amp;ldquo;how are you going?&amp;rdquo; and actually mean it, and stay long enough to hear the real answer. The dragon we&amp;rsquo;re fighting is almost beside the point. What matters is being there for each other.&lt;/p>
&lt;p>When you&amp;rsquo;re up at 2am resolving a critical data pipeline failure, your employer will not remember that next year. They might not remember it next month. The organisation absorbs it, thanks you if you&amp;rsquo;re lucky, and moves on to the next incident.&lt;/p>
&lt;p>But fighting dragons with your friends — showing up for a weekend in Queenscliff year after year — those become stories. Stories that become memories. Memories that become the fabric of who you are to each other, and who they are to you.&lt;/p>
&lt;p>Your friends will be there for you. Your work won&amp;rsquo;t.&lt;/p>
&lt;p>And yet.&lt;/p>
&lt;p>When was the last time someone you loved lost someone, and you climbed into bed next to them — not to fix it, not to say the right thing, just to be there in the dark with them? When something hard was happening in your own life, did you pick up the phone and call a friend? Or did the thought cross your mind — &amp;ldquo;they&amp;rsquo;re busy, they don&amp;rsquo;t have time to listen to me&amp;rdquo; — and you put the phone down and carried it alone?&lt;/p>
&lt;p>Do you have a friend you&amp;rsquo;d call when you got the promotion — someone who&amp;rsquo;d hear the news and mean it when they say &amp;ldquo;that&amp;rsquo;s great&amp;rdquo;? Not quietly threatened by it. Not performing enthusiasm. Just genuinely proud of you, because your win is their win. That kind of friend is rarer than it should be.&lt;/p>
&lt;p>Those moments. That&amp;rsquo;s what friendship actually is. Not the catch-up drinks you schedule six weeks out and cancel twice. The showing up in the hard moments. The willingness to burden someone with your struggle, and the willingness to carry theirs.&lt;/p>
&lt;p>We don&amp;rsquo;t build trust by offering help. We build trust by asking for it. The act of calling someone when things are falling apart — not suffering quietly, not performing fine — is the thing that makes the friendship real on both sides. It&amp;rsquo;s not a burden. For the right person, it&amp;rsquo;s an honour.&lt;/p>
&lt;p>This is something I&amp;rsquo;m still learning. I want to talk about what it actually means to be a good friend — because I think most of us, if we&amp;rsquo;re honest, have let that muscle go slack.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="are-you-actually-a-good-friend">Are You Actually a Good Friend?&lt;/h3>
&lt;br>
&lt;p>Most people, if you ask them directly, will say yes. Of course they&amp;rsquo;re a good friend. They&amp;rsquo;re there for the people they care about.&lt;/p>
&lt;p>But ask yourself: do you call your friends on their birthday — actually call, sing happy birthday — or do you post on their Facebook because you saw the notification and everyone else was doing it? When a friend is going through something hard, do you show up? Not message. Show up. Do you say &amp;ldquo;I love you&amp;rdquo; to the people in your life that you love?&lt;/p>
&lt;p>Most of us, if we&amp;rsquo;re being honest, have outsourced friendship to the low-effort channel. The emoji response. The &amp;ldquo;we should catch up soon!&amp;rdquo; comment thread that leads nowhere. We maintain the social graph without maintaining the relationships.&lt;/p>
&lt;p>But when you&amp;rsquo;re in a dark place, who do you call? And more confronting — who would call you?&lt;/p>
&lt;p>The answer to that second question is the real measure. Friendship isn&amp;rsquo;t what you feel for someone. It&amp;rsquo;s what you demonstrate over time. It&amp;rsquo;s the accumulated weight of showing up — for the birthday dinner that falls on a work night, for the hospital waiting room at 7am, for the phone call that starts &amp;ldquo;I just need to talk to someone&amp;rdquo; at an inconvenient hour.&lt;/p>
&lt;p>Those moments don&amp;rsquo;t happen automatically. They have to be built. And they have to be built before you need them, not when you&amp;rsquo;re reaching for them in a crisis.&lt;/p>
&lt;p>When our daughter Natasha was born, she wouldn&amp;rsquo;t latch on, and breastfeeding wasn&amp;rsquo;t working. Lena and I tried everything, exhausted and running on no sleep, seriously weighing up whether to just switch to formula. At some point we gave up trying to solve it ourselves and called our friends Tom and Rachel. We ended up talking for somewhere between four and six hours straight. We didn&amp;rsquo;t need advice, really — we needed to not be alone with it. And something about that phone call, about calming down, settled Natasha too. By the end of it she was feeding. I still think about that night. We&amp;rsquo;d been treating a human problem like a technical problem, trying to fix it in isolation, when all we&amp;rsquo;d actually needed was to pick up the phone.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-women-already-know">What Women Already Know&lt;/h3>
&lt;br>
&lt;p>Women are better at friendship than men. Not universally, not as a stereotype — but as a pattern that shows up consistently enough to be worth talking about honestly.&lt;/p>
&lt;p>The research on women&amp;rsquo;s friendship patterns tells part of the story. Women&amp;rsquo;s friendships tend to orient toward direct emotional disclosure — what psychologists call face-to-face friendship. Personal sharing, vulnerability, actual conversation about what&amp;rsquo;s happening in your life. Men&amp;rsquo;s friendships tend to be shoulder-to-shoulder — bonding through shared activity, talking while doing something else together. Neither mode is better. But when the activity disappears — when the sport stops, when the shared project ends, when the workplace changes — shoulder-to-shoulder friendship has nothing to stand on. Face-to-face friendship survives the context change because the context was never the point.&lt;/p>
&lt;p>What women understand, and what most men have to learn later and harder, is that the friendship is the thing. Not the activity. The person.&lt;/p>
&lt;p>The good news is that the shoulder-to-shoulder model doesn&amp;rsquo;t have to disappear. The Queenscliff table is shoulder-to-shoulder. The hack is building ritual around it — making the activity a recurring container for the friendship, rather than the friendship being a side effect of the activity. When you have the ritual, the friendship survives when everything else changes.&lt;/p>
&lt;p>I saw this firsthand when I was working in India. Some colleagues (Sarasa, Nidhi, Abhilash, Deepak) took me out to play badminton — I&amp;rsquo;d never played before, and I was genuinely terrible. But what struck me wasn&amp;rsquo;t the sport. It was that this group did it every week, without fail. They weren&amp;rsquo;t there to compete or keep score. They were there to be together, to unwind, to laugh at each other and at themselves. What I remember is leaving that court feeling like I&amp;rsquo;d glimpsed something. These people had built a ritual, and the ritual had built them.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-the-harvard-longevity-study-actually-found">What the Harvard Longevity Study Actually Found&lt;/h3>
&lt;br>
&lt;p>Harvard physician Arlie Bock began following the lives of a few hundred Harvard sophomores. The study is now 85 years old, has followed over 1,300 people including the children of original participants, and is the longest-running study of adult development in history.&lt;/p>
&lt;p>The fourth director of the study, psychiatrist Robert Waldinger, gave what became one of the ten most-watched TED talks ever. And the headline finding, distilled from nearly nine decades of data, is deceptively simple:&lt;/p>
&lt;p>&lt;strong>Good relationships lead to health and happiness. Not wealth. Not professional achievement. Not optimised nutrition or fitness metrics. Relationships.&lt;/strong>&lt;/p>
&lt;p>Waldinger and his co-author Marc Schulz published the full findings in &lt;em>The Good Life&lt;/em> in 2023. Two things from that research hit particularly hard.&lt;/p>
&lt;p>First: relationship satisfaction at age 50 was a better predictor of physical health at 80 than cholesterol levels. That&amp;rsquo;s the kind of finding that should make every analytically-minded person stop and recalibrate. We spend enormous energy optimising for things we can measure while ignoring the metric that turns out to be most predictive of how we age and how long we live.&lt;/p>
&lt;p>Second: when participants reached their 80s, their biggest regret was almost universally the same. Too much time at work. Not enough time with the people they loved. And their proudest achievements were almost entirely relational — being a good parent, a good friend, a good partner, a good mentor.&lt;/p>
&lt;p>George Vaillant, who directed the study for over three decades, summarised it: &amp;ldquo;When the study began, nobody cared about empathy or attachment. But the key to healthy aging is relationships, relationships, relationships.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-we-actually-traded">What We Actually Traded&lt;/h3>
&lt;br>
&lt;p>Reflecting on how high achievers use the word &amp;ldquo;sacrifice&amp;rdquo;. When we say we&amp;rsquo;ve sacrificed something for our career, we shouldn&amp;rsquo;t be afraid to put a name to who that sacrifice was. Because often it was the people in our lives that we call friends.&lt;/p>
&lt;p>Put a name to it.&lt;/p>
&lt;p>We&amp;rsquo;re not sacrificing some abstract concept. We&amp;rsquo;re sacrificing specific people. Former colleagues whose numbers we keep but never dial. School friends who live in the same city and see us once a year if they&amp;rsquo;re lucky. The people who would drop everything if we called — but who we don&amp;rsquo;t call, because we&amp;rsquo;re heads-down on the next deliverable.&lt;/p>
&lt;p>So many of us have cancelled on friends because a meeting came up, telling ourselves they&amp;rsquo;ll understand. Yet the reverse almost never happens — we wouldn&amp;rsquo;t reschedule a meeting for a friend. We&amp;rsquo;ve quietly trained ourselves to treat friendship as the flexible commitment, the one that can move.&lt;/p>
&lt;p>And the friendship debt is unlike technical debt — you can&amp;rsquo;t see it slowing you down, it doesn&amp;rsquo;t show up in velocity metrics or retrospectives. It just quietly accumulates until something breaks. The incident you pushed through alone. The difficult conversation at home that had nowhere to go because there were no friends to process it with. The creeping sense that despite being deeply capable and professionally respected, you&amp;rsquo;re not quite sure who you&amp;rsquo;d call.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="ubuntu-a-different-operating-system">Ubuntu: A Different Operating System&lt;/h3>
&lt;br>
&lt;p>There&amp;rsquo;s a Zulu/Nguni philosophy called Ubuntu. The phrase &amp;ldquo;Umuntu ngumuntu ngabantu&amp;rdquo; translates roughly as &amp;ldquo;a person is a person through other people.&amp;rdquo; You&amp;rsquo;ll also hear it rendered as &amp;ldquo;I am because we are.&amp;rdquo;&lt;/p>
&lt;p>Desmond Tutu described Ubuntu this way: &amp;ldquo;In our African worldview, we need other human beings for us to learn how to be human. For none of us comes fully formed into the world. &lt;strong>The solitary human being is a contradiction in terms.&lt;/strong>&amp;rdquo;&lt;/p>
&lt;p>He contrasted it explicitly with Descartes: &amp;ldquo;It is not &amp;lsquo;I think therefore I am.&amp;rsquo; It says rather: &amp;lsquo;I am human because I belong. I participate. I share.&amp;rsquo;&amp;rdquo;&lt;/p>
&lt;p>Most of the professional identity we build in data engineering runs on Descartes. I architect, therefore I am. I optimise the pipeline, therefore I am. I resolved the incident, therefore I am. We define ourselves by what we produce, alone, with headphones on, in a flow state that the rest of the world is just interrupting.&lt;/p>
&lt;p>Ubuntu is a different operating system. One where your identity is constituted by your relationships — where the question &amp;ldquo;who are you?&amp;rdquo; is answered not by your job title or your GitHub commit history, but by who you show up for and who shows up for you.&lt;/p>
&lt;p>The longest-running study of adult development in history arrived at the same conclusion Tutu was describing. Every major piece of research on social connection and mortality found it too. The architecture most of us are running on isn&amp;rsquo;t wrong in a subtle way. It&amp;rsquo;s wrong in the foundational way.&lt;/p>
&lt;p>Mark Shuttleworth — South African entrepreneur — named the Ubuntu Linux operating system after this philosophy in 2004, explicitly to emphasise community, open-source contribution, and the idea that the work is better when people build it together. Engineers understood that intuitively, because it maps to how the best software actually gets built. It also maps, as it turns out, to how the best lives get built.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-dragon-will-be-there-next-year">The Dragon Will Be There Next Year&lt;/h3>
&lt;br>
&lt;p>There&amp;rsquo;s a table in a rented house in Queenscliff, with a hand-drawn map on it and dice scattered at the edges.&lt;/p>
&lt;p>Every year, we make the trip. We gather around a table and roll dice and laugh at the missed sword swing and the impossible jump. The dragons change. The characters level up. The rules get argued over and amended and house-ruled into something nobody outside the group would recognise.&lt;/p>
&lt;p>But the people are constant. And the ritual is the point.&lt;/p>
&lt;p>It is more amazing to have an extraordinary experience with someone than by yourself. You can go somewhere alone and say &amp;ldquo;look what I did&amp;rdquo; — versus &amp;ldquo;do you remember that time we did that?&amp;rdquo;&lt;/p>
&lt;p>The 2am pipeline failure you resolved will be forgotten. By you. By your employer. By the Jira ticket that gets closed and archived.&lt;/p>
&lt;p>The dragon you fought with your friends — the copper golem that went down to a natural twenty on an impossible jump — that becomes a story. The story becomes a memory. The memory becomes part of who you are to each other and who they are to you.&lt;/p>
&lt;p>That&amp;rsquo;s not sentimentality. That&amp;rsquo;s the mechanism by which good lives actually get built. Eighty-five years of longitudinal data says so.&lt;/p>
&lt;p>Researcher Robin Dunbar found that building a close friendship takes roughly 200 hours of time together. Those hours don&amp;rsquo;t come from nowhere. They come from showing up, again and again, at the table. From protecting that time the same way you&amp;rsquo;d protect a critical production window. From understanding that the maintenance cost of friendship is the highest-ROI investment available to you as a human being.&lt;/p>
&lt;p>Your work will absorb your best years and move on when it&amp;rsquo;s convenient. Your friends will still be at the table.&lt;/p>
&lt;p>Put a name to what you&amp;rsquo;re protecting.&lt;/p>
&lt;p>Then protect it.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Leadership</category><category>Career Development</category><category>Personal Development</category><category>Leadership</category><category>Career Development</category><category>Mental Health</category><category>Burnout</category><category>Wellbeing</category><category>Friendship</category><category>Work-Life Balance</category></item><item><title>Your Data Model Isn't Broken, Part I: Why Refactoring Beats Rebuilding</title><link>https://ghostinthedata.info/posts/2026/2026-03-14-your-data-model-isnt-broken-part-1/</link><pubDate>Sat, 14 Mar 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-03-14-your-data-model-isnt-broken-part-1/</guid><author>Chris Hillman</author><description>That fact table with 200 columns? Those bridge tables nobody understands? They're not bugs — they're reality encoded. Why the 'let's rebuild' instinct destroys more data teams than technical debt ever will.</description><content:encoded>&lt;p>In the early 2000&amp;rsquo;s - Netscape&amp;rsquo;s decision to rewrite their browser from scratch was the single worst strategic mistake a software company could make.&lt;/p>
&lt;/br>
&lt;p>At the time, Netscape was &lt;em>winning&lt;/em>. They had the dominant browser. They had market share. They had momentum. And then they decided the codebase was too messy, too tangled, too hard to work with — so they threw it all away and started over. Navigator 4.0 became the foundation for a rewrite that would eventually ship as version 6.0. There was no 5.0. Three years of development. No shipping product. And while Netscape&amp;rsquo;s engineers were busy building their beautiful new browser in a vacuum, Internet Explorer ate their lunch, their dinner, and most of their market share.&lt;/p>
&lt;p>It&amp;rsquo;s haunted me ever since: old code isn&amp;rsquo;t ugly because it&amp;rsquo;s bad. Old code is ugly because it &lt;em>works&lt;/em>. Every strange condition, every seemingly redundant check, every patch that makes a new developer wince — those are battle scars. Each one represents a bug that took weeks to find in production, a customer workflow nobody anticipated, or an edge case that only surfaces on the third Tuesday of months ending in &amp;ldquo;R.&amp;rdquo;&lt;/p>
&lt;p>I think about that every time I hear a data team say &amp;ldquo;let&amp;rsquo;s just rebuild it.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-sentence-that-starts-every-failed-data-project">The sentence that starts every failed data project&lt;/h3>
&lt;br>
&lt;p>I&amp;rsquo;ve been on data teams long enough to recognise the pattern. It always starts the same way. Someone — usually someone new, often someone senior — opens a dimensional model they didn&amp;rsquo;t build, scrolls through a few hundred lines of transformation logic, and says the words that should make every data leader&amp;rsquo;s blood run cold:&lt;/p>
&lt;p>&amp;ldquo;This is a mess. We need to start from scratch.&amp;rdquo;&lt;/p>
&lt;p>And look, I get it. I&amp;rsquo;ve felt that impulse myself, and likely have said the words myself. You open a fact table with 200 columns. You find bridge tables that reference other bridge tables. You discover a slowly-changing dimension nested inside another slowly-changing dimension, and you think: who built this? What were they &lt;em>thinking&lt;/em>?&lt;/p>
&lt;p>But here&amp;rsquo;s what I&amp;rsquo;ve learned the hard way, across multiple teams and more warehouse migrations than I care to count: they were thinking about the business. That bizarre WHERE clause filtering out both &amp;ldquo;Unknown&amp;rdquo; and &amp;ldquo;unknown&amp;rdquo;? That&amp;rsquo;s not sloppy code. That&amp;rsquo;s a case-sensitivity bug someone found in production data from a source system that nobody controlled. The seemingly redundant join that adds three seconds to your query? It handles a quarterly reconciliation edge case that cost the finance team two days of manual work before someone encoded the fix.&lt;/p>
&lt;p>That fact table with 200 columns isn&amp;rsquo;t a design failure. It&amp;rsquo;s an accurate representation of a business that has 200 things it needs to measure. The &amp;ldquo;clean&amp;rdquo; replacement model will eventually have 200 columns too — they&amp;rsquo;ll just have different names and it&amp;rsquo;ll take you eighteen months to figure out why you need them all.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-data-teams-keep-forgetting">What data teams keep forgetting&lt;/h3>
&lt;br>
&lt;p>This isn&amp;rsquo;t about browsers or even about code quality. It was about &lt;em>knowledge&lt;/em>. When you throw away a codebase and start fresh, you&amp;rsquo;re not just discarding syntax. You&amp;rsquo;re discarding years of accumulated understanding about how the real world actually behaves — understanding that was earned through production incidents, user complaints, and painful debugging sessions.&lt;/p>
&lt;p>Every collected bug fix in that old code represents something learned. Each fix might be just one line, a couple of characters even, but a lot of work and time went into figuring out those two characters were needed. And that knowledge — the &lt;em>why&lt;/em> behind the fix — almost never makes it into documentation. It lives in the code itself, or it lives nowhere.&lt;/p>
&lt;p>This is doubly true for data systems. A software application has unit tests, integration tests, user acceptance testing. A data model has&amp;hellip; what exactly? Row counts? Spot checks? A business user who eyeballs the dashboard and says &amp;ldquo;yeah, that looks about right&amp;rdquo;? The knowledge embedded in a mature data warehouse is far more fragile than application code, because the testing infrastructure around it is almost always weaker. Throw it away and you&amp;rsquo;re not just rebuilding a codebase — you&amp;rsquo;re rebuilding an institutional memory that was never written down in the first place.&lt;/p>
&lt;p>Fred Brooks saw this coming fifty years ago. His &amp;ldquo;Second System Effect&amp;rdquo; from &lt;em>The Mythical Man-Month&lt;/em> describes what happens when an engineer builds their second version of something: they over-design it. All the features they wisely deferred from the first version, all the architectural improvements they dreamed about, all the &amp;ldquo;if only we&amp;rsquo;d done it this way&amp;rdquo; ideas — they dump everything into the replacement. Brooks noted that a designer&amp;rsquo;s first system tends to be spare and clean because they know their limitations. The second system becomes a dumping ground for ambition.&lt;/p>
&lt;p>Sound familiar? Every data warehouse rebuild I&amp;rsquo;ve witnessed follows this arc. The team doesn&amp;rsquo;t just want to replicate what exists — they want to add a semantic layer, implement a medallion architecture, introduce data contracts, add real-time streaming, switch to Data Vault, build a self-serve analytics platform, and migrate to a new cloud provider. All at once. In a single initiative.&lt;/p>
&lt;p>And then they wonder why, two years later, they&amp;rsquo;ve delivered nothing and the business is still running reports off the &amp;ldquo;legacy&amp;rdquo; warehouse that was supposed to be decommissioned eighteen months ago.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-teradata-to-snowflake-reality-check">The Teradata-to-Snowflake reality check&lt;/h3>
&lt;br>
&lt;p>If you want to see the rebuild-vs-refactor debate play out in real time, watch a Teradata-to-Snowflake migration. The pattern is remarkably consistent.&lt;/p>
&lt;p>The pitch is compelling: Snowflake is cheaper, scales elastically, separates compute from storage, and runs standard SQL. Moving from Teradata should be straightforward — convert the SQL, move the data, validate the results. Easy, right?&lt;/p>
&lt;p>Roland Wenzlofsky, a Snowflake Solutions Architect, wrote about this in early 2026 and his observations line up perfectly with what I&amp;rsquo;ve seen. Most organisations approach these migrations as a translation exercise. The technical migration succeeds. Then the first quarterly cloud bill arrives at double the projected budget. Dashboards that loaded in seconds now queue for twenty minutes during batch windows. Pipelines that ran in 45 minutes on Teradata take five hours on Snowflake.&lt;/p>
&lt;p>The problem isn&amp;rsquo;t Snowflake. The problem is that Teradata professionals carry assumptions into Snowflake that aren&amp;rsquo;t just incomplete — they&amp;rsquo;re actively counterproductive. Distribution keys, join strategies, indexing patterns — all the hard-won optimisation knowledge from Teradata becomes a liability in a platform built on fundamentally different architecture.&lt;/p>
&lt;p>One of the largest Teradata-to-Snowflake migrations in North America involved 1.5 petabytes across 600 databases and roughly 45,000 objects. Before they could even begin, the team had to inventory every orphan object and document every behavioural difference between platforms. That&amp;rsquo;s not a &amp;ldquo;lift and shift.&amp;rdquo; That&amp;rsquo;s an archaeological dig.&lt;/p>
&lt;p>But here&amp;rsquo;s what I find most telling: even when the migration succeeds technically, the data quality problems survive the move perfectly intact. William Flaiz documented a healthcare organisation that spent $1.8 million migrating from Siebel to Salesforce. Not a single record lost. Zero downtime. Perfect data mapping. Post-migration, the sales team still couldn&amp;rsquo;t run accurate pipeline reports. The &amp;ldquo;opportunity stage&amp;rdquo; field contained 89 different values for what should have been six standard stages — including 1,247 records with the typo &amp;ldquo;Closeing Soon.&amp;rdquo; That typo migrated perfectly to the new $2.3 million platform. As Flaiz put it: when performance is bad, we assume the technology is the limiting factor. Data quality problems are messier. They implicate people, processes, training gaps, and years of accumulated shortcuts.&lt;/p>
&lt;p>New platform, same chaos. Because the platform was never the problem.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="chestertons-fence-or-why-you-shouldnt-delete-that-where-clause">Chesterton&amp;rsquo;s Fence, or: why you shouldn&amp;rsquo;t delete that WHERE clause&lt;/h3>
&lt;br>
&lt;p>There&amp;rsquo;s a principle in philosophy that every data engineer should have tattooed somewhere visible. G.K. Chesterton wrote it in 1929, and it goes roughly like this: if you come across a fence in the middle of a field and can&amp;rsquo;t see what purpose it serves, don&amp;rsquo;t tear it down. Go away and figure out why someone built it. Once you understand the reason, &lt;em>then&lt;/em> you can decide whether it still needs to be there.&lt;/p>
&lt;p>In data engineering, the fences are everywhere. That &lt;code>WHERE status NOT IN ('Unknown', 'unknown', 'UNKNOWN')&lt;/code> clause. That filter excluding records from a specific date range in 2019. The join to a reference table that only has 12 rows and hasn&amp;rsquo;t been updated in three years. They all look pointless until you remove one and discover that the finance reconciliation breaks, or that a regulatory report starts including test transactions that were supposed to be filtered out, or that a dashboard starts showing a revenue spike from a data migration artifact that happened four years ago.&lt;/p>
&lt;p>Hyrum Wright — formerly at Google, now at Adobe — formalised a related idea that&amp;rsquo;s become known as Hyrum&amp;rsquo;s Law: with enough users of an API, every observable behaviour will be depended on by somebody, regardless of what the documentation promises. For data systems, this is an absolute nightmare during rebuilds. Your downstream consumers don&amp;rsquo;t just depend on documented schemas. They depend on output ordering, null handling patterns, timestamp precision, and format quirks that were never specified anywhere. You rebuild the pipeline, and suddenly a report that&amp;rsquo;s worked for three years breaks — not because the data is wrong, but because the columns come back in a different order and someone&amp;rsquo;s Excel macro was hardcoded to column positions.&lt;/p>
&lt;p>I&amp;rsquo;ve seen teams spend more time debugging these &amp;ldquo;invisible contracts&amp;rdquo; after a rebuild than they would have spent refactoring the original system over two years.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-psychology-that-makes-us-do-it-anyway">The psychology that makes us do it anyway&lt;/h3>
&lt;br>
&lt;p>So if the evidence against big-bang rewrites is this overwhelming — and it is, from Spolsky to Brooks to McKinsey studies showing that large IT projects run 45% over budget and deliver 56% less value than predicted — why do smart, experienced data teams keep proposing them?&lt;/p>
&lt;p>Because the impulse to rebuild isn&amp;rsquo;t rational. It&amp;rsquo;s psychological. And once you understand the cognitive biases at play, you start seeing them everywhere.&lt;/p>
&lt;p>&lt;strong>The planning fallacy&lt;/strong> is the first culprit. Kahneman and Tversky showed that humans systematically underestimate how long tasks will take, even when they have direct experience with similar tasks that took longer than expected. In controlled studies, only 13% of subjects finished their project by the time they&amp;rsquo;d assigned a 50% probability of completion. The Sydney Opera House was estimated at AU$7 million and four years; it was delivered at AU$102 million and fourteen years. Every data warehouse rebuild proposal I&amp;rsquo;ve seen exhibits this same delusional optimism. &amp;ldquo;Six months, maybe nine.&amp;rdquo; It&amp;rsquo;s never six months.&lt;/p>
&lt;p>And it gets worse, because of what one writer calls the Achilles Paradox of rewrites: while you&amp;rsquo;re building the new version, features keep getting added to the old one. The business doesn&amp;rsquo;t stop generating requirements just because you&amp;rsquo;ve decided to rebuild. So the target keeps moving. The new system is perpetually six months from matching the functionality of the old one.&lt;/p>
&lt;p>&lt;strong>Not Invented Here syndrome&lt;/strong> is the second bias, and it&amp;rsquo;s the one that nobody wants to admit. Katz and Allen&amp;rsquo;s research on R&amp;amp;D project groups found that teams with stable composition develop a decreasing ability to absorb ideas from outside the group over time. They start believing they possess a monopoly on knowledge in their domain. The diagnostic sign? What researchers call &amp;ldquo;thought-terminating clichés&amp;rdquo; — phrases like &amp;ldquo;we already tried that&amp;rdquo; or &amp;ldquo;our situation is different.&amp;rdquo;&lt;/p>
&lt;p>In data teams, NIH manifests as building custom orchestration frameworks instead of using Airflow. Writing bespoke quality checks instead of adopting Great Expectations or dbt tests. Designing proprietary transformation layers instead of using tools that thousands of other teams have battle-tested. And most relevantly: insisting that the existing data model is fundamentally broken and needs to be replaced with something designed in-house from the ground up.&lt;/p>
&lt;p>&lt;strong>The new leader rewrite&lt;/strong> is the third pattern, and it might be the most destructive. A new head of data arrives. They look at the legacy warehouse they&amp;rsquo;ve inherited. They don&amp;rsquo;t understand the history behind any of the design decisions. They don&amp;rsquo;t know about the regulatory edge case that explains the weird date filter, or the source system quirk that necessitates the redundant join. All they see is complexity that they didn&amp;rsquo;t create — and complexity you didn&amp;rsquo;t create always looks worse than complexity you did.&lt;/p>
&lt;p>So they propose a rebuild. It&amp;rsquo;s partly strategic — they want to put their stamp on the architecture. And it&amp;rsquo;s partly genuine — they really do think they can do better. But they&amp;rsquo;re falling prey to the same illusion Spolsky identified: reading code is harder than writing it, and unfamiliar code always looks worse than it is.&lt;/p>
&lt;p>There&amp;rsquo;s a Stack Overflow survey finding that sits underneath all of this: &amp;ldquo;feeling unproductive&amp;rdquo; was the number one cause of developer unhappiness at 45%. Working with legacy systems is slow. It&amp;rsquo;s frustrating. Progress is incremental and often invisible. A greenfield rebuild, by contrast, feels &lt;em>amazing&lt;/em> — for the first few months. You&amp;rsquo;re making decisions, building things, moving fast. The architecture is clean and the tests pass and the world makes sense. And then reality seeps in, the edge cases accumulate, and eighteen months later you&amp;rsquo;ve got a codebase that looks suspiciously like the one you replaced.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-complexity-was-always-essential">The complexity was always essential&lt;/h3>
&lt;br>
&lt;p>Fred Brooks drew a distinction that I think about constantly. He separated &lt;strong>essential complexity&lt;/strong> — complexity that&amp;rsquo;s inherent to the problem domain and can&amp;rsquo;t be removed by better engineering — from &lt;strong>accidental complexity&lt;/strong>, which comes from poor implementation choices and can theoretically be eliminated.&lt;/p>
&lt;p>The truth about mature data warehouses: most of the complexity is essential. It&amp;rsquo;s not there because the original engineers were incompetent. It&amp;rsquo;s there because the business is genuinely that complicated.&lt;/p>
&lt;p>The transformation handling fifteen date formats? That&amp;rsquo;s because you have fifteen source systems and none of them agree on how to represent a date. The slowly-changing dimension with Type 2 tracking on attributes that seem trivial? Someone in compliance needed audit trails on those fields after a regulatory inquiry. The bridge table that connects customers to accounts through an intermediate entity that nobody can explain? It models a many-to-many relationship that emerged when the company acquired a subsidiary with a different customer hierarchy.&lt;/p>
&lt;p>A rebuild won&amp;rsquo;t make this complexity disappear. It&amp;rsquo;ll just redistribute it. Instead of one tangled fact table, you&amp;rsquo;ll have twelve microservices each handling a piece of the logic. Instead of one confusing WHERE clause, you&amp;rsquo;ll have business rules scattered across a semantic layer, a transformation layer, and a data quality framework. The total complexity will be identical — or worse, because now it&amp;rsquo;s distributed across more systems with more failure modes.&lt;/p>
&lt;p>Ward Cunningham — the person who coined the term &amp;ldquo;technical debt&amp;rdquo; — has said he wishes he&amp;rsquo;d used the word &amp;ldquo;opportunity&amp;rdquo; instead. His original metaphor wasn&amp;rsquo;t about sloppy code at all. It was about the gap between your code&amp;rsquo;s current model of the problem domain and your team&amp;rsquo;s evolved understanding of that domain. Technical debt, properly understood, is &lt;em>learning&lt;/em> that hasn&amp;rsquo;t been applied yet. The legacy data model doesn&amp;rsquo;t represent failure. It represents the team&amp;rsquo;s best understanding at the time it was built, plus every correction that production reality demanded afterward.&lt;/p>
&lt;p>Refactoring respects this. It asks: which parts of this complexity are essential (keep them, clarify them, test them) and which parts are accidental (remove them incrementally, one safe step at a time)? It preserves institutional knowledge while improving structure. It delivers value continuously instead of asking the business to wait years for a payoff that — statistically, based on everything we know about large-scale IT projects — probably won&amp;rsquo;t arrive.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="so-what-do-you-do-instead">So what do you do instead?&lt;/h3>
&lt;br>
&lt;p>I&amp;rsquo;m going to save the detailed playbook for Part II of this series, but the short version is this: you refactor. Methodically. Incrementally. With tests.&lt;/p>
&lt;p>Martin Fowler and Pramod Sadalage proved that database refactoring is possible back in 2006, when conventional wisdom said it couldn&amp;rsquo;t be done. Their principle was simple: make each change as small as possible, because the pain of integration increases exponentially with the size of the integration.&lt;/p>
&lt;p>dbt has turned this into a practical workflow for analytics engineering teams. Bring your legacy SQL in unchanged. Wrap it in a model. Verify the output matches. Then — and only then — start decomposing. Extract CTEs. Introduce staging layers. Build tests. Refactor one model at a time, auditing each change against the original output. It&amp;rsquo;s not glamorous. It doesn&amp;rsquo;t let you redesign everything from first principles. But it works, and it works without asking the business to lose access to their reports for six months.&lt;/p>
&lt;p>The Strangler Fig pattern, the Write-Audit-Publish pattern, the Expand and Contract pattern — these are all variations on the same theme: replace incrementally, verify continuously, and never throw away working logic until you&amp;rsquo;ve proved the replacement handles every case the original did.&lt;/p>
&lt;p>I&amp;rsquo;ll dig into each of these in Part II. For now, the point is simpler.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-scars-are-data">The scars are data&lt;/h3>
&lt;br>
&lt;p>When I look at a legacy data model — really look at it, with patience and without ego — I don&amp;rsquo;t see a mess anymore. I see a record of everything a business has learned about itself. Every weird join is a relationship somebody fought to understand. Every cryptic transformation is a business rule that was discovered through painful experience. Every seemingly arbitrary filter is a production incident that someone fixed at some point, probably at an hour they&amp;rsquo;d rather not remember.&lt;/p>
&lt;p>The Netscape rewrite remains one of the most studied failures in software history. And yet here we are, in 2026, watching data teams propose the same mistake — just with different technology names. Teradata becomes Snowflake. Informatica becomes dbt. The on-prem warehouse becomes the cloud lakehouse. The pitch changes, but the impulse doesn&amp;rsquo;t: tear it down, start over, do it right this time.&lt;/p>
&lt;p>My view? Your data model isn&amp;rsquo;t broken. It&amp;rsquo;s battle-tested. It&amp;rsquo;s messy because reality is messy. And the right response to inherited complexity isn&amp;rsquo;t demolition — it&amp;rsquo;s archaeology. Understand what&amp;rsquo;s there. Understand &lt;em>why&lt;/em> it&amp;rsquo;s there. Then improve it, one careful change at a time.&lt;/p>
&lt;p>The scars in your data model aren&amp;rsquo;t flaws. They&amp;rsquo;re knowledge. Treat them accordingly.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Modelling</category><category>Data Engineering</category><category>Refactoring</category><category>Data Warehousing</category><category>Technical Debt</category><category>Snowflake</category><category>dbt</category><category>Legacy Systems</category><category>Data Quality</category></item><item><title>12 Steps to Better Data Engineering</title><link>https://ghostinthedata.info/posts/2026/2026-03-07-twelve-steps-to-better-data-engineering/</link><pubDate>Sat, 07 Mar 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-03-07-twelve-steps-to-better-data-engineering/</guid><author>Chris Hillman</author><description>A quick scoring framework to assess your data team's engineering maturity. Twelve yes-or-no questions, each with concrete benchmarks for what good and amazing look like using dbt, Snowflake, GitHub Actions, and AWS.</description><content:encoded>&lt;p>Let me tell you about the moment I stopped trusting architecture diagrams.&lt;/p>
&lt;p>I was three days into a new role, getting up to speed with the data team. Smart people. Modern stack. On paper, everything looked right. They walked me through a beautiful data platform diagram: clean lines, labelled layers, colour-coded domains. It looked like something you&amp;rsquo;d see in a data conference.&lt;/p>
&lt;p>Then I asked a question that changed everything: &amp;ldquo;Can you rebuild your finance table from scratch right now?&amp;rdquo;&lt;/p>
&lt;p>The room went quiet. One person started explaining that it was &amp;ldquo;mostly possible&amp;rdquo; but there were &amp;ldquo;a few manual steps&amp;rdquo; and &amp;ldquo;some seeds that someone uploads&amp;rdquo;. Someone else mentioned an incremental model that &amp;ldquo;hasn&amp;rsquo;t been full-refreshed in about eight months because last time it broke.&amp;rdquo;&lt;/p>
&lt;p>That gap — between the diagram on the wall and the reality in the warehouse — is where I&amp;rsquo;ve spent most of my career. And over the years, working across team after team, I started noticing something. The teams that were struggling weren&amp;rsquo;t missing knowledge. They had smart engineers who knew about testing, CI/CD, documentation, data contracts. They&amp;rsquo;d read the blog posts. They&amp;rsquo;d watched the conference talks. What they didn&amp;rsquo;t have was a way to honestly assess where they stood and what to fix first.&lt;/p>
&lt;p>I kept wishing someone would build a simple diagnostic. Maybe they just needed a handful of honest questions that would tell you, in ten minutes, whether your team was engineering or firefighting.&lt;/p>
&lt;p>The moment that turned wishing into doing came about six months later, with a team I was leading. We implemented just two practices — CI testing on pull requests and mandatory code review — and I watched the compound effect over twelve weeks. Our incidents dropped. The analysts stopped asking &amp;ldquo;which table do I use?&amp;rdquo; A new hire shipped a model on day four. One of the senior engineers pulled me aside and said, &amp;ldquo;I wish we&amp;rsquo;d known how bad things were before you got here. We thought we were fine because nothing was on fire.&amp;rdquo;&lt;/p>
&lt;p>Nothing was on fire. But everything was smouldering.&lt;/p>
&lt;p>That&amp;rsquo;s why I built this test. Not because the world needs another framework — it doesn&amp;rsquo;t. But because the gap between &lt;em>thinking you&amp;rsquo;re fine&lt;/em> and &lt;em>knowing where you stand&lt;/em> is where data teams lose months of momentum. And the only way to close that gap is to ask questions honest enough that the answers sting a little.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-twelve-questions">The twelve questions&lt;/h3>
&lt;br>
&lt;p>Before we get into the detail, here they are. Score yourself. One point for each &amp;ldquo;yes.&amp;rdquo;&lt;/p>
&lt;ol>
&lt;li>Can you rebuild any table from raw data in one command?&lt;/li>
&lt;li>Do you have a data catalog that people actually use?&lt;/li>
&lt;li>Can a new analyst find the data they need without asking you?&lt;/li>
&lt;li>Do you test transformations before deploying them?&lt;/li>
&lt;li>Do you fix data quality issues before building new pipelines?&lt;/li>
&lt;li>Do you have SLAs for your critical tables?&lt;/li>
&lt;li>Do you have a single source of truth for business definitions?&lt;/li>
&lt;li>Can you explain the lineage of any metric in under 2 minutes?&lt;/li>
&lt;li>Do data producers know when they break downstream consumers?&lt;/li>
&lt;li>Do you do code review on SQL and dbt models?&lt;/li>
&lt;li>Do new hires build a real pipeline in their first week?&lt;/li>
&lt;li>Do you regularly talk to the people who actually use your data?&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>A score of 12 is unicorn territory.&lt;/strong> I&amp;rsquo;ve never seen it in the wild. Most teams I&amp;rsquo;ve worked with score 3 or 4. Anything below 6 and you&amp;rsquo;re not doing data engineering — you&amp;rsquo;re doing data triage.&lt;/p>
&lt;p>Now let&amp;rsquo;s break each one down. For every question, I&amp;rsquo;ll show you what &lt;em>good&lt;/em> looks like and what &lt;em>amazing&lt;/em> looks like — with concrete implementations using dbt, Snowflake, GitHub Actions, and AWS. Because &amp;ldquo;yes&amp;rdquo; isn&amp;rsquo;t binary in practice. There&amp;rsquo;s a massive gap between &amp;ldquo;sort of&amp;rdquo; and &amp;ldquo;absolutely.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="1-can-you-rebuild-any-table-from-raw-data-in-one-command">1. Can you rebuild any table from raw data in one command?&lt;/h3>
&lt;br>
&lt;p>This is the foundation. If you can&amp;rsquo;t reproduce your outputs from your inputs, you don&amp;rsquo;t have a pipeline — you have a prayer.&lt;/p>
&lt;p>The core principle here is &lt;strong>idempotency&lt;/strong>: running the same operation twice produces identical results. dbt is designed around this idea, but achieving it requires deliberate choices, especially with incremental models. Too many teams build incremental models that silently drift from what a full refresh would produce, and they don&amp;rsquo;t discover the discrepancy until something breaks spectacularly.&lt;/p>
&lt;h4 id="what-good-looks-like">What good looks like&lt;/h4>
&lt;p>Individual tables rebuild cleanly with &lt;code>dbt run --full-refresh --select model_name&lt;/code>. Your incremental models are tested against full refreshes during development. You&amp;rsquo;ve configured &lt;code>on_schema_change: sync_all_columns&lt;/code> so schema evolution doesn&amp;rsquo;t silently break things. And you&amp;rsquo;ve got a CI pipeline in GitHub Actions that runs &lt;code>dbt build&lt;/code> on pull requests.&lt;/p>
&lt;p>That&amp;rsquo;s good. That&amp;rsquo;s better than most. But it&amp;rsquo;s still table-by-table.&lt;/p>
&lt;h4 id="what-amazing-looks-like">What amazing looks like&lt;/h4>
&lt;p>Your entire warehouse rebuilds from raw data using a &lt;strong>Write-Audit-Publish (WAP) pattern&lt;/strong>. If you&amp;rsquo;re not familiar with WAP, I wrote a &lt;a href="https://ghostinthedata.info/posts/2025/2025-05-18-wap-data-pipelines/" target="_blank" rel="noopener">deep dive on implementing it with Airflow&lt;/a> — the core idea is that new data gets written to a staging environment, audited against quality checks, and only published to production once it passes. It&amp;rsquo;s the data engineering equivalent of a preflight checklist: nothing reaches consumers until it&amp;rsquo;s been verified. And if you&amp;rsquo;re on Snowflake with Iceberg tables, the game has changed — &lt;a href="https://ghostinthedata.info/posts/2026/2026-02-27-wap-iceberg-branching/" target="_blank" rel="noopener">WAP with Iceberg branching&lt;/a> gives you git-like isolation at the table level, which means you can stage, audit, and publish without creating separate schemas or databases at all.&lt;/p>
&lt;p>The practical implementation: dbt always targets a staging schema or database (or an Iceberg branch), runs all models and tests, and only on success does the data get promoted to production. Combined with Snowflake&amp;rsquo;s &lt;strong>zero-copy cloning&lt;/strong>, this becomes practical even at scale — cloning a multi-terabyte database takes seconds and costs nothing in storage. You&amp;rsquo;re not duplicating data; you&amp;rsquo;re creating metadata pointers.&lt;/p>
&lt;p>And when things &lt;em>do&lt;/em> go wrong — and they will — the question becomes how quickly you can recover. If your incremental models have drifted, or a source system silently changed its schema three weeks ago, you need a backfill strategy that doesn&amp;rsquo;t involve rebuilding everything from scratch. I wrote about the &lt;a href="https://ghostinthedata.info/posts/2026/2026-02-07-self-healing/" target="_blank" rel="noopener">pitfalls of day-by-day backfills and how to heal tables properly&lt;/a> — it&amp;rsquo;s the companion piece to idempotency, because reproducibility isn&amp;rsquo;t just about building forward. It&amp;rsquo;s about being able to go back.&lt;/p>
&lt;p>The really mature teams take it further with &lt;strong>Slim CI&lt;/strong>. Instead of rebuilding everything on every PR, they store the production &lt;code>manifest.json&lt;/code> in S3 and compare it against the new build. Only modified models and their downstream dependencies get rebuilt:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># .github/workflows/dbt-ci.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">on&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pull_request&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">types&lt;/span>: [&lt;span style="color:#ae81ff">opened, reopened, synchronize]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">steps&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Download production manifest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">run&lt;/span>: &lt;span style="color:#ae81ff">aws s3 cp s3://dbt-artifacts/manifest.json ./state/manifest.json&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Run dbt build (modified models only)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">run&lt;/span>: &lt;span style="color:#ae81ff">dbt build -s &amp;#39;state:modified+&amp;#39; --defer --state ./state --target ci&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>One team I worked with reported a &lt;strong>30% reduction in compute costs&lt;/strong> just by splitting their monolithic CI job into targeted workflows.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Watch out for this:&lt;/strong> Protect massive tables with &lt;code>full_refresh = false&lt;/code> in your dbt config. One accidental &lt;code>--full-refresh&lt;/code> on a billion-row fact table can ruin your morning. Override it with a variable when you actually mean it: &lt;code>full_refresh = var(&amp;quot;force_full_refresh&amp;quot;, false)&lt;/code>.&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="2-do-you-have-a-data-catalog-that-people-actually-use">2. Do you have a data catalog that people actually use?&lt;/h3>
&lt;br>
&lt;p>The emphasis is on &amp;ldquo;actually use.&amp;rdquo; I&amp;rsquo;ve lost count of how many teams have shown me a data catalog, only for me to discover that nobody&amp;rsquo;s opened it in months. The data catalog is the gym membership of the data world — everyone has one, nobody goes.&lt;/p>
&lt;p>The failure mode is almost always the same: a team selects a tool, loads metadata, declares victory, and moves on. Six months later it&amp;rsquo;s a ghost town of stale descriptions and orphaned tables.&lt;/p>
&lt;h4 id="what-good-looks-like-1">What good looks like&lt;/h4>
&lt;p>dbt Docs generated and hosted somewhere accessible. Your major tables have descriptions in &lt;code>schema.yml&lt;/code>. The lineage graph exists and gets pulled up occasionally during incident response or onboarding. It&amp;rsquo;s not perfect, but it&amp;rsquo;s there.&lt;/p>
&lt;h4 id="what-amazing-looks-like-1">What amazing looks like&lt;/h4>
&lt;p>The catalog is the &lt;strong>default entry point&lt;/strong> for data questions — not Microsoft Teams, not the data engineer sitting three desks over.&lt;/p>
&lt;p>Spotify&amp;rsquo;s internal tool Lexikon pushed data scientist adoption from 75% to 95% by doing something clever: they added &lt;em>personalised dataset recommendations&lt;/em>, people/team pages showing who uses each dataset, common joins, and popular fields. It became a top-five internal tool because it was genuinely faster than asking a colleague.&lt;/p>
&lt;p>Airbnb&amp;rsquo;s Metis serves over 1,000 data users weekly. Their secret? A Google-like search interface that works for every skill level, from SQL-fluent engineers to product managers who&amp;rsquo;ve never written a query.&lt;/p>
&lt;p>The pattern that actually works for smaller teams: author descriptions in whatever UI is easiest, auto-generate a PR back to dbt&amp;rsquo;s &lt;code>schema.yml&lt;/code> daily, review in Git. This keeps your definitions version-controlled without making people learn Git just to document a table.&lt;/p>
&lt;p>And here&amp;rsquo;s the leading indicator that your catalog is working: &lt;strong>track the decline in Microsoft Teams questions about data.&lt;/strong> Productboard used exactly this metric. Fewer &amp;ldquo;which table do I use for revenue?&amp;rdquo; messages meant the catalog was earning its keep.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="3-can-a-new-analyst-find-the-data-they-need-without-asking-you">3. Can a new analyst find the data they need without asking you?&lt;/h3>
&lt;br>
&lt;p>This is the catalog question&amp;rsquo;s evil twin. A catalog helps, but self-service discovery is really about the cumulative effect of naming conventions, documentation-as-code, semantic layers, and project structure.&lt;/p>
&lt;p>dbt Labs published guidance that should be printed and taped to every data engineer&amp;rsquo;s monitor: &lt;strong>&amp;ldquo;Assume your end-user will have no other context than the model name.&amp;rdquo;&lt;/strong> Model names persist across databases, BI tools, DAGs, and docs. Folder names and schemas don&amp;rsquo;t follow the data the same way.&lt;/p>
&lt;h4 id="what-good-looks-like-2">What good looks like&lt;/h4>
&lt;p>You&amp;rsquo;re using the canonical dbt naming convention: &lt;code>stg_&lt;/code> for staging, &lt;code>int_&lt;/code> for intermediate, &lt;code>fct_&lt;/code> for facts, &lt;code>dim_&lt;/code> for dimensions. Your project follows the three-layer structure — staging (1:1 with sources, materialised as views), intermediate (composable business logic), and marts (final business-ready datasets organised by domain). A reasonably technical person can navigate the DAG and find what they need within ten minutes.&lt;/p>
&lt;h4 id="what-amazing-looks-like-2">What amazing looks like&lt;/h4>
&lt;p>A new analyst finds and understands any dataset &lt;strong>within minutes&lt;/strong>, without messaging anyone.&lt;/p>
&lt;p>Every model has both table-level and column-level descriptions. Schema field search works across all tables — &amp;ldquo;find every table with &lt;code>customer_id&lt;/code>&amp;rdquo; returns results instantly. Personalised dataset recommendations surface relevant tables based on role.&lt;/p>
&lt;p>And the crown jewel: a &lt;strong>semantic layer&lt;/strong> that lets business users query metrics in plain language without writing SQL. The dbt Semantic Layer (powered by MetricFlow) centralises metric definitions in YAML alongside your models — entities, dimensions, and measures declared once, then consumed via API in Tableau, Power BI, Looker, Python notebooks, and increasingly, AI agents.&lt;/p>
&lt;p>Bilt Rewards reported an 80% decrease in data costs after implementing it for embedded analytics. And dbt Labs&amp;rsquo; testing showed 83% of natural-language questions answered correctly when routed through the semantic layer. That last number matters more than you&amp;rsquo;d think — it&amp;rsquo;s the difference between AI tooling that actually works and AI tooling that confidently gives the wrong answer.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="4-do-you-test-transformations-before-deploying-them">4. Do you test transformations before deploying them?&lt;/h3>
&lt;br>
&lt;p>This is probably the single highest-impact practice on the list. If I could only get a team to adopt one of these twelve, it would be this one.&lt;/p>
&lt;p>But here&amp;rsquo;s what most teams get wrong: they think &amp;ldquo;testing&amp;rdquo; means slapping &lt;code>not_null&lt;/code> on a few columns and calling it done. Real testing means understanding the &lt;a href="https://ghostinthedata.info/posts/2025/2025-11-24-data-quality-framework/" target="_blank" rel="noopener">dimensions of data quality&lt;/a> — completeness, uniqueness, timeliness, validity, accuracy, consistency — and building checks that cover each one deliberately. I&amp;rsquo;ve written about &lt;a href="https://ghostinthedata.info/posts/2025/2025-11-22-data-quality/" target="_blank" rel="noopener">why data quality is a deeper problem than most teams realise&lt;/a>, and the short version is this: testing in CI is where quality becomes a habit rather than a hope. But only if your tests are actually measuring the things that matter.&lt;/p>
&lt;p>The consensus across every practitioner I&amp;rsquo;ve studied is clear: &lt;strong>CI for dbt is the most impactful thing you can do for data quality.&lt;/strong> Three complementary testing layers form a comprehensive strategy: generic dbt tests (&lt;code>not_null&lt;/code>, &lt;code>unique&lt;/code>, &lt;code>accepted_values&lt;/code>, &lt;code>relationships&lt;/code>), unit tests (introduced in dbt v1.8 for testing complex business logic with static inputs), and data diffing for value-level comparison between production and development.&lt;/p>
&lt;h4 id="what-good-looks-like-3">What good looks like&lt;/h4>
&lt;p>Basic dbt tests on key models. Tests run as part of scheduled production jobs using &lt;code>dbt build&lt;/code> (which runs tests immediately after each model, not in a separate pass). Pull requests exist for dbt changes, and someone eyeballs them before merging.&lt;/p>
&lt;h4 id="what-amazing-looks-like-3">What amazing looks like&lt;/h4>
&lt;p>Full Slim CI with per-PR isolated Snowflake schemas, data diffing integrated into PR comments, and automated linting that catches style issues before a human ever looks at the code.&lt;/p>
&lt;p>The numbers from real teams are staggering. Thumbtack — 50-plus analysts, five data engineers, over 100 PRs per month — previously spent one to two hours manually validating each pull request with SQL queries and spreadsheets. After integrating data diffing into their GitHub CI pipeline, they saved over 200 hours per month. That&amp;rsquo;s not a rounding error. That&amp;rsquo;s a full-time engineer&amp;rsquo;s worth of capacity recovered.&lt;/p>
&lt;p>Dutchie caught timezone corruption on &lt;code>created_at&lt;/code> fields and case-when logic errors that were silently shifting 20% of data between columns. Nutrafol caught a transformation that would have shown net revenue plummeting — before it reached production and before the CFO&amp;rsquo;s Monday morning dashboard refreshed.&lt;/p>
&lt;p>Zscaler went even further. They built PRISM, a multi-agent AI PR review system that reduced manual review time by 90% — auto-approving conformant PRs and posting targeted comments on complex logic changes.&lt;/p>
&lt;p>Here&amp;rsquo;s what the mature pipeline looks like in practice:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># On every PR: lint, compile, build modified models, diff against prod&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">steps&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">SQLFluff lint&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">run&lt;/span>: &lt;span style="color:#ae81ff">sqlfluff lint models/ --dialect snowflake&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">dbt compile&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">run&lt;/span>: &lt;span style="color:#ae81ff">dbt compile&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Slim CI build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">run&lt;/span>: &lt;span style="color:#ae81ff">dbt build -s &amp;#39;state:modified+&amp;#39; --defer --state ./state --target ci&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Data Diff&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">run&lt;/span>: &lt;span style="color:#ae81ff">datafold ci submit --ci-config-id ${{ secrets.DATAFOLD_CI_CONFIG }}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="5-do-you-fix-data-quality-issues-before-building-new-pipelines">5. Do you fix data quality issues before building new pipelines?&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s an uncomfortable stat: Monte Carlo&amp;rsquo;s 2023 State of Data Quality report found that &lt;strong>74% of organisations reported that business stakeholders identify data quality issues first&lt;/strong> — up from 47% the prior year. Most data teams learn about broken data from the people who are supposed to trust it. That&amp;rsquo;s not a technology problem. That&amp;rsquo;s a culture problem.&lt;/p>
&lt;h4 id="what-good-looks-like-4">What good looks like&lt;/h4>
&lt;p>Basic dbt tests catch obvious issues. Tests run in production jobs. The team has an informal sense of which data matters most, usually earned through painful experience — someone got burned by bad revenue numbers in an executive review, and now those tables have tests.&lt;/p>
&lt;h4 id="what-amazing-looks-like-4">What amazing looks like&lt;/h4>
&lt;p>Data quality SLAs enforced with &lt;strong>error budgets&lt;/strong>, borrowed from the SRE playbook. A 99.5% data availability target allows approximately 3.6 hours of acceptable downtime per month. When the error budget is consumed, reliability work takes priority over feature delivery. Full stop. No new pipelines until you&amp;rsquo;ve earned back your quality margin.&lt;/p>
&lt;p>Warner Bros. Discovery created something they call a &lt;strong>Data Quality Forum&lt;/strong> — not a reactive incident-review meeting, but an operational bridge between teams. They used observability tooling to surface anomalies early, developed a priority matrix (P0/P1/P2), and made the forum&amp;rsquo;s patterns part of new hire onboarding. During Olympics livestreaming, custom SQL checks detected missing content metadata before it could break reporting.&lt;/p>
&lt;p>Snowflake now offers native &lt;strong>Data Metric Functions&lt;/strong> — &lt;code>FRESHNESS&lt;/code>, &lt;code>NULL_COUNT&lt;/code>, &lt;code>DUPLICATE_COUNT&lt;/code>, &lt;code>UNIQUE_COUNT&lt;/code>, &lt;code>ROW_COUNT&lt;/code> — that can be scheduled to run on DML changes or time intervals. Results land in &lt;code>SNOWFLAKE.LOCAL.DATA_QUALITY_MONITORING_RESULTS&lt;/code>. It&amp;rsquo;s not a replacement for dbt tests, but it provides a warehouse-level safety net that catches issues your transformation layer might miss.&lt;/p>
&lt;p>The cultural shift matters more than the tooling. HelloFresh&amp;rsquo;s VP of Data drove a transformation through three stages: ad-hoc individual fixes, organised cleanup by a central team, and finally proactive quality at the source. The final stage required embedding data product owners within business domains and running data literacy programs. It&amp;rsquo;s not glamorous work. But it&amp;rsquo;s the work that changes outcomes.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="6-do-you-have-slas-for-your-critical-tables">6. Do you have SLAs for your critical tables?&lt;/h3>
&lt;br>
&lt;p>&amp;ldquo;The dashboard is stale&amp;rdquo; is the Slack message that launches a thousand fire drills. SLAs turn that reactive scramble into a measured, prioritised response.&lt;/p>
&lt;p>The breakthrough insight from practitioners: &lt;strong>don&amp;rsquo;t aim for 100%.&lt;/strong> A 99.5% target gives you a realistic buffer for maintenance, edge cases, and the occasional Snowflake service hiccup. Different data products warrant different targets — which means you need a tiered classification system.&lt;/p>
&lt;h4 id="what-good-looks-like-5">What good looks like&lt;/h4>
&lt;p>Your team knows which tables are critical, usually because they&amp;rsquo;ve been burned before. Some &lt;code>dbt source freshness&lt;/code> checks are running. Basic Slack alerts fire when jobs fail. It&amp;rsquo;s reactive, but at least you know when things break.&lt;/p>
&lt;h4 id="what-amazing-looks-like-5">What amazing looks like&lt;/h4>
&lt;p>A formal tiered classification published in the data catalog with specific, measurable commitments:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tier 1 (Gold):&lt;/strong> ML systems, revenue reporting → PagerDuty on-call, must have &lt;code>unique&lt;/code> + &lt;code>not_null&lt;/code> tests + assigned owner, freshness SLA under 4 hours&lt;/li>
&lt;li>&lt;strong>Tier 2 (Silver):&lt;/strong> Executive dashboards, KPI reports → Slack team channel alerts, owner assigned, freshness SLA under 12 hours&lt;/li>
&lt;li>&lt;strong>Tier 3 (Bronze):&lt;/strong> Ad-hoc analytics, exploration tables → weekly digest, freshness SLA under 24 hours&lt;/li>
&lt;/ul>
&lt;p>The implementation lives in dbt &lt;code>meta&lt;/code> config:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">fct_revenue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">meta&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">owner&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;finance-data-team&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">criticality&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;tier_1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">sla_freshness&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;4 hours&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">unique&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">column_name&lt;/span>: &lt;span style="color:#ae81ff">order_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">not_null&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">column_name&lt;/span>: &lt;span style="color:#ae81ff">order_id&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SLA compliance dashboards track attainment percentage, breach counts, and trends over time. When the error budget is exhausted, feature development freezes — same principle as practice #5. Dedicated Snowflake warehouses isolate critical workloads from exploratory queries so that someone&amp;rsquo;s &lt;code>SELECT *&lt;/code> on a billion-row table doesn&amp;rsquo;t cause your revenue report to miss its 8 AM deadline.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="7-do-you-have-a-single-source-of-truth-for-business-definitions">7. Do you have a single source of truth for business definitions?&lt;/h3>
&lt;br>
&lt;p>Everyone&amp;rsquo;s had this conversation: &amp;ldquo;Which revenue number is right?&amp;rdquo; Two dashboards, two numbers, two teams who each think the other is wrong. This is what happens when metric definitions live inside BI tools, tribal knowledge, and the head of that one analyst who&amp;rsquo;s been here since 2019. If you&amp;rsquo;ve ever worked with dimensional models, you&amp;rsquo;ll recognise this as the conformed dimension problem — I covered why &lt;a href="https://ghostinthedata.info/posts/2025/2025-11-07-effective-data-modelling/" target="_blank" rel="noopener">dimensional modeling still matters&lt;/a> and how conformed dimensions are the integration backbone that prevents exactly this kind of mess.&lt;/p>
&lt;h4 id="what-good-looks-like-6">What good looks like&lt;/h4>
&lt;p>Key metrics are defined in dbt docs or a wiki. The team knows which mart table is the canonical source for revenue, orders, or whatever your core entities are. When someone asks &amp;ldquo;which table do I use?&amp;rdquo;, there&amp;rsquo;s a consistent answer — even if it&amp;rsquo;s only communicated verbally.&lt;/p>
&lt;h4 id="what-amazing-looks-like-6">What amazing looks like&lt;/h4>
&lt;p>A centralised &lt;strong>semantic layer&lt;/strong> enforced as the only path to metrics. All definitions are version-controlled in dbt YAML, reviewed via PR, and validated in CI.&lt;/p>
&lt;p>Here&amp;rsquo;s what a metric definition looks like in MetricFlow:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metrics&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">cancellation_rate&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">description&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Percentage of orders cancelled within 24 hours of placement&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">type&lt;/span>: &lt;span style="color:#ae81ff">ratio&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">type_params&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">numerator&lt;/span>: &lt;span style="color:#ae81ff">cancellations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">denominator&lt;/span>: &lt;span style="color:#ae81ff">order_total&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">filter&lt;/span>: |&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> {{ TimeDimension(&amp;#39;metric_time&amp;#39;, &amp;#39;day&amp;#39;) }}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That definition gets consumed identically whether someone queries it from Tableau, Power BI, a Python notebook, or an AI agent. One definition. One answer. Everywhere.&lt;/p>
&lt;p>Whatnot — the livestream marketplace growing at breakneck speed — solved a different version of this problem. They used Protobuf schemas as a single source of truth for event definitions, consolidating hundreds of chaotic Snowflake tables down to two clean &amp;ldquo;exposure&amp;rdquo; tables: &lt;code>backend_events&lt;/code> and &lt;code>frontend_events&lt;/code>. Brutal simplification. But it worked because everyone could find the data.&lt;/p>
&lt;p>&lt;code>dbt sl validate&lt;/code> in CI catches breaking changes to semantic definitions before merge. That&amp;rsquo;s the key differentiator — your metric definitions aren&amp;rsquo;t just documented, they&amp;rsquo;re &lt;em>enforced&lt;/em>.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="8-can-you-explain-the-lineage-of-any-metric-in-under-2-minutes">8. Can you explain the lineage of any metric in under 2 minutes?&lt;/h3>
&lt;br>
&lt;p>When your CFO asks &amp;ldquo;where does this number come from?&amp;rdquo;, you need an answer faster than &amp;ldquo;let me check and get back to you.&amp;rdquo; Two minutes is generous. In most incident situations, you&amp;rsquo;ve got about thirty seconds before people start making assumptions.&lt;/p>
&lt;h4 id="what-good-looks-like-7">What good looks like&lt;/h4>
&lt;p>The dbt DAG is viewable, hosted on S3 or an internal site. Your team can trace major mart tables back to their sources. During incident response, someone pulls up the lineage graph and walks through the chain. It takes some squinting, but the information is there.&lt;/p>
&lt;h4 id="what-amazing-looks-like-7">What amazing looks like&lt;/h4>
&lt;p>&lt;strong>Column-level lineage&lt;/strong> automated across the full stack — from source systems through dbt transformations to the BI dashboards consumers see.&lt;/p>
&lt;p>dbt Cloud provides column-level lineage showing whether each column is &amp;ldquo;transformed&amp;rdquo; versus &amp;ldquo;passthrough/rename.&amp;rdquo; On the Snowflake side, the &lt;code>ACCESS_HISTORY&lt;/code> view (Enterprise Edition) tracks both read and write operations with column-level mappings. Snowflake Labs published an open-source adapter that converts &lt;code>ACCESS_HISTORY&lt;/code> data into OpenLineage JSON format, making cross-platform lineage possible.&lt;/p>
&lt;p>But here&amp;rsquo;s where it gets practical. The open-source tool &lt;strong>Recce&lt;/strong> compares two dbt environments and produces a &amp;ldquo;Lineage Diff&amp;rdquo; in CI. It categorises changes as breaking, partial-breaking, or non-breaking. PR reviewers get an instant risk assessment: &amp;ldquo;this change affects 47 downstream models, including three Tier 1 dashboards&amp;rdquo; versus &amp;ldquo;this change is isolated to a staging model with no downstream consumers.&amp;rdquo;&lt;/p>
&lt;p>PRs get annotated automatically with impact analysis. Cross-project lineage via dbt Mesh connects multiple dbt projects. Lineage isn&amp;rsquo;t a pretty graph that nobody looks at — it&amp;rsquo;s used daily for onboarding, incident response, impact analysis, and cost optimisation.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="9-do-data-producers-know-when-they-break-downstream-consumers">9. Do data producers know when they break downstream consumers?&lt;/h3>
&lt;br>
&lt;p>This is where data engineering starts borrowing seriously from software engineering. In a microservices world, you wouldn&amp;rsquo;t deploy an API change without knowing who calls your endpoint. But in data, teams routinely change source schemas, modify column semantics, or sunset tables with zero awareness of who&amp;rsquo;s consuming them downstream.&lt;/p>
&lt;p>&lt;strong>Data contracts&lt;/strong> change that dynamic.&lt;/p>
&lt;h4 id="what-good-looks-like-8">What good looks like&lt;/h4>
&lt;p>dbt sources are defined with &lt;code>dbt source freshness&lt;/code> running and alerting. The team is aware of major upstream dependencies. When Fivetran&amp;rsquo;s sync breaks or a source system changes its schema, someone notices within a few hours — usually because a test fails.&lt;/p>
&lt;h4 id="what-amazing-looks-like-8">What amazing looks like&lt;/h4>
&lt;p>dbt &lt;strong>model contracts&lt;/strong> (v1.5+) enforced on all gold-layer and public models:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">dim_users&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">config&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">contract&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enforced&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">user_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">data_type&lt;/span>: &lt;span style="color:#ae81ff">int&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">constraints&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">type&lt;/span>: &lt;span style="color:#ae81ff">primary_key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">email&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">data_type&lt;/span>: &lt;span style="color:#ae81ff">varchar&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">constraints&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">type&lt;/span>: &lt;span style="color:#ae81ff">not_null&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a &lt;strong>preflight check&lt;/strong>: dbt verifies the model&amp;rsquo;s compiled SQL returns columns matching the contract before building. Breaking changes — removed columns, changed data types, modified constraints — are caught by &lt;code>state:modified&lt;/code> in CI. Model access levels (&lt;code>public&lt;/code>, &lt;code>protected&lt;/code>, &lt;code>private&lt;/code>) control cross-project visibility.&lt;/p>
&lt;p>Whatnot went further with Protobuf schemas enforced via Buf linter in CI. Every event producer runs against a common testing harness. Post-deployment monitoring catches semantic drift — when a field still exists but its meaning has changed.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Gotcha worth knowing:&lt;/strong> dbt contracts validate the compiled SQL, not the actual Snowflake table. If tools like Fivetran add columns directly to your raw layer, dbt won&amp;rsquo;t know until the next run. Monitor schema changes externally using Snowflake&amp;rsquo;s &lt;code>INFORMATION_SCHEMA&lt;/code> or &lt;code>ACCESS_HISTORY&lt;/code> to catch drift between runs.&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="10-do-you-do-code-review-on-sql-and-dbt-models">10. Do you do code review on SQL and dbt models?&lt;/h3>
&lt;br>
&lt;p>I&amp;rsquo;m constantly surprised by how many data teams still deploy SQL changes without review. In software engineering, unreviewed code going to production would be considered reckless. In data engineering, it&amp;rsquo;s Tuesday.&lt;/p>
&lt;h4 id="what-good-looks-like-9">What good looks like&lt;/h4>
&lt;p>Pull requests are required for all dbt changes — branch protection is enabled on main. At least one human reviewer looks at each PR. There&amp;rsquo;s a basic PR description explaining what changed and why.&lt;/p>
&lt;h4 id="what-amazing-looks-like-9">What amazing looks like&lt;/h4>
&lt;p>Automated CI running SQLFluff lint + &lt;code>dbt compile&lt;/code> + &lt;code>dbt build&lt;/code> + data diff on every PR. Pre-commit hooks catch issues before code is even committed. PR templates with structured sections: description, linked tickets, impact zone, testing evidence.&lt;/p>
&lt;p>The recommended pre-commit stack combines SQLFluff for linting with dbt-checkpoint for structural validation:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># .pre-commit-config.yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">repos&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">repo&lt;/span>: &lt;span style="color:#ae81ff">https://github.com/sqlfluff/sqlfluff&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">hooks&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">id&lt;/span>: &lt;span style="color:#ae81ff">sqlfluff-lint&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">args&lt;/span>: [--&lt;span style="color:#ae81ff">dialect, snowflake]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">repo&lt;/span>: &lt;span style="color:#ae81ff">https://github.com/dbt-checkpoint/dbt-checkpoint&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">hooks&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">id&lt;/span>: &lt;span style="color:#ae81ff">check-model-has-tests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">id&lt;/span>: &lt;span style="color:#ae81ff">check-model-columns-have-desc&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">id&lt;/span>: &lt;span style="color:#ae81ff">check-source-has-freshness&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That last hook — &lt;code>check-model-columns-have-desc&lt;/code> — is quietly revolutionary. It means documentation isn&amp;rsquo;t optional. You literally cannot merge a model without column descriptions. The &amp;ldquo;we&amp;rsquo;ll document it later&amp;rdquo; excuse dies right there in the CI pipeline.&lt;/p>
&lt;p>Surfline (700+ dbt models) reported that after integrating SQLFluff, SQL consistency improved dramatically and reviewer burden dropped. New engineers learned &amp;ldquo;good SQL&amp;rdquo; from day one because the linter enforced it automatically. Another team, Markerr, found that automated style enforcement freed up review time to focus on deeper logic questions rather than arguing about capitalisation and trailing commas.&lt;/p>
&lt;p>The &lt;code>dbt_project_evaluator&lt;/code> package is worth mentioning too — it audits your entire DAG structure against dbt Labs&amp;rsquo; published best practices. Run it in CI and it&amp;rsquo;ll flag things like models that reference sources directly (bypassing staging), duplicate sources, or models with no tests.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="11-do-new-hires-build-a-real-pipeline-in-their-first-week">11. Do new hires build a real pipeline in their first week?&lt;/h3>
&lt;br>
&lt;p>The speed at which a new hire becomes productive tells you everything about the state of your documentation, tooling, and team culture. If it takes three weeks before someone can make a meaningful contribution, you don&amp;rsquo;t have an onboarding problem — you have a platform problem. I wrote a &lt;a href="https://ghostinthedata.info/posts/2025/2025-02-23-landed-the-role/" target="_blank" rel="noopener">guide to navigating your first 90 days as a data engineer&lt;/a> — and the teams that make those first 90 days count are almost always the ones who&amp;rsquo;ve invested in the infrastructure described here.&lt;/p>
&lt;h4 id="what-good-looks-like-10">What good looks like&lt;/h4>
&lt;p>New hire has access to tools within a day or two. Some documentation exists. A buddy or mentor is assigned. They can run the dbt project locally by end of week one, even if they haven&amp;rsquo;t contributed anything yet.&lt;/p>
&lt;h4 id="what-amazing-looks-like-10">What amazing looks like&lt;/h4>
&lt;p>Pre-arrival setup is complete before Day 1: machine provisioned, logins ready, Snowflake roles assigned, dbt Cloud account active. No engineer should spend their first morning installing things.&lt;/p>
&lt;p>Each developer gets a &lt;strong>personal Snowflake sandbox&lt;/strong> — &lt;code>dbt_&amp;lt;username&amp;gt;&lt;/code> schema in the dev database, with write access only to dev and read-only access to raw/staging. Snowflake&amp;rsquo;s zero-copy cloning makes production data available instantly without duplicating storage costs. Productboard open-sourced &lt;code>dbt-snowflake-sandbox&lt;/code> — a set of dbt macros that create isolated sandboxes by cloning only the specific model dependencies a developer needs.&lt;/p>
&lt;p>The first-week project is structured and progressive:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Day 1-2:&lt;/strong> Run the existing dbt project locally. Explore the DAG. Read the key model documentation.&lt;/li>
&lt;li>&lt;strong>Day 3:&lt;/strong> Add a new source or staging model for a real (but low-risk) dataset.&lt;/li>
&lt;li>&lt;strong>Day 4:&lt;/strong> Build a staging model with tests and documentation.&lt;/li>
&lt;li>&lt;strong>Day 5:&lt;/strong> Open a PR, go through the review process, and get it merged.&lt;/li>
&lt;/ol>
&lt;p>By Friday, they&amp;rsquo;ve shipped something real. They understand the workflow. They&amp;rsquo;ve experienced CI, code review, and deployment. And critically, they &lt;em>feel&lt;/em> like a contributor rather than a tourist.&lt;/p>
&lt;p>7shifts estimated cutting onboarding time by over a week per new hire just by having documentation and data questions accessible in their data catalog.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="12-do-you-regularly-talk-to-the-people-who-actually-use-your-data">12. Do you regularly talk to the people who actually use your data?&lt;/h3>
&lt;br>
&lt;p>This is the question that separates data teams who build for their portfolio from data teams who build for their business. You can nail every technical practice on this list and still fail if you&amp;rsquo;re building the wrong things. I&amp;rsquo;ve written before about &lt;a href="https://ghostinthedata.info/posts/2025/2025-02-15-data-impact/" target="_blank" rel="noopener">how to maximise your data team&amp;rsquo;s impact&lt;/a> — and the through-line is always the same: the teams that create real value are the ones who stay close to the people consuming their work.&lt;/p>
&lt;h4 id="what-good-looks-like-11">What good looks like&lt;/h4>
&lt;p>A Slack channel exists for data questions. Communication happens when issues arise. There are occasional stakeholder meetings, usually prompted by something breaking or a new request coming in.&lt;/p>
&lt;h4 id="what-amazing-looks-like-11">What amazing looks like&lt;/h4>
&lt;p>&lt;strong>Data office hours.&lt;/strong> Weekly or bi-weekly open sessions where anyone in the organisation can bring data questions. Not a presentation. Not a status update. An open door.&lt;/p>
&lt;p>Holistics published a detailed playbook for running what they call &amp;ldquo;data clinics.&amp;rdquo; The core principles: teach, don&amp;rsquo;t serve. Show business users how to self-serve rather than just answering their question and sending them on their way. Montreal Analytics reported that after implementing data clinics, the number of self-serve business users on their BI tool grew tenfold. &lt;em>Tenfold.&lt;/em>&lt;/p>
&lt;p>Pair that with a monthly &lt;strong>Data NPS survey&lt;/strong> — a single question: &amp;ldquo;How likely are you to recommend our data team&amp;rsquo;s products to a colleague?&amp;rdquo; It sounds corporate, but without this metric, you have no way to quantify whether your consumers are satisfied or just silently building workarounds in Excel.&lt;/p>
&lt;p>dbt &lt;strong>exposures&lt;/strong> formalise the connection between data models and their consumers:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">exposures&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">weekly_revenue_dashboard&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">type&lt;/span>: &lt;span style="color:#ae81ff">dashboard&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">owner&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Finance Team&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">email&lt;/span>: &lt;span style="color:#ae81ff">finance@company.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">depends_on&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">ref(&amp;#39;fct_revenue&amp;#39;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">ref(&amp;#39;dim_date&amp;#39;)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When a model changes, the PR shows which exposures are affected. Stakeholder impact becomes visible in code review. You can proactively notify the finance team that their revenue dashboard&amp;rsquo;s upstream model is changing before they discover it themselves.&lt;/p>
&lt;p>Frame your communication in business terms. Not &amp;ldquo;model precision is up 12%&amp;rdquo; but &amp;ldquo;the sales team can now identify high-value leads 40% faster.&amp;rdquo; Nobody outside your team cares about your DAG. They care about whether they can trust the numbers. If you&amp;rsquo;re not sure how to bridge that gap, I wrote a guide on &lt;a href="https://ghostinthedata.info/posts/2025/2025-02-08-breaking-down-business-context/" target="_blank" rel="noopener">breaking down business context&lt;/a> — because the hardest part of talking to stakeholders isn&amp;rsquo;t the talking, it&amp;rsquo;s knowing what they actually need to hear.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="scoring-your-team">Scoring your team&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s the scoring framework, and I want you to be ruthless with yourself:&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Score&lt;/th>
 &lt;th>What it means&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>10-12&lt;/strong>&lt;/td>
 &lt;td>You&amp;rsquo;re in elite territory. Your data platform is a competitive advantage.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>7-9&lt;/strong>&lt;/td>
 &lt;td>You&amp;rsquo;ve got strong foundations with clear areas to improve.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>4-6&lt;/strong>&lt;/td>
 &lt;td>You&amp;rsquo;re functional but fragile. One bad incident away from a crisis of trust.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>1-3&lt;/strong>&lt;/td>
 &lt;td>You&amp;rsquo;re firefighting, not engineering. The business tolerates your team — it doesn&amp;rsquo;t trust it.&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Most teams land at 3-4. That&amp;rsquo;s not a criticism — it&amp;rsquo;s where the industry is. The gap between knowing these practices exist and actually implementing them is where the real work lives.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="where-to-start">Where to start&lt;/h3>
&lt;br>
&lt;p>If you&amp;rsquo;re staring at a low score and feeling overwhelmed, here&amp;rsquo;s what I&amp;rsquo;d suggest: &lt;strong>start with practices 4 and 10.&lt;/strong> CI testing and code review.&lt;/p>
&lt;p>Here&amp;rsquo;s why. Once dbt changes go through pull requests with automated checks, you have the infrastructure to enforce everything else. Contracts? They&amp;rsquo;re a CI check. Quality gates? CI check. Documentation requirements? CI check. SLA validation? CI check. You&amp;rsquo;re not adopting twelve practices — you&amp;rsquo;re building one pipeline that gates on twelve things.&lt;/p>
&lt;p>The canonical architecture appears across virtually every mature team I&amp;rsquo;ve studied: GitHub Actions triggering Slim CI with &lt;code>dbt build -s state:modified+&lt;/code> against per-PR Snowflake schemas, with production manifests stored in S3 for state comparison. Start there. Layer on the remaining practices as your team&amp;rsquo;s maturity grows.&lt;/p>
&lt;p>Three patterns emerged from the research that I think are worth calling out explicitly:&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;As code&amp;rdquo; wins everywhere.&lt;/strong> Documentation-as-code, style-guides-as-code, permissions-as-code, quality-checks-as-code, contracts-as-code. Every manual process that gets codified becomes version-controlled, reviewable, and enforceable. Every one that stays manual eventually drifts.&lt;/p>
&lt;p>&lt;strong>Snowflake&amp;rsquo;s zero-copy cloning is the force multiplier.&lt;/strong> Isolated PR environments, sandbox onboarding, rebuild testing — all of these become cheap and instant with cloning. If you&amp;rsquo;re on Snowflake and not using this feature aggressively, you&amp;rsquo;re leaving the most powerful tool in the shed.&lt;/p>
&lt;p>&lt;strong>Culture matters more than tooling.&lt;/strong> Catalog adoption fails without meeting users in their existing workflows. Data quality requires executive sponsorship and error budgets, not just more tests. Producer-consumer contracts are first and foremost a cultural change. You can buy every tool on this list and still score a 3 if the organisation doesn&amp;rsquo;t value the practices behind them.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-diagram-and-the-warehouse">The diagram and the warehouse&lt;/h3>
&lt;br>
&lt;p>Remember that team — the one with the beautiful diagram and the revenue table nobody could rebuild?&lt;/p>
&lt;p>I caught up with one of the engineers about four months after I&amp;rsquo;d moved on. They&amp;rsquo;d started with exactly what I&amp;rsquo;d pushed for: CI testing and code review. Then they added contracts on their gold-layer models. Then SLAs on their Tier 1 tables. Then a first-week onboarding project for new hires.&lt;/p>
&lt;p>Their score when I first asked these twelve questions? Three. Four months later? Eight. Not perfect. But the difference wasn&amp;rsquo;t really the number. The difference was that when they pulled up that architecture diagram now, it matched what was actually running in Snowflake. The gap between the wall and the warehouse had closed.&lt;/p>
&lt;p>That&amp;rsquo;s what this test is really measuring. Not whether you have the right tools — you probably do. Not whether you know the right practices — you&amp;rsquo;re reading this, so clearly you care. It&amp;rsquo;s measuring whether there&amp;rsquo;s a gap between what you think your data platform looks like and what it actually looks like.&lt;/p>
&lt;p>These twelve questions just make you say it out loud.&lt;/p>
&lt;p>So print it. Score yourself honestly. Share it with your team. And the next time someone asks you how your data platform is doing, give them a number between 1 and 12.&lt;/p>
&lt;p>That number is worth more than any architecture diagram.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Quality</category><category>Leadership</category><category>Data Engineering</category><category>dbt</category><category>Snowflake</category><category>GitHub Actions</category><category>AWS</category><category>Data Quality</category><category>CI/CD</category><category>Data Contracts</category></item><item><title>The CSV Test Suite Nobody Writes</title><link>https://ghostinthedata.info/posts/2026/2026-03-04-csv-test-suite/</link><pubDate>Wed, 04 Mar 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-03-04-csv-test-suite/</guid><author>Chris Hillman</author><description>How to turn RFC 4180 standards and real-world edge cases into a concrete test suite that catches CSV problems before your pipeline does. A practical guide for producers and consumers.</description><content:encoded>&lt;p>In October 2020, roughly 16,000 positive COVID-19 test results vanished from the UK&amp;rsquo;s public health reporting for nearly a week. Not because the tests weren&amp;rsquo;t run. Not because the labs didn&amp;rsquo;t report them. The results were collected, transmitted, and received — inside CSV files.&lt;/p>
&lt;p>The problem? Public Health England was importing those CSV files into Microsoft Excel&amp;rsquo;s legacy &lt;code>.xls&lt;/code> format. The format has a hard row limit of 65,536. When the files grew past that limit, Excel didn&amp;rsquo;t throw an error. It didn&amp;rsquo;t warn anyone. It just silently dropped the extra rows. Sixteen thousand people who tested positive for a deadly virus during a second wave went untraced. An estimated 50,000 of their contacts were never notified. And the system this happened in? Part of a £12 billion Test and Trace programme.&lt;/p>
&lt;p>I remember reading that story and feeling something beyond frustration. I felt recognition. Because I&amp;rsquo;d seen this pattern before — not at that scale, not with those stakes — but the same fundamental failure. Someone produced a CSV. Someone else consumed it. And nobody, at any point in the chain, had written a test that asked: &lt;em>&amp;ldquo;What happens when this file gets bigger than we expect?&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>That&amp;rsquo;s the moment I became a bit obsessive about CSV testing. Not because CSV is complicated — it&amp;rsquo;s deceptively simple. But because that simplicity breeds a dangerous confidence. Everyone thinks they know how to handle a CSV. Everyone thinks the format is too basic to need a test suite. And that assumption has cost organisations billions of dollars, corrupted scientific research, and — in the case of COVID — put actual lives at risk.&lt;/p>
&lt;/br>
&lt;p>I&amp;rsquo;m telling you this because CSV testing is one of those things that sounds unnecessary until you&amp;rsquo;ve spent a weekend rebuilding a pipeline because a vendor changed their quoting convention and nobody noticed for three weeks. The engineers I&amp;rsquo;ve worked with who do this well — who write proper CSV test suites — don&amp;rsquo;t do it because they&amp;rsquo;re cautious by nature. They do it because they&amp;rsquo;ve been burned. And they decided it wouldn&amp;rsquo;t happen again.&lt;/p>
&lt;p>This article is the test suite I wish someone had handed me years ago. We&amp;rsquo;re going to take RFC 4180, the closest thing CSV has to a standard, and turn it into concrete test cases. We&amp;rsquo;ll build manifest files that make file delivery verifiable. And we&amp;rsquo;ll walk through how to systematically break down everything you know about CSV edge cases — embedded commas, encoding traps, null representation chaos, the lot — into tests you can run during development, not discoveries you make in production at 2am.&lt;/p>
&lt;p>Whether you&amp;rsquo;re producing CSVs for a downstream consumer or receiving them from a vendor who swears the file is &amp;ldquo;fine,&amp;rdquo; this is your playbook.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="why-i-know-how-to-test-isnt-enough">Why &amp;ldquo;I Know How to Test&amp;rdquo; Isn&amp;rsquo;t Enough&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s what I&amp;rsquo;ve noticed when I ask data engineers how they test their CSV files: most of them describe &lt;em>validation&lt;/em>, not &lt;em>testing&lt;/em>. They&amp;rsquo;ll say things like &amp;ldquo;we check the row count&amp;rdquo; or &amp;ldquo;we verify the columns match the schema.&amp;rdquo; And those are good things to do. But they&amp;rsquo;re runtime checks — they catch problems after they&amp;rsquo;ve already arrived at your door.&lt;/p>
&lt;p>Testing is different. Testing happens during development. Testing means deliberately creating malformed files and confirming your system handles them correctly. Testing means asking &amp;ldquo;what&amp;rsquo;s the worst thing a vendor could send me?&amp;rdquo; and then actually building that worst-case file and feeding it through your pipeline.&lt;/p>
&lt;p>The gap between validation and testing is where the expensive surprises live. Validation tells you &lt;em>this specific file&lt;/em> has a problem. Testing tells you &lt;em>your system&lt;/em> has a problem — before any real data gets near it.&lt;/p>
&lt;p>So how do you build a proper CSV test suite? You need a specification to test against. And despite its limitations, that specification is RFC 4180.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="rfc-4180-your-test-specification-and-its-gaps">RFC 4180: Your Test Specification (and Its Gaps)&lt;/h3>
&lt;br>
&lt;p>RFC 4180 was published in October 2005 by Yakov Shafranovich. It&amp;rsquo;s universally treated as &amp;ldquo;the CSV standard,&amp;rdquo; but its opening line tells a different story: it&amp;rsquo;s an &amp;ldquo;Informational&amp;rdquo; memo that &amp;ldquo;does not specify an Internet standard of any kind.&amp;rdquo; It was written to document common practice and register the &lt;code>text/csv&lt;/code> MIME type. That&amp;rsquo;s it.&lt;/p>
&lt;p>But here&amp;rsquo;s the thing — even a flawed specification is better than no specification. RFC 4180 gives us seven concrete rules, and each one translates directly into test cases. Let&amp;rsquo;s walk through them.&lt;/p>
&lt;/br>
&lt;h4 id="rule-1-records-are-separated-by-crlf">Rule 1: Records Are Separated by CRLF&lt;/h4>
&lt;p>The spec mandates &lt;code>\r\n&lt;/code> (carriage return + line feed) as the line ending. Not just &lt;code>\n&lt;/code> (Unix/macOS default). Not just &lt;code>\r&lt;/code> (old Mac).&lt;/p>
&lt;p>&lt;strong>Test cases for producers:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_line_endings_are_crlf&lt;/span>(csv_content: bytes):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Every record must end with CRLF, not LF-only or CR-only.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lines &lt;span style="color:#f92672">=&lt;/span> csv_content&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Check no bare LF exists outside of quoted fields&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i, line &lt;span style="color:#f92672">in&lt;/span> enumerate(lines):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> line, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>i&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">: Found bare LF (Unix line ending). &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;RFC 4180 requires CRLF.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Check no bare CR exists outside of quoted fields&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i, line &lt;span style="color:#f92672">in&lt;/span> enumerate(lines):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> line, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>i&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">: Found bare CR (old Mac line ending). &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;RFC 4180 requires CRLF.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Test cases for consumers:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_consumer_handles_mixed_line_endings&lt;/span>(parser):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Consumer should handle LF, CR, and CRLF gracefully.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test_cases &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;unix_lf&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#34;name,age&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">Alice,30&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">Bob,25&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;windows_crlf&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#34;name,age&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Alice,30&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Bob,25&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;old_mac_cr&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#34;name,age&lt;/span>&lt;span style="color:#ae81ff">\r&lt;/span>&lt;span style="color:#e6db74">Alice,30&lt;/span>&lt;span style="color:#ae81ff">\r&lt;/span>&lt;span style="color:#e6db74">Bob,25&lt;/span>&lt;span style="color:#ae81ff">\r&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;mixed&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#34;name,age&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Alice,30&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">Bob,25&lt;/span>&lt;span style="color:#ae81ff">\r&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> label, content &lt;span style="color:#f92672">in&lt;/span> test_cases&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> parser(content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> len(result) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Line ending style &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>label&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;: Expected 2 data rows, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;got &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(result)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This distinction matters. If you&amp;rsquo;re a producer, your job is to emit compliant files. If you&amp;rsquo;re a consumer, your job is to survive whatever arrives. The test mindset is different for each role — producers test for correctness, consumers test for resilience.&lt;/p>
&lt;/br>
&lt;h4 id="rule-2-the-last-record-may-or-may-not-have-a-trailing-crlf">Rule 2: The Last Record May or May Not Have a Trailing CRLF&lt;/h4>
&lt;p>This sounds trivial, but it catches people. Some systems emit a final newline, others don&amp;rsquo;t. Your parser needs to handle both without creating a phantom empty row at the end.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_trailing_newline_does_not_create_empty_row&lt;/span>(parser):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;A trailing CRLF should not produce an extra empty record.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> with_trailing &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#34;name,age&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Alice,30&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> without_trailing &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#34;name,age&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Alice,30&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> len(parser(with_trailing)) &lt;span style="color:#f92672">==&lt;/span> len(parser(without_trailing))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/br>
&lt;h4 id="rule-3-there-should-be-a-header-row">Rule 3: There Should Be a Header Row&lt;/h4>
&lt;p>The RFC says &amp;ldquo;should&amp;rdquo; — not &amp;ldquo;must.&amp;rdquo; This ambiguity is a source of real problems. Some vendors send headers, some don&amp;rsquo;t, and some send headers that change between deliveries.&lt;/p>
&lt;p>Not having headers isn&amp;rsquo;t always a mistake. From a &lt;strong>security perspective, omitting headers is actually defensible&lt;/strong>. Think about what a header row really is: it&amp;rsquo;s a plaintext schema sitting right there in the first line of your file. If someone intercepts a CSV with columns named &lt;code>ssn&lt;/code>, &lt;code>credit_score&lt;/code>, &lt;code>annual_income&lt;/code>, and &lt;code>account_balance&lt;/code>, they don&amp;rsquo;t need to decode anything. The header tells them exactly what they&amp;rsquo;ve stolen and which column to exploit first.&lt;/p>
&lt;p>Stripping headers from files in transit — especially files containing PII, financial data, or health records — reduces the value of an intercepted file to an attacker. A file full of numbers and strings without column names is significantly harder to weaponise than one with a helpful schema attached.&lt;/p>
&lt;p>But here&amp;rsquo;s the catch: &lt;strong>headerless files only work if you have a reliable mechanism to reconstruct the schema at the consumer end&lt;/strong>. The column definitions need to live somewhere — and that somewhere should be your manifest file or your data contract. The manifest we build later in this article includes a &lt;code>columns&lt;/code> array with names, types, and ordering for exactly this reason. The producer strips the headers, the manifest carries the schema, and the consumer rebuilds the structure on ingestion.&lt;/p>
&lt;p>This means your tests need to handle both scenarios:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_header_row_present_and_matches_schema&lt;/span>(csv_file, expected_columns):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;When headers are expected, verify they match the schema exactly.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#f92672">=&lt;/span> csv_file&lt;span style="color:#f92672">.&lt;/span>readline()&lt;span style="color:#f92672">.&lt;/span>strip()&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;,&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> header &lt;span style="color:#f92672">==&lt;/span> expected_columns, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Header mismatch.&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Expected: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>expected_columns&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Got: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>header&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_no_duplicate_column_names&lt;/span>(csv_file):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Duplicate column names cause silent column overwrites in pandas.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#f92672">=&lt;/span> csv_file&lt;span style="color:#f92672">.&lt;/span>readline()&lt;span style="color:#f92672">.&lt;/span>strip()&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;,&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> duplicates &lt;span style="color:#f92672">=&lt;/span> [h &lt;span style="color:#66d9ef">for&lt;/span> h &lt;span style="color:#f92672">in&lt;/span> header &lt;span style="color:#66d9ef">if&lt;/span> header&lt;span style="color:#f92672">.&lt;/span>count(h) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> duplicates, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Duplicate column names found: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>set(duplicates)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Pandas will silently rename these to name.1, name.2, etc.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_headerless_file_matches_manifest_schema&lt;/span>(csv_file, manifest):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;When headers are intentionally omitted for security,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> the first data row must match the manifest&amp;#39;s column count
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> and the manifest must define column names and types.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> first_row &lt;span style="color:#f92672">=&lt;/span> csv_file&lt;span style="color:#f92672">.&lt;/span>readline()&lt;span style="color:#f92672">.&lt;/span>strip()&lt;span style="color:#f92672">.&lt;/span>split(&lt;span style="color:#e6db74">&amp;#39;,&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected_cols &lt;span style="color:#f92672">=&lt;/span> manifest[&lt;span style="color:#e6db74">&amp;#39;files&amp;#39;&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;columns&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> len(first_row) &lt;span style="color:#f92672">==&lt;/span> len(expected_cols), (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Headerless file has &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(first_row)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> fields but manifest &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;defines &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(expected_cols)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> columns. Without headers, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;column alignment is entirely dependent on the manifest.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Verify manifest actually defines names (not just count)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col_def &lt;span style="color:#f92672">in&lt;/span> expected_cols:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#e6db74">&amp;#39;name&amp;#39;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> col_def, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Manifest column definitions must include &amp;#39;name&amp;#39; — &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;headerless files are only safe if the schema is &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;fully specified in the manifest.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Whether you include headers or not, &lt;strong>the decision must be explicit and documented in your data contract&lt;/strong>. The worst scenario isn&amp;rsquo;t a headerless file or a headered file — it&amp;rsquo;s a file where nobody agreed which one it should be, and the answer changes depending on who ran the export.&lt;/p>
&lt;/br>
&lt;h4 id="rule-4-each-row-should-contain-the-same-number-of-fields">Rule 4: Each Row Should Contain the Same Number of Fields&lt;/h4>
&lt;p>This is where &amp;ldquo;ragged rows&amp;rdquo; come from — and they&amp;rsquo;re surprisingly common. A free-text field containing an unescaped comma silently creates an extra column in that row, shifting everything to the right.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_consistent_column_count&lt;/span>(csv_reader):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Every row must have the same number of fields as the header.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#f92672">=&lt;/span> next(csv_reader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected &lt;span style="color:#f92672">=&lt;/span> len(header)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ragged_rows &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i, row &lt;span style="color:#f92672">in&lt;/span> enumerate(csv_reader, start&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> len(row) &lt;span style="color:#f92672">!=&lt;/span> expected:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ragged_rows&lt;span style="color:#f92672">.&lt;/span>append({
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;row&amp;#39;&lt;/span>: i,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;expected&amp;#39;&lt;/span>: expected,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;got&amp;#39;&lt;/span>: len(row),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;content&amp;#39;&lt;/span>: row[:&lt;span style="color:#ae81ff">3&lt;/span>] &lt;span style="color:#75715e"># First 3 fields for debugging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> ragged_rows, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Found &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(ragged_rows)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> ragged rows. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;First: row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>ragged_rows[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;row&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> has &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>ragged_rows[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;got&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> fields (expected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>expected&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">). &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;This usually means an unescaped comma or newline in the data.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This test is the single most valuable check you can run against a CSV file. If it passes, most of the structural problems aren&amp;rsquo;t present. If it fails, you know exactly where to look.&lt;/p>
&lt;/br>
&lt;h4 id="rules-5-7-quoting-and-escaping">Rules 5-7: Quoting and Escaping&lt;/h4>
&lt;p>These three rules govern how fields handle special characters:&lt;/p>
&lt;ul>
&lt;li>Fields containing commas, double-quotes, or line breaks &lt;strong>must&lt;/strong> be enclosed in double-quotes&lt;/li>
&lt;li>Double-quotes inside a quoted field are escaped by doubling them: &lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/li>
&lt;li>Spaces are part of field data and must not be trimmed&lt;/li>
&lt;/ul>
&lt;p>These rules are where the most insidious bugs hide:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -- Producer tests: Do we generate valid quoted fields? --&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_fields_with_commas_are_quoted&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;A field containing a comma must be wrapped in double-quotes.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> record &lt;span style="color:#f92672">=&lt;/span> generate_csv_row(address&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;456 Oak Ave, Apt 2B&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> record &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;id,&amp;#34;456 Oak Ave, Apt 2B&amp;#34;,other_field&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_fields_with_quotes_are_escaped&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Double-quotes in data must be doubled, and field must be quoted.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> record &lt;span style="color:#f92672">=&lt;/span> generate_csv_row(company&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;Acme &amp;#34;Holdings&amp;#34; Ltd&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#34;Acme &amp;#34;&amp;#34;Holdings&amp;#34;&amp;#34; Ltd&amp;#34;&amp;#39;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> record
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_fields_with_newlines_are_quoted&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;A field containing a line break must be wrapped in double-quotes.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> record &lt;span style="color:#f92672">=&lt;/span> generate_csv_row(notes&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Line one&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Line two&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># The entire field, including the newline, should be inside quotes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#34;Line one&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Line two&amp;#34;&amp;#39;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> record
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -- Consumer tests: Can we parse valid-but-tricky fields? --&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_parse_embedded_comma&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;name,address&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Alice&amp;#34;,&amp;#34;456 Oak Ave, Apt 2B&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> parse(content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> result[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;address&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;456 Oak Ave, Apt 2B&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_parse_embedded_newline&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;name,notes&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Alice&amp;#34;,&amp;#34;Line one&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Line two&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> parse(content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> result[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;notes&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;Line one&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">Line two&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_parse_escaped_quotes&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;name,company&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Alice&amp;#34;,&amp;#34;Acme &amp;#34;&amp;#34;Holdings&amp;#34;&amp;#34; Ltd&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\r\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> parse(content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> result[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;company&amp;#39;&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;Acme &amp;#34;Holdings&amp;#34; Ltd&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That covers the seven RFC rules. But here&amp;rsquo;s the catch — the RFC is only half the story. The really nasty problems come from everything the RFC &lt;em>doesn&amp;rsquo;t&lt;/em> cover.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="testing-what-the-rfc-forgot">Testing What the RFC Forgot&lt;/h3>
&lt;br>
&lt;p>RFC 4180 says nothing about character encoding, data types, null values, or how Excel will mangle your data. These are the gaps where production failures breed. Each one becomes a test category.&lt;/p>
&lt;h4 id="encoding-tests">Encoding Tests&lt;/h4>
&lt;p>The most common encoding problem is invisible: the &lt;strong>UTF-8 BOM&lt;/strong> (Byte Order Mark). Excel on Windows prepends three bytes (&lt;code>EF BB BF&lt;/code>) to CSV files saved as UTF-8. Those bytes become an invisible character attached to your first column name. You try &lt;code>df['Name']&lt;/code> and get a &lt;code>KeyError&lt;/code> because the actual column is &lt;code>'\ufeffName'&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_no_bom_prefix&lt;/span>(csv_path):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;UTF-8 BOM causes invisible characters in the first column name.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, &lt;span style="color:#e6db74">&amp;#39;rb&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> first_bytes &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read(&lt;span style="color:#ae81ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> first_bytes &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\xef\xbb\xbf&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;File contains UTF-8 BOM. This will prepend an invisible &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;character to the first column name and break column lookups.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_file_is_valid_utf8&lt;/span>(csv_path):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Catch encoding mismatches before they become mojibake.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, &lt;span style="color:#e6db74">&amp;#39;rb&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>decode(&lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">UnicodeDecodeError&lt;/span> &lt;span style="color:#66d9ef">as&lt;/span> e:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Try to identify the actual encoding&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> chardet
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> detected &lt;span style="color:#f92672">=&lt;/span> chardet&lt;span style="color:#f92672">.&lt;/span>detect(content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">AssertionError&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;File is not valid UTF-8. Decode error at byte &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>e&lt;span style="color:#f92672">.&lt;/span>start&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Detected encoding: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>detected[&lt;span style="color:#e6db74">&amp;#39;encoding&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;(confidence: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>detected[&lt;span style="color:#e6db74">&amp;#39;confidence&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">.0%&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">). &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;This is likely a Latin-1 or Windows-1252 file.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_no_null_bytes&lt;/span>(csv_path):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Null bytes (0x00) indicate binary data or a UTF-16 file
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> being read as UTF-8.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, &lt;span style="color:#e6db74">&amp;#39;rb&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> null_positions &lt;span style="color:#f92672">=&lt;/span> [i &lt;span style="color:#66d9ef">for&lt;/span> i, b &lt;span style="color:#f92672">in&lt;/span> enumerate(content) &lt;span style="color:#66d9ef">if&lt;/span> b &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> null_positions, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Found &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(null_positions)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> null bytes. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;First at position &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_positions[&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;This might be a UTF-16 encoded file.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you&amp;rsquo;re a &lt;strong>consumer&lt;/strong>, you should also test that your parser survives non-UTF-8 input gracefully — does it raise a clear error, or does it silently produce mojibake? Build test fixtures with Latin-1 encoded files containing accented characters (&lt;code>café&lt;/code>, &lt;code>naïve&lt;/code>, &lt;code>München&lt;/code>) and verify your system either decodes correctly or fails explicitly.&lt;/p>
&lt;h4 id="null-value-tests">Null Value Tests&lt;/h4>
&lt;p>RFC 4180 has no concept of null. An empty field (&lt;code>,,&lt;/code>) is just an empty string. But every system represents null differently, and when you&amp;rsquo;re receiving files from multiple vendors, the chaos compounds.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># These are all real null representations I&amp;#39;ve seen in production&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>NULL_VARIANTS &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Empty string (most common)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NULL&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># MySQL Workbench default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;null&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># JavaScript/JSON convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;None&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Python convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;\N&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># PostgreSQL COPY convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;N/A&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Business user convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;n/a&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Lowercase variant&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NA&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># R convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;#N/A&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Excel formula error&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;-&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Dashboard export convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NaN&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Pandas/NumPy convention&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;(null)&amp;#39;&lt;/span>, &lt;span style="color:#75715e"># Some BI tools&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_null_representation_is_consistent&lt;/span>(csv_path, null_convention&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;All null values should use the agreed convention.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Mixed conventions cause type inference failures.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> csv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, newline&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reader &lt;span style="color:#f92672">=&lt;/span> csv&lt;span style="color:#f92672">.&lt;/span>reader(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#f92672">=&lt;/span> next(reader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> violations &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> row_num, row &lt;span style="color:#f92672">in&lt;/span> enumerate(reader, start&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col_idx, value &lt;span style="color:#f92672">in&lt;/span> enumerate(row):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> value &lt;span style="color:#f92672">in&lt;/span> NULL_VARIANTS &lt;span style="color:#f92672">and&lt;/span> value &lt;span style="color:#f92672">!=&lt;/span> null_convention:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> violations&lt;span style="color:#f92672">.&lt;/span>append({
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;row&amp;#39;&lt;/span>: row_num,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;column&amp;#39;&lt;/span>: header[col_idx],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;value&amp;#39;&lt;/span>: repr(value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;expected&amp;#39;&lt;/span>: repr(null_convention)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> violations, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Found &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(violations)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> non-standard null representations. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;First: row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;row&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;column &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;column&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39; contains &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;value&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> (expected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;expected&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a test you should agree on with your vendor as part of a &lt;strong>data contract&lt;/strong> — which we&amp;rsquo;ll get to shortly. Without explicit agreement, you&amp;rsquo;ll spend more time debugging null handling than you will on actual analysis.&lt;/p>
&lt;h4 id="the-leading-zeros-problem">The Leading Zeros Problem&lt;/h4>
&lt;p>Excel silently strips leading zeros from any field it interprets as numeric. ZIP code &lt;code>07102&lt;/code> becomes &lt;code>7102&lt;/code>. Product code &lt;code>00456&lt;/code> becomes &lt;code>456&lt;/code>. Numbers over 15 digits lose precision permanently. This is not a CSV problem — it&amp;rsquo;s an Excel problem — but it affects every CSV that passes through Excel, which is most of them.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_leading_zeros_preserved&lt;/span>(csv_path, zero_padded_columns):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Fields that should retain leading zeros still have them.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Excel strips these silently on open-and-save.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> csv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, newline&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reader &lt;span style="color:#f92672">=&lt;/span> csv&lt;span style="color:#f92672">.&lt;/span>DictReader(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> row_num, row &lt;span style="color:#f92672">in&lt;/span> enumerate(reader, start&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> zero_padded_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> value &lt;span style="color:#f92672">=&lt;/span> row[col]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> value &lt;span style="color:#f92672">and&lt;/span> &lt;span style="color:#f92672">not&lt;/span> value[&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>isdigit():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span> &lt;span style="color:#75715e"># Skip non-numeric values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> value &lt;span style="color:#f92672">and&lt;/span> value[&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span> &lt;span style="color:#f92672">and&lt;/span> len(value) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">pass&lt;/span> &lt;span style="color:#75715e"># Good — leading zero preserved&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> value &lt;span style="color:#f92672">and&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> zero_padded_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Check against expected length&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># e.g., ZIP should be 5 digits&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_no_scientific_notation&lt;/span>(csv_path, numeric_columns):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Numbers should not be in scientific notation (1.23E+11).
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> This happens when Excel converts long numbers.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> csv&lt;span style="color:#f92672">,&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sci_notation &lt;span style="color:#f92672">=&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>compile(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;^-?\d+\.?\d*[eE][+-]?\d+$&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, newline&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reader &lt;span style="color:#f92672">=&lt;/span> csv&lt;span style="color:#f92672">.&lt;/span>DictReader(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> violations &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> row_num, row &lt;span style="color:#f92672">in&lt;/span> enumerate(reader, start&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> numeric_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> sci_notation&lt;span style="color:#f92672">.&lt;/span>&lt;span style="color:#66d9ef">match&lt;/span>(row[col] &lt;span style="color:#f92672">or&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> violations&lt;span style="color:#f92672">.&lt;/span>append((row_num, col, row[col]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> violations, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Scientific notation found — data may have been &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;corrupted by Excel. First: row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;column &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;, value &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">2&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="date-format-tests">Date Format Tests&lt;/h4>
&lt;p>Is &lt;code>03/04/2024&lt;/code> March 4th or April 3rd? Unless you know the source system&amp;rsquo;s locale, you literally cannot tell for dates where both day and month values are 12 or below. The only safe format is ISO 8601: &lt;code>YYYY-MM-DD&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_dates_are_iso8601&lt;/span>(csv_path, date_columns):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Date fields should use ISO 8601 (YYYY-MM-DD) format.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Ambiguous formats like MM/DD/YYYY vs DD/MM/YYYY
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> are programmatically unresolvable.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> csv&lt;span style="color:#f92672">,&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> iso_pattern &lt;span style="color:#f92672">=&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>compile(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;^\d&lt;/span>&lt;span style="color:#e6db74">{4}&lt;/span>&lt;span style="color:#e6db74">-\d&lt;/span>&lt;span style="color:#e6db74">{2}&lt;/span>&lt;span style="color:#e6db74">-\d&lt;/span>&lt;span style="color:#e6db74">{2}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, newline&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reader &lt;span style="color:#f92672">=&lt;/span> csv&lt;span style="color:#f92672">.&lt;/span>DictReader(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> violations &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> row_num, row &lt;span style="color:#f92672">in&lt;/span> enumerate(reader, start&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> date_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> val &lt;span style="color:#f92672">=&lt;/span> row&lt;span style="color:#f92672">.&lt;/span>get(col, &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> val &lt;span style="color:#f92672">and&lt;/span> &lt;span style="color:#f92672">not&lt;/span> iso_pattern&lt;span style="color:#f92672">.&lt;/span>&lt;span style="color:#66d9ef">match&lt;/span>(val):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> violations&lt;span style="color:#f92672">.&lt;/span>append((row_num, col, val))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> violations, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Non-ISO date format found. First: row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;column &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;, value &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>violations[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">2&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Ambiguous date formats cannot be safely parsed.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="invisible-character-tests">Invisible Character Tests&lt;/h4>
&lt;p>These are the ones that make you question your sanity. Non-breaking spaces (&lt;code>U+00A0&lt;/code>), zero-width spaces (&lt;code>U+200B&lt;/code>), zero-width joiners — they look identical to normal characters (or invisible entirely) but break numeric conversion, string matching, and joins.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>INVISIBLE_CHARS &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u00a0&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Non-breaking space&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u200b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Zero-width space&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u200c&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Zero-width non-joiner&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u200d&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Zero-width joiner&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\ufeff&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;BOM / Zero-width no-break space&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u2028&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Line separator&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u2029&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Paragraph separator&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u00ad&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Soft hyphen&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_no_invisible_characters&lt;/span>(csv_path):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Invisible Unicode characters break joins, type casting,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> and string comparisons while being impossible to spot visually.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(csv_path, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> found &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> char, name &lt;span style="color:#f92672">in&lt;/span> INVISIBLE_CHARS&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> positions &lt;span style="color:#f92672">=&lt;/span> [i &lt;span style="color:#66d9ef">for&lt;/span> i, c &lt;span style="color:#f92672">in&lt;/span> enumerate(content) &lt;span style="color:#66d9ef">if&lt;/span> c &lt;span style="color:#f92672">==&lt;/span> char]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> positions:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> found&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>name&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> (U+&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>ord(char)&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">04X&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">): &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(positions)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> occurrences&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> &lt;span style="color:#f92672">not&lt;/span> found, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Invisible characters detected:&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>join(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; - &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>f&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">for&lt;/span> f &lt;span style="color:#f92672">in&lt;/span> found)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="manifest-files-testing-secure-delivery">Manifest Files: Testing Secure Delivery&lt;/h3>
&lt;br>
&lt;p>All of those tests assume you&amp;rsquo;ve received the file intact. But how do you know? A file could be truncated during transfer, silently corrupted on disk, or swapped out entirely. This is where manifest files come in — they&amp;rsquo;re the contract between producer and consumer.&lt;/p>
&lt;p>A manifest is metadata &lt;em>about&lt;/em> the file that travels alongside it. At minimum, it should contain the filename, a cryptographic checksum, the row count, and the file size. NIST has formally deprecated MD5 and SHA-1, so use SHA-256.&lt;/p>
&lt;p>Here&amp;rsquo;s what a solid manifest looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;manifest_version&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;1.0&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;generated_at&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;2026-03-04T08:00:00Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;source_system&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;vendor-crm&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;batch_id&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;batch-20260304-001&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;files&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;filename&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;customers_20260304.csv&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;sha256&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7...&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;row_count&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">50432&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;row_count_includes_header&amp;#34;&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;file_size_bytes&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">8234567&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;column_count&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">12&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;encoding&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;UTF-8&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;delimiter&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;,&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;quote_char&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;\&amp;#34;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;has_header&amp;#34;&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;columns&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { &lt;span style="color:#f92672">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;id&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;integer&amp;#34;&lt;/span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { &lt;span style="color:#f92672">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;name&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;string&amp;#34;&lt;/span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { &lt;span style="color:#f92672">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;email&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;string&amp;#34;&lt;/span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { &lt;span style="color:#f92672">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;created_date&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;#34;format&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;YYYY-MM-DD&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now here&amp;rsquo;s the test suite that validates a delivery against its manifest:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> hashlib
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">validate_delivery&lt;/span>(manifest_path, data_dir):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Validate a CSV delivery against its manifest.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Returns list of errors. Empty list = valid delivery.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 1. Parse manifest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(manifest_path) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> manifest &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>load(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> file_spec &lt;span style="color:#f92672">in&lt;/span> manifest[&lt;span style="color:#e6db74">&amp;#39;files&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filepath &lt;span style="color:#f92672">=&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>join(data_dir, file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 2. File existence&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">not&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>exists(filepath):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;MISSING: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> not found&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 3. File size&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> actual_size &lt;span style="color:#f92672">=&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>getsize(filepath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> actual_size &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;EMPTY: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> is zero bytes&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> actual_size &lt;span style="color:#f92672">!=&lt;/span> file_spec[&lt;span style="color:#e6db74">&amp;#39;file_size_bytes&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;SIZE MISMATCH: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;expected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;file_size_bytes&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> bytes, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;got &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>actual_size&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 4. Checksum&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sha256 &lt;span style="color:#f92672">=&lt;/span> hashlib&lt;span style="color:#f92672">.&lt;/span>sha256()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(filepath, &lt;span style="color:#e6db74">&amp;#39;rb&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> chunk &lt;span style="color:#f92672">in&lt;/span> iter(&lt;span style="color:#66d9ef">lambda&lt;/span>: f&lt;span style="color:#f92672">.&lt;/span>read(&lt;span style="color:#ae81ff">8192&lt;/span>), &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sha256&lt;span style="color:#f92672">.&lt;/span>update(chunk)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> actual_hash &lt;span style="color:#f92672">=&lt;/span> sha256&lt;span style="color:#f92672">.&lt;/span>hexdigest()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> actual_hash &lt;span style="color:#f92672">!=&lt;/span> file_spec[&lt;span style="color:#e6db74">&amp;#39;sha256&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;CHECKSUM MISMATCH: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;has been modified or corrupted in transit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 5. Row count&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(filepath, encoding&lt;span style="color:#f92672">=&lt;/span>file_spec&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;encoding&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> actual_rows &lt;span style="color:#f92672">=&lt;/span> sum(&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">for&lt;/span> _ &lt;span style="color:#f92672">in&lt;/span> f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected &lt;span style="color:#f92672">=&lt;/span> file_spec[&lt;span style="color:#e6db74">&amp;#39;row_count&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> actual_rows &lt;span style="color:#f92672">!=&lt;/span> expected:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;ROW COUNT MISMATCH: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;expected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>expected&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows, got &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>actual_rows&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 6. Column count and names&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(filepath, encoding&lt;span style="color:#f92672">=&lt;/span>file_spec&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;encoding&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>)) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> csv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reader &lt;span style="color:#f92672">=&lt;/span> csv&lt;span style="color:#f92672">.&lt;/span>reader(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#f92672">=&lt;/span> next(reader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> len(header) &lt;span style="color:#f92672">!=&lt;/span> file_spec[&lt;span style="color:#e6db74">&amp;#39;column_count&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;COLUMN COUNT MISMATCH: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;expected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;column_count&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> columns, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;got &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(header)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#39;columns&amp;#39;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> file_spec:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected_names &lt;span style="color:#f92672">=&lt;/span> [c[&lt;span style="color:#e6db74">&amp;#39;name&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> c &lt;span style="color:#f92672">in&lt;/span> file_spec[&lt;span style="color:#e6db74">&amp;#39;columns&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> header &lt;span style="color:#f92672">!=&lt;/span> expected_names:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors&lt;span style="color:#f92672">.&lt;/span>append(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;COLUMN NAME MISMATCH: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>file_spec[&lt;span style="color:#e6db74">&amp;#39;filename&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; Expected: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>expected_names&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; Got: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>header&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> errors
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The key insight about manifest validation is that it should be a &lt;strong>gate&lt;/strong>, not a check. If the manifest fails, you don&amp;rsquo;t process the file. Period. No &amp;ldquo;well, the row count is close enough.&amp;rdquo; No &amp;ldquo;the checksum is probably fine.&amp;rdquo; Either the delivery matches the contract or it gets quarantined.&lt;/p>
&lt;p>For orchestration tools like Airflow, the pattern looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Airflow DAG pattern: manifest-gated processing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wait_for_manifest &lt;span style="color:#f92672">=&lt;/span> FileSensor(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> task_id&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;wait_for_manifest&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filepath&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/landing/{{ ds_nodash }}/manifest.json&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> poke_interval&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">120&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> timeout&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">7200&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>validate &lt;span style="color:#f92672">=&lt;/span> BranchPythonOperator(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> task_id&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;validate_delivery&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> python_callable&lt;span style="color:#f92672">=&lt;/span>validate_and_route,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Routes to either &amp;#39;load_data&amp;#39; or &amp;#39;quarantine_and_alert&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wait_for_manifest &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> validate &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> [load_data, quarantine_and_alert]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>As a producer&lt;/strong>, you should generate the manifest &lt;em>after&lt;/em> writing the CSV file — computing the checksum on the exact bytes that were written. Ship the manifest alongside the data file. If you&amp;rsquo;re using SFTP or S3, upload the data file first, then the manifest. The consumer watches for the manifest as a &amp;ldquo;ready&amp;rdquo; signal — its presence means the data file is complete.&lt;/p>
&lt;p>&lt;strong>As a consumer&lt;/strong>, you should test your manifest validation against deliberately corrupted scenarios:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_detects_truncated_file&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Simulate a file that was cut short during transfer.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Write a file, then truncate it&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_detects_missing_file&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Manifest references a file that doesn&amp;#39;t exist.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_detects_extra_rows&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;File has more rows than manifest claims —
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> could indicate duplicate data or appended garbage.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_detects_wrong_checksum&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;File was modified after manifest was generated.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">test_rejects_zero_byte_file&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Empty files should never pass validation.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-csv-test-matrix-thinking-like-a-tester">The CSV Test Matrix: Thinking Like a Tester&lt;/h3>
&lt;br>
&lt;p>Most people know about edge cases — embedded commas, encoding problems, null ambiguity — but they don&amp;rsquo;t know how to systematically translate that knowledge into a test suite. They end up with ad hoc checks scattered across their codebase, catching some things and missing others.&lt;/p>
&lt;p>The trick is to think in &lt;strong>categories&lt;/strong>, not individual cases. Every CSV problem falls into one of five categories, and each category has a predictable set of test cases.&lt;/p>
&lt;h4 id="category-1-structural-integrity">Category 1: Structural Integrity&lt;/h4>
&lt;p>&lt;em>Does the file conform to the expected shape?&lt;/em>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Test&lt;/th>
 &lt;th>What It Catches&lt;/th>
 &lt;th>Producer/Consumer&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Consistent column count across all rows&lt;/td>
 &lt;td>Unescaped delimiters in data&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Header row present and matches schema (when expected)&lt;/td>
 &lt;td>Schema drift, renamed columns&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Headerless file matches manifest column count&lt;/td>
 &lt;td>Misaligned data when headers stripped for security&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No duplicate column names&lt;/td>
 &lt;td>Silent column overwrites&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No trailing delimiter creating phantom column&lt;/td>
 &lt;td>Off-by-one column counts&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>File is not empty / not just a header&lt;/td>
 &lt;td>Failed exports, empty result sets&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Row count within expected range&lt;/td>
 &lt;td>Truncation, duplication, data loss&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="category-2-encoding-and-character-safety">Category 2: Encoding and Character Safety&lt;/h4>
&lt;p>&lt;em>Are the bytes what we think they are?&lt;/em>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Test&lt;/th>
 &lt;th>What It Catches&lt;/th>
 &lt;th>Producer/Consumer&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>File is valid UTF-8&lt;/td>
 &lt;td>Latin-1/Windows-1252 masquerading as UTF-8&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No BOM prefix&lt;/td>
 &lt;td>Invisible first-column corruption&lt;/td>
 &lt;td>Producer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No null bytes&lt;/td>
 &lt;td>Binary data or wrong encoding family&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No invisible Unicode characters&lt;/td>
 &lt;td>Broken joins, failed type casts&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No smart quotes or em-dashes&lt;/td>
 &lt;td>Copy-paste from Word/email&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="category-3-quoting-and-escaping">Category 3: Quoting and Escaping&lt;/h4>
&lt;p>&lt;em>Are special characters handled correctly?&lt;/em>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Test&lt;/th>
 &lt;th>What It Catches&lt;/th>
 &lt;th>Producer/Consumer&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Fields with commas are quoted&lt;/td>
 &lt;td>Row splitting, column misalignment&lt;/td>
 &lt;td>Producer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fields with double-quotes use &lt;code>&amp;quot;&amp;quot;&lt;/code> escaping&lt;/td>
 &lt;td>Unclosed quotes consuming multiple rows&lt;/td>
 &lt;td>Producer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fields with newlines are quoted&lt;/td>
 &lt;td>Row count mismatches, multi-line chaos&lt;/td>
 &lt;td>Producer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Consumer survives unclosed quotes gracefully&lt;/td>
 &lt;td>Vendor bugs eating your pipeline&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No backslash escaping (non-standard)&lt;/td>
 &lt;td>Interop failures with strict parsers&lt;/td>
 &lt;td>Producer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="category-4-data-type-safety">Category 4: Data Type Safety&lt;/h4>
&lt;p>&lt;em>Will downstream systems interpret values correctly?&lt;/em>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Test&lt;/th>
 &lt;th>What It Catches&lt;/th>
 &lt;th>Producer/Consumer&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Dates in ISO 8601 format&lt;/td>
 &lt;td>Ambiguous MM/DD vs DD/MM&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Leading zeros preserved&lt;/td>
 &lt;td>Excel corruption of ZIPs, product codes&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>No scientific notation in numeric fields&lt;/td>
 &lt;td>Excel corruption of long numbers&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Consistent null representation&lt;/td>
 &lt;td>Mixed NULL/None/N/A/empty conventions&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Boolean fields use consistent values&lt;/td>
 &lt;td>TRUE/True/true/1/yes/Y chaos&lt;/td>
 &lt;td>Both&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Numeric fields contain only valid numbers&lt;/td>
 &lt;td>Currency symbols, percentage signs, commas in numbers&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="category-5-delivery-integrity">Category 5: Delivery Integrity&lt;/h4>
&lt;p>&lt;em>Did the file arrive intact and complete?&lt;/em>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Test&lt;/th>
 &lt;th>What It Catches&lt;/th>
 &lt;th>Producer/Consumer&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>SHA-256 checksum matches manifest&lt;/td>
 &lt;td>Corruption in transit&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>File size matches manifest&lt;/td>
 &lt;td>Truncation, wrong file&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Row count matches manifest&lt;/td>
 &lt;td>Silent data loss (the COVID problem)&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Column count matches manifest&lt;/td>
 &lt;td>Schema changes without notice&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>File is not a duplicate of previous delivery&lt;/td>
 &lt;td>Accidental re-sends&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Manifest is present and valid JSON&lt;/td>
 &lt;td>Incomplete delivery&lt;/td>
 &lt;td>Consumer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>This matrix is your test plan. Print it out. Stick it on the wall next to your desk. When you&amp;rsquo;re building a new CSV integration, walk through each category and ask: &amp;ldquo;Do I have a test for this?&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="building-test-fixtures-the-art-of-deliberate-breakage">Building Test Fixtures: The Art of Deliberate Breakage&lt;/h3>
&lt;br>
&lt;p>A test suite is only as good as its fixtures. And for CSV testing, your fixtures need to be deliberately, creatively broken. Here&amp;rsquo;s how I build mine.&lt;/p>
&lt;p>Start with a &lt;strong>golden file&lt;/strong> — a perfectly compliant CSV that passes every test. This is your baseline. Then create variations that each break exactly one thing:&lt;/p>



&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 568 553"
 >
 &lt;g transform='translate(8,16)'>
&lt;text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='148' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='164' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='180' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='196' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='212' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='228' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='244' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='260' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='276' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='292' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='308' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='324' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='340' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='356' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='372' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='388' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='404' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='420' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='436' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='452' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='468' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='180' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='276' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='372' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='468' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='276' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='372' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='468' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='32' y='180' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='32' y='196' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='212' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='244' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='260' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='32' y='276' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='32' y='292' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='308' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='324' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='340' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='356' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='32' y='372' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='32' y='388' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='404' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='420' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='436' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='452' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='32' y='468' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='32' y='484' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='500' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='516' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='532' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='180' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='212' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='244' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='260' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='276' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='40' y='292' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='308' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='324' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='340' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='356' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='372' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='40' y='388' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='404' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='420' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='436' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='452' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='468' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='40' y='484' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='500' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='516' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='532' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='48' y='196' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='212' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='244' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='260' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='276' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='48' y='292' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='308' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='324' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='340' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='356' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='372' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='48' y='388' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='404' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='420' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='436' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='452' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='468' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='48' y='484' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='500' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='516' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='532' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='56' y='276' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='372' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='56' y='468' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='180' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='64' y='228' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='64' y='244' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='64' y='260' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='64' y='276' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='64' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='324' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='64' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='356' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='64' y='372' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='64' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='64' y='404' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='64' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='64' y='436' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='64' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='64' y='468' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='64' y='484' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='500' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='64' y='516' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='64' y='532' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='244' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='72' y='260' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='276' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='292' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='72' y='308' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='72' y='324' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='340' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='72' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='72' y='372' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='72' y='404' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='72' y='436' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='72' y='468' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='484' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='72' y='500' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='72' y='516' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='72' y='532' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='80' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='244' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='80' y='260' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='80' y='276' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='80' y='292' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='80' y='308' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='80' y='324' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='80' y='340' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='80' y='356' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='80' y='372' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='80' y='388' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='80' y='404' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='80' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='80' y='436' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='80' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='80' y='468' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='80' y='484' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='80' y='500' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='80' y='516' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='80' y='532' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='88' y='196' fill='currentColor' style='font-size:1em'>8&lt;/text>
&lt;text text-anchor='middle' x='88' y='212' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='88' y='244' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='88' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='276' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='88' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='324' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='88' y='340' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='88' y='356' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='88' y='372' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='88' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='88' y='404' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='88' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='436' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='88' y='468' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='88' y='484' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='88' y='500' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='88' y='516' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='88' y='532' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='96' y='244' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='96' y='260' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='96' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='96' y='308' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='96' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='96' y='340' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='96' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='96' y='372' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='388' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='96' y='404' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='96' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='96' y='436' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='96' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='468' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='96' y='484' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='96' y='500' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='96' y='516' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='96' y='532' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='104' y='196' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='104' y='212' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='104' y='244' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='104' y='260' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='104' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='104' y='308' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='104' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='356' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='104' y='372' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='104' y='404' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='104' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='104' y='436' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='104' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='104' y='484' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='104' y='500' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='104' y='516' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='104' y='532' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='112' y='212' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='112' y='244' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='112' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='340' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='112' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='112' y='372' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='112' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='112' y='404' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='112' y='436' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='112' y='452' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='112' y='484' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='500' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='112' y='516' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='112' y='532' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='120' y='196' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='120' y='212' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='120' y='244' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='260' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='120' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='120' y='308' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='120' y='324' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='120' y='340' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='120' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='120' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='120' y='404' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='120' y='420' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='120' y='436' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='120' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='120' y='484' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='120' y='500' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='120' y='516' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='532' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='128' y='212' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='128' y='244' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='260' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='128' y='292' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='128' y='308' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='128' y='324' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='128' y='340' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='128' y='356' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='128' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='404' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='128' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='128' y='436' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='128' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='484' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='128' y='500' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='516' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='128' y='532' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='196' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='136' y='212' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='136' y='228' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='136' y='244' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='260' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='136' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='136' y='308' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='136' y='324' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='136' y='340' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='136' y='356' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='136' y='388' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='136' y='404' fill='currentColor' style='font-size:1em'>z&lt;/text>
&lt;text text-anchor='middle' x='136' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='136' y='436' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='136' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='136' y='484' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='136' y='500' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='136' y='516' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='136' y='532' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='144' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='144' y='228' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='144' y='244' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='144' y='260' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='144' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='144' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='324' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='144' y='340' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='144' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='144' y='404' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='420' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='144' y='436' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='144' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='144' y='484' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='144' y='500' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='144' y='516' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='144' y='532' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='152' y='228' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='152' y='244' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='152' y='260' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='152' y='292' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='152' y='308' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='152' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='152' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='152' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='152' y='404' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='152' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='152' y='436' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='152' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='152' y='484' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='500' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='516' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='152' y='532' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='160' y='228' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='244' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='160' y='260' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='160' y='308' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='160' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='356' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='404' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='160' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='160' y='436' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='160' y='484' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='160' y='500' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='160' y='516' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='160' y='532' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='84' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='244' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='168' y='260' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='168' y='292' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='168' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='168' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='340' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='168' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='404' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='436' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='500' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='168' y='516' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='532' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='176' y='116' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='176' y='228' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='176' y='260' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='292' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='176' y='308' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='324' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='340' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='356' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='176' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='176' y='404' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='176' y='436' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='176' y='452' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='500' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='516' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='532' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='184' y='116' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='184' y='260' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='184' y='292' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='184' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='324' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='184' y='340' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='184' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='388' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='184' y='404' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='184' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='184' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='184' y='500' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='184' y='516' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='184' y='532' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='260' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='192' y='308' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='340' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='356' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='192' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='192' y='404' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='192' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='500' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='516' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='532' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='200' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='200' y='132' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='200' y='260' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='200' y='292' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='200' y='308' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='200' y='324' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='200' y='340' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='200' y='356' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='200' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='200' y='404' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='200' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='200' y='452' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='200' y='500' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='200' y='516' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='200' y='532' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='208' y='100' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='208' y='116' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='208' y='132' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='208' y='292' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='208' y='308' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='208' y='356' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='208' y='388' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='208' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='500' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='516' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='532' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='116' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='216' y='132' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='148' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='216' y='308' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='420' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='216' y='532' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='224' y='116' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='224' y='132' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='224' y='148' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='224' y='308' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='224' y='356' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='224' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='224' y='532' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='232' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='232' y='148' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='232' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='232' y='532' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='240' y='116' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='240' y='420' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='248' y='116' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='264' y='260' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='36' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='116' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='148' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='164' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='196' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='212' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='228' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='244' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='292' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='308' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='324' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='340' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='356' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='388' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='404' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='420' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='436' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='452' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='484' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='500' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='516' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='272' y='532' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='280' y='260' fill='currentColor' style='font-size:1em'>U&lt;/text>
&lt;text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='288' y='116' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='288' y='148' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='288' y='164' fill='currentColor' style='font-size:1em'>Z&lt;/text>
&lt;text text-anchor='middle' x='288' y='196' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='288' y='212' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='288' y='228' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='288' y='244' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='288' y='260' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='288' y='292' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='288' y='308' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='288' y='324' fill='currentColor' style='font-size:1em'>Q&lt;/text>
&lt;text text-anchor='middle' x='288' y='340' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='288' y='356' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='288' y='388' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='288' y='404' fill='currentColor' style='font-size:1em'>Z&lt;/text>
&lt;text text-anchor='middle' x='288' y='420' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='288' y='436' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='288' y='452' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='288' y='484' fill='currentColor' style='font-size:1em'>F&lt;/text>
&lt;text text-anchor='middle' x='288' y='500' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='288' y='516' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='288' y='532' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='296' y='116' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='296' y='148' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='296' y='164' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='196' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='296' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='296' y='244' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='296' y='260' fill='currentColor' style='font-size:1em'>F&lt;/text>
&lt;text text-anchor='middle' x='296' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='296' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='324' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='296' y='340' fill='currentColor' style='font-size:1em'>H&lt;/text>
&lt;text text-anchor='middle' x='296' y='356' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='296' y='388' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='296' y='404' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='296' y='420' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='296' y='436' fill='currentColor' style='font-size:1em'>U&lt;/text>
&lt;text text-anchor='middle' x='296' y='452' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='296' y='484' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='296' y='500' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='516' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='532' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='304' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='304' y='116' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='304' y='148' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='164' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='304' y='196' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='304' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='304' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='304' y='244' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='260' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='304' y='292' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='304' y='308' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='304' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='304' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='356' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='388' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='304' y='404' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='304' y='420' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='304' y='436' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='304' y='452' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='304' y='484' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='304' y='500' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='516' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='532' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='312' y='36' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='148' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='312' y='164' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='312' y='212' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='312' y='244' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='312' y='260' fill='currentColor' style='font-size:1em'>8&lt;/text>
&lt;text text-anchor='middle' x='312' y='292' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='312' y='308' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='312' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='312' y='356' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='312' y='388' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='312' y='420' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='312' y='436' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='312' y='452' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='312' y='484' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='500' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='516' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='532' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='36' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='116' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='320' y='132' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='320' y='148' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='320' y='196' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='320' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='320' y='244' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='320' y='292' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='320' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='320' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='340' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='388' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='320' y='404' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='320' y='420' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='320' y='436' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='320' y='452' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='320' y='500' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='320' y='516' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='320' y='532' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='20' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='328' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>7&lt;/text>
&lt;text text-anchor='middle' x='328' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='328' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='328' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='328' y='164' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='328' y='196' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='328' y='212' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='328' y='244' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='328' y='260' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='328' y='292' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='328' y='308' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='328' y='340' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='328' y='356' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='388' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='328' y='404' fill='currentColor' style='font-size:1em'>7&lt;/text>
&lt;text text-anchor='middle' x='328' y='420' fill='currentColor' style='font-size:1em'>6&lt;/text>
&lt;text text-anchor='middle' x='328' y='452' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='328' y='484' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='328' y='500' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='516' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='532' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='336' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='36' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='84' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='100' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='336' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='336' y='148' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='336' y='164' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='336' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='212' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='336' y='228' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='336' y='244' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='336' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='336' y='340' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='336' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='336' y='388' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='336' y='404' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='336' y='420' fill='currentColor' style='font-size:1em'>7&lt;/text>
&lt;text text-anchor='middle' x='336' y='436' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='336' y='452' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='336' y='484' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='336' y='500' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='516' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='532' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='344' y='36' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='344' y='84' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='344' y='100' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='344' y='116' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='344' y='148' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='344' y='164' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='196' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='344' y='228' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='344' y='244' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='344' y='260' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='344' y='292' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='344' y='308' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='344' y='324' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='344' y='340' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='344' y='356' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='344' y='388' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='344' y='404' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='344' y='420' fill='currentColor' style='font-size:1em'>8&lt;/text>
&lt;text text-anchor='middle' x='344' y='436' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='344' y='452' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='344' y='484' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='500' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='516' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='532' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='352' y='20' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='352' y='84' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='352' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='352' y='116' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='352' y='132' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='352' y='148' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='352' y='164' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='352' y='212' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='352' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='352' y='260' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='352' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='352' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='356' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='352' y='388' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='352' y='404' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='352' y='420' fill='currentColor' style='font-size:1em'>9&lt;/text>
&lt;text text-anchor='middle' x='352' y='436' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='352' y='452' fill='currentColor' style='font-size:1em'>6&lt;/text>
&lt;text text-anchor='middle' x='352' y='532' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='360' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='360' y='36' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='360' y='68' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='360' y='84' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='360' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='360' y='116' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='164' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='360' y='196' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='360' y='212' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='360' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='360' y='244' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='360' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='360' y='292' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='360' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='360' y='324' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='340' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='360' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='360' y='388' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='360' y='420' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='360' y='436' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='360' y='452' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='360' y='484' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='360' y='500' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='360' y='516' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='368' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='368' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='368' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='368' y='100' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='368' y='132' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='368' y='148' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='244' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='368' y='260' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='368' y='292' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='368' y='308' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='368' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='340' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='368' y='356' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='368' y='404' fill='currentColor' style='font-size:1em'>→&lt;/text>
&lt;text text-anchor='middle' x='368' y='420' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='368' y='436' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='368' y='484' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='368' y='500' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='368' y='516' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='368' y='532' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='376' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='376' y='36' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='100' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='376' y='116' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='376' y='148' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='376' y='212' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='376' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='376' y='244' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='376' y='260' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='376' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='376' y='308' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='376' y='324' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='376' y='340' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='376' y='356' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='376' y='388' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='376' y='420' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='376' y='452' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='376' y='484' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='376' y='500' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='376' y='516' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='532' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='84' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='384' y='100' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='384' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='148' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='384' y='244' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='384' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='384' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='404' fill='currentColor' style='font-size:1em'>7&lt;/text>
&lt;text text-anchor='middle' x='384' y='436' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='384' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='384' y='484' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='384' y='500' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='384' y='516' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='392' y='20' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='392' y='36' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='392' y='84' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='392' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='148' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='392' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='260' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='392' y='308' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='392' y='324' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='392' y='340' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='392' y='356' fill='currentColor' style='font-size:1em'>\&lt;/text>
&lt;text text-anchor='middle' x='392' y='388' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='392' y='404' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='392' y='420' fill='currentColor' style='font-size:1em'>→&lt;/text>
&lt;text text-anchor='middle' x='392' y='436' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='392' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='392' y='484' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='392' y='516' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='392' y='532' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='400' y='36' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='400' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='400' y='116' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='400' y='132' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='400' y='212' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='400' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='400' y='260' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='400' y='292' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='400' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='400' y='324' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='400' y='340' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='400' y='356' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='400' y='388' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='400' y='404' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='400' y='436' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='400' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='400' y='484' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='400' y='500' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='400' y='516' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='400' y='532' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='408' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='408' y='68' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='408' y='84' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='408' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='408' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='408' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='408' y='148' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='408' y='212' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='408' y='228' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='408' y='260' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='408' y='292' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='408' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='408' y='340' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='408' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='404' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='408' y='420' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='408' y='436' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='408' y='484' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='408' y='500' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='408' y='532' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='416' y='68' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='84' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='100' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='416' y='116' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='416' y='132' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='416' y='148' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='212' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='260' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='416' y='308' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='416' y='340' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='416' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='416' y='420' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='416' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='500' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='416' y='516' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='416' y='532' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='424' y='36' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='424' y='68' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='424' y='84' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='100' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='424' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='424' y='148' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='424' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='424' y='260' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='292' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='424' y='308' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='424' y='324' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='340' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='424' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='424' y='388' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='424' y='420' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='424' y='436' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='424' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='424' y='516' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='424' y='532' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='432' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='432' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='432' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='100' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='432' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='132' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='432' y='148' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='432' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='432' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='432' y='260' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='432' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='308' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='432' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='340' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='432' y='356' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='432' y='420' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='432' y='436' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='432' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='432' y='500' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='432' y='516' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='432' y='532' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='440' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='440' y='36' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='440' y='100' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='440' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='440' y='132' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='440' y='212' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='440' y='228' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='440' y='260' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='440' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='440' y='308' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='440' y='324' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='440' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='440' y='388' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='440' y='420' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='440' y='436' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='440' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='440' y='500' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='440' y='516' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='440' y='532' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='448' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='36' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='448' y='68' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='448' y='84' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='448' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='448' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='448' y='148' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='448' y='212' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='448' y='228' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='448' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='356' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='448' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='448' y='420' fill='currentColor' style='font-size:1em'>+&lt;/text>
&lt;text text-anchor='middle' x='448' y='436' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='448' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='448' y='500' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='448' y='516' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='448' y='532' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='456' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='456' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='456' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='100' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='456' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='456' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='456' y='260' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='456' y='292' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='456' y='308' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='456' y='324' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='456' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='456' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='456' y='420' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='456' y='436' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='456' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='456' y='500' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='464' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='464' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='464' y='68' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='464' y='116' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='464' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='464' y='148' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='464' y='212' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='464' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='464' y='260' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='464' y='292' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='464' y='356' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='464' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='464' y='420' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='464' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='464' y='516' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='464' y='532' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='472' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='472' y='68' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='472' y='100' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='472' y='116' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='472' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='472' y='148' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='472' y='212' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='472' y='228' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='472' y='260' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='472' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='472' y='308' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='472' y='324' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='472' y='356' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='472' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='472' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='472' y='516' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='472' y='532' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='480' y='36' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='480' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='480' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='480' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='480' y='132' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='480' y='148' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='480' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='480' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='480' y='260' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='480' y='292' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='480' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='480' y='324' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='480' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='480' y='516' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='480' y='532' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='488' y='20' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='488' y='36' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='488' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='488' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='132' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='488' y='148' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='488' y='212' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='488' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='488' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='488' y='308' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='488' y='388' fill='currentColor' style='font-size:1em'>?&lt;/text>
&lt;text text-anchor='middle' x='488' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='488' y='516' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='532' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='496' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='496' y='36' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='496' y='100' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='496' y='148' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='496' y='212' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='496' y='292' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='496' y='308' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='496' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='496' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='496' y='532' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='504' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='504' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='504' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='504' y='308' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='504' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='504' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='504' y='516' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='512' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='512' y='100' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='512' y='308' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='512' y='324' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='512' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='512' y='516' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='512' y='532' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='520' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='520' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='520' y='516' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='520' y='532' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='528' y='532' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='536' y='516' fill='currentColor' style='font-size:1em'>9&lt;/text>
&lt;text text-anchor='middle' x='536' y='532' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='544' y='516' fill='currentColor' style='font-size:1em'>9&lt;/text>
&lt;text text-anchor='middle' x='552' y='516' fill='currentColor' style='font-size:1em'>9&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div>
&lt;p>Each fixture is a test case. Your producer tests verify you never generate files that look like the broken fixtures. Your consumer tests verify you detect and handle each type of breakage correctly.&lt;/p>
&lt;p>The most valuable fixture? &lt;code>unclosed_quote.csv&lt;/code>. A single unclosed double-quote can cause a parser to consume every subsequent row as part of one giant field — potentially the entire rest of the file. I&amp;rsquo;ve seen this eat 2 million rows. Test for it explicitly.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-to-put-in-your-data-contract">What to Put in Your Data Contract&lt;/h3>
&lt;br>
&lt;p>Tests tell you whether a specific file is good or bad. A &lt;strong>data contract&lt;/strong> tells you what &amp;ldquo;good&amp;rdquo; means. Without one, you&amp;rsquo;re testing against assumptions — and your assumptions will eventually differ from your vendor&amp;rsquo;s.&lt;/p>
&lt;p>Here&amp;rsquo;s what a data contract for CSV exchange should specify. Think of it as the RFC that RFC 4180 forgot to write:&lt;/p>
&lt;p>&lt;strong>File format:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Delimiter (comma, pipe, tab — be explicit)&lt;/li>
&lt;li>Quote character (double-quote)&lt;/li>
&lt;li>Escape method (doubled quotes, per RFC 4180)&lt;/li>
&lt;li>Line ending (CRLF)&lt;/li>
&lt;li>Encoding (UTF-8, no BOM)&lt;/li>
&lt;li>Header row policy (present, omitted for security, or conditional — and if omitted, where the schema lives)&lt;/li>
&lt;li>Column names and order (in manifest if headers are stripped)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Data type conventions:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Date format (ISO 8601: &lt;code>YYYY-MM-DD&lt;/code>)&lt;/li>
&lt;li>Timestamp format (ISO 8601: &lt;code>YYYY-MM-DDTHH:MM:SSZ&lt;/code>)&lt;/li>
&lt;li>Null representation (empty field — no &amp;ldquo;NULL&amp;rdquo;, no &amp;ldquo;N/A&amp;rdquo;)&lt;/li>
&lt;li>Boolean representation (&lt;code>true&lt;/code>/&lt;code>false&lt;/code>)&lt;/li>
&lt;li>Numeric precision (no scientific notation, no thousands separators)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Delivery protocol:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Transfer method (SFTP, S3, etc.)&lt;/li>
&lt;li>Manifest file required (JSON, SHA-256 checksum)&lt;/li>
&lt;li>Signal file or completion marker&lt;/li>
&lt;li>Delivery schedule and SLA&lt;/li>
&lt;li>Who to contact when something breaks&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Change management:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Minimum notice period for schema changes&lt;/li>
&lt;li>Versioning strategy (new columns appended, never reordered)&lt;/li>
&lt;li>Breaking vs non-breaking change definitions&lt;/li>
&lt;/ul>
&lt;p>You don&amp;rsquo;t need a 50-page legal document. A one-page markdown file committed to a shared repository works. The point isn&amp;rsquo;t formality — it&amp;rsquo;s explicitness. When the vendor&amp;rsquo;s export changes and your pipeline breaks at 3am, you want something to point to that says &amp;ldquo;this is what we agreed.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="putting-it-all-together-a-reusable-validation-function">Putting It All Together: A Reusable Validation Function&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s a practical, copy-paste-ready validation function that covers the most critical checks. This isn&amp;rsquo;t a framework — it&amp;rsquo;s a function you can drop into any Python project and start using immediately.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> csv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> hashlib
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> dataclasses &lt;span style="color:#f92672">import&lt;/span> dataclass, field
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> typing &lt;span style="color:#f92672">import&lt;/span> Optional
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">CSVValidationResult&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Results from validating a CSV file.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filepath: str
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> is_valid: bool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> errors: list &lt;span style="color:#f92672">=&lt;/span> field(default_factory&lt;span style="color:#f92672">=&lt;/span>list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warnings: list &lt;span style="color:#f92672">=&lt;/span> field(default_factory&lt;span style="color:#f92672">=&lt;/span>list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stats: dict &lt;span style="color:#f92672">=&lt;/span> field(default_factory&lt;span style="color:#f92672">=&lt;/span>dict)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">add_error&lt;/span>(self, message: str):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>errors&lt;span style="color:#f92672">.&lt;/span>append(message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>is_valid &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">add_warning&lt;/span>(self, message: str):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>warnings&lt;span style="color:#f92672">.&lt;/span>append(message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">validate_csv&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filepath: str,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected_columns: Optional[list] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected_encoding: str &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_columns: Optional[list] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> zero_padded_columns: Optional[list] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> null_convention: str &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_row_count: Optional[int] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min_row_count: Optional[int] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> CSVValidationResult:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Validate a CSV file against common quality checks.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Works for both producers (checking your own output)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> and consumers (checking vendor deliveries).
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> CSVValidationResult(filepath&lt;span style="color:#f92672">=&lt;/span>filepath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># --- Check 1: File exists and isn&amp;#39;t empty ---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">not&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>exists(filepath):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;File not found: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>filepath&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file_size &lt;span style="color:#f92672">=&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>getsize(filepath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> file_size &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(&lt;span style="color:#e6db74">&amp;#34;File is empty (zero bytes)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;file_size_bytes&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> file_size
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># --- Check 2: Encoding ---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(filepath, &lt;span style="color:#e6db74">&amp;#39;rb&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> raw_head &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read(&lt;span style="color:#ae81ff">10000&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># BOM check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> raw_head[:&lt;span style="color:#ae81ff">3&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\xef\xbb\xbf&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_warning(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;File contains UTF-8 BOM — this will corrupt &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;the first column name in most parsers&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Null byte check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\x00&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#f92672">in&lt;/span> raw_head:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;File contains null bytes — likely wrong encoding &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;(e.g., UTF-16 read as UTF-8) or binary data&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Encoding validity&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> raw_full &lt;span style="color:#f92672">=&lt;/span> open(filepath, &lt;span style="color:#e6db74">&amp;#39;rb&amp;#39;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> raw_full&lt;span style="color:#f92672">.&lt;/span>decode(expected_encoding)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">UnicodeDecodeError&lt;/span> &lt;span style="color:#66d9ef">as&lt;/span> e:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;File is not valid &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>expected_encoding&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Decode error at byte position &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>e&lt;span style="color:#f92672">.&lt;/span>start&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># --- Check 3: Structural integrity ---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> encoding &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> raw_head[:&lt;span style="color:#ae81ff">3&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\xef\xbb\xbf&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">else&lt;/span> expected_encoding
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(filepath, newline&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>encoding) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reader &lt;span style="color:#f92672">=&lt;/span> csv&lt;span style="color:#f92672">.&lt;/span>reader(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Header check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#f92672">=&lt;/span> next(reader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> &lt;span style="color:#a6e22e">StopIteration&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(&lt;span style="color:#e6db74">&amp;#34;File has no rows (not even a header)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;column_count&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> len(header)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;columns&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> header
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Duplicate column check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> seen &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> header:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> seen[col] &lt;span style="color:#f92672">=&lt;/span> seen&lt;span style="color:#f92672">.&lt;/span>get(col, &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dupes &lt;span style="color:#f92672">=&lt;/span> {k: v &lt;span style="color:#66d9ef">for&lt;/span> k, v &lt;span style="color:#f92672">in&lt;/span> seen&lt;span style="color:#f92672">.&lt;/span>items() &lt;span style="color:#66d9ef">if&lt;/span> v &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> dupes:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Duplicate column names: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>dupes&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Expected columns check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> expected_columns &lt;span style="color:#f92672">and&lt;/span> header &lt;span style="color:#f92672">!=&lt;/span> expected_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Column mismatch.&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; Expected: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>expected_columns&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; Got: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>header&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Row-level checks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> row_count &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ragged_rows &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> iso_date &lt;span style="color:#f92672">=&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>compile(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;^\d&lt;/span>&lt;span style="color:#e6db74">{4}&lt;/span>&lt;span style="color:#e6db74">-\d&lt;/span>&lt;span style="color:#e6db74">{2}&lt;/span>&lt;span style="color:#e6db74">-\d&lt;/span>&lt;span style="color:#e6db74">{2}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> row_num, row &lt;span style="color:#f92672">in&lt;/span> enumerate(reader, start&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> row_count &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Column count consistency&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> len(row) &lt;span style="color:#f92672">!=&lt;/span> len(header):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ragged_rows&lt;span style="color:#f92672">.&lt;/span>append((row_num, len(row)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Date format check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> date_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> date_columns:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> idx &lt;span style="color:#f92672">=&lt;/span> header&lt;span style="color:#f92672">.&lt;/span>index(col) &lt;span style="color:#66d9ef">if&lt;/span> col &lt;span style="color:#f92672">in&lt;/span> header &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> idx &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span> &lt;span style="color:#f92672">and&lt;/span> row[idx]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">not&lt;/span> iso_date&lt;span style="color:#f92672">.&lt;/span>&lt;span style="color:#66d9ef">match&lt;/span>(row[idx]):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_warning(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Row &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row_num&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">, column &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>col&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;: &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Non-ISO date &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row[idx]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;row_count&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> row_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> ragged_rows:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(ragged_rows)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows have wrong column count. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Expected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(header)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> columns. &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;First bad row: line &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>ragged_rows[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;has &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>ragged_rows[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> fields.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Row count bounds&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> min_row_count &lt;span style="color:#f92672">and&lt;/span> row_count &lt;span style="color:#f92672">&amp;lt;&lt;/span> min_row_count:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Row count &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> is below minimum &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;threshold of &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>min_row_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> max_row_count &lt;span style="color:#f92672">and&lt;/span> row_count &lt;span style="color:#f92672">&amp;gt;&lt;/span> max_row_count:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_error(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Row count &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> exceeds maximum &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;threshold of &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>max_row_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># --- Check 4: Invisible characters ---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> invisible &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u00a0&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Non-breaking space&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\u200b&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Zero-width space&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\ufeff&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;BOM (mid-file)&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text &lt;span style="color:#f92672">=&lt;/span> raw_full&lt;span style="color:#f92672">.&lt;/span>decode(encoding, errors&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;replace&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> char, name &lt;span style="color:#f92672">in&lt;/span> invisible&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> count &lt;span style="color:#f92672">=&lt;/span> text&lt;span style="color:#f92672">.&lt;/span>count(char)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> count &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>add_warning(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Found &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>name&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39; characters (U+&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>ord(char)&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">04X&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># --- Compute checksum for manifest comparison ---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sha256 &lt;span style="color:#f92672">=&lt;/span> hashlib&lt;span style="color:#f92672">.&lt;/span>sha256(raw_full)&lt;span style="color:#f92672">.&lt;/span>hexdigest()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;sha256&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> sha256
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Use it like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>result &lt;span style="color:#f92672">=&lt;/span> validate_csv(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;vendor_delivery/customers_20260304.csv&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expected_columns&lt;span style="color:#f92672">=&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;name&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;email&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;city&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;state&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;zip&amp;#39;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_columns&lt;span style="color:#f92672">=&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;created_date&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;updated_date&amp;#39;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min_row_count&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_row_count&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">100000&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">not&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>is_valid:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> error &lt;span style="color:#f92672">in&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>errors:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;ERROR: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>error&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Route to quarantine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Valid. &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;row_count&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;SHA-256: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>result&lt;span style="color:#f92672">.&lt;/span>stats[&lt;span style="color:#e6db74">&amp;#39;sha256&amp;#39;&lt;/span>][:&lt;span style="color:#ae81ff">16&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Proceed to loading&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-16000-missing-records-taught-us">What 16,000 Missing Records Taught Us&lt;/h3>
&lt;br>
&lt;p>Let&amp;rsquo;s come back to where we started. Sixteen thousand COVID test results, dropped silently because nobody tested what would happen when the file exceeded a row limit. The labs did their job. The CSV was generated correctly. The file was transmitted successfully. Every individual component worked. The system failed because nobody tested the &lt;em>boundaries&lt;/em> — the places where one component&amp;rsquo;s assumptions meet another component&amp;rsquo;s limitations.&lt;/p>
&lt;p>That&amp;rsquo;s what CSV testing is really about. It&amp;rsquo;s not about whether you can parse a comma-separated file — of course you can. It&amp;rsquo;s about whether you&amp;rsquo;ve thought through every place where the producer&amp;rsquo;s assumptions and the consumer&amp;rsquo;s assumptions might diverge. The vendor who exports &lt;code>NULL&lt;/code> as the literal string &amp;ldquo;NULL.&amp;rdquo; The CRM that allows newlines in address fields. The business user who opens your carefully formatted CSV in Excel, saves it, and sends it back with stripped zeros and mangled dates.&lt;/p>
&lt;p>Every one of those is a test case. Every one of those is a boundary you can verify during development instead of discovering in production.&lt;/p>
&lt;p>The engineers who sleep well at night aren&amp;rsquo;t the ones who assume their CSV files are fine. They&amp;rsquo;re the ones who wrote the tests to prove it.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Quality</category><category>CSV</category><category>Data Quality</category><category>Testing</category><category>RFC 4180</category><category>Python</category><category>Data Engineering</category><category>Data Pipelines</category></item><item><title>Write-Audit-Publish with Iceberg Tables in Snowflake</title><link>https://ghostinthedata.info/posts/2026/2026-02-27-wap-iceberg-branching/</link><pubDate>Fri, 27 Feb 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-02-27-wap-iceberg-branching/</guid><author>Chris Hillman</author><description>A hands-on tutorial for implementing the WAP pattern on Apache Iceberg tables in Snowflake — with synthetic data, quality gates, and the zero-I/O publish step that keeps production safe.</description><content:encoded>&lt;p>It was a Tuesday afternoon when the analyst pinged me on Microsoft Teams: &amp;ldquo;Hey, the Total Portfolio numbers just jumped 40% overnight. Did we land a whale?&amp;rdquo;&lt;/p>
&lt;p>We hadn&amp;rsquo;t.&lt;/p>
&lt;p>What actually happened was more mundane and significantly more painful. A schema change in the source system introduced a currency conversion bug. Our pipeline dutifully loaded the corrupted data into production at 3 AM, the dashboards updated by 6 AM, and the Department Head opened her morning report to numbers that looked like champagne-worthy growth.&lt;/p>
&lt;p>Here&amp;rsquo;s the thing — we had data quality tests. They ran &lt;em>after&lt;/em> the data hit production. A classic case of closing the gate after the horse has bolted, kicked over the fence, and trampled the neighbour&amp;rsquo;s garden.&lt;/p>
&lt;p>That incident changed how I think about data pipelines entirely. It led me to the &lt;strong>Write-Audit-Publish&lt;/strong> pattern — a concept championed heavily by Zach Wilson. Zach frames it simply: write to a staging area with an identical schema to production, run your data quality checks, and only publish to production if they pass. It&amp;rsquo;s deceptively simple. And it works.&lt;/p>
&lt;p>I&amp;rsquo;ve &lt;a href="https://ghostinthedata.info/posts/2025/2025-05-18-wap-data-pipelines/" target="_blank" rel="noopener">written about WAP before&lt;/a> using Airflow as the orchestration layer, with traditional staging tables and swap mechanics. That approach is solid, battle-tested, and it&amp;rsquo;ll serve most teams well. But now that Apache Iceberg tables in Snowflake have matured — there&amp;rsquo;s a more elegant way to implement WAP that eliminates one of the pattern&amp;rsquo;s biggest practical headaches: the I/O overhead.&lt;/p>
&lt;p>In this tutorial, we&amp;rsquo;re going to build a complete WAP test harness from scratch on Snowflake Iceberg tables. We&amp;rsquo;ll create synthetic data, simulate a change that introduces bad data, catch it with quality gates, and publish only the clean data to production. Along the way, I&amp;rsquo;ll show you why the zero-copy mechanics of Iceberg&amp;rsquo;s architecture make the publish step insanely fast — and why that speed changes the calculus on when WAP is worth the effort.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="why-the-publish-step-is-where-traditional-wap-hurts">Why the Publish Step Is Where Traditional WAP Hurts&lt;/h3>
&lt;br>
&lt;p>If you&amp;rsquo;ve worked in data engineering for more than a few months, you&amp;rsquo;ve probably built some version of WAP without calling it that. You load data into a staging table, run some checks, then copy the good stuff into production. That works. Sort of.&lt;/p>
&lt;p>The problem is what happens at the publish step. In a traditional staging-table WAP implementation, publishing means physically moving data. You&amp;rsquo;re running an &lt;code>INSERT INTO production SELECT * FROM staging&lt;/code>, or you&amp;rsquo;re doing a table swap that involves Snowflake creating new micro-partitions. Either way, you&amp;rsquo;re generating I/O. You&amp;rsquo;re writing data that already exists in a perfectly good format, to another location, in another format, just so it lives under the right table name.&lt;/p>
&lt;p>For a table with a few million rows, nobody notices. For a table with a few billion rows partitioned across hundreds of files? That publish step becomes a pipeline bottleneck. I&amp;rsquo;ve seen WAP implementations where the audit takes 30 seconds and the publish takes 45 minutes. The quality gate is fast. The bureaucratic data shuffle is slow.&lt;/p>
&lt;p>This is where Iceberg changes the equation.&lt;/p>
&lt;p>Iceberg tables don&amp;rsquo;t store data inside Snowflake&amp;rsquo;s proprietary micro-partition format. The data lives as Parquet files in your external storage — S3, GCS, or Azure Blob. Snowflake manages the metadata that tells it which Parquet files belong to which snapshot of the table. When you do a &lt;code>CLONE&lt;/code> of an Iceberg table in Snowflake, it creates a new metadata pointer to the same underlying Parquet files. &lt;strong>No data moves. No files are copied. No I/O is generated.&lt;/strong>&lt;/p>
&lt;p>And when you &lt;code>SWAP&lt;/code> a validated clone back into production? Again — it&amp;rsquo;s a metadata operation. Snowflake updates which metadata pointer the production table name resolves to. The Parquet files don&amp;rsquo;t move. The snapshots don&amp;rsquo;t change. The swap is essentially instant regardless of table size.&lt;/p>
&lt;p>Let me put that in concrete terms. Say you have a 500 GB Iceberg table with three years of order history. In a traditional WAP setup, publishing validated staging data means writing hundreds of gigabytes to new micro-partitions — that&amp;rsquo;s real compute cost, real wall-clock time, and a real window where downstream consumers might see partial data. With Iceberg&amp;rsquo;s zero-copy clone and swap, the same publish step takes seconds. Not minutes. Seconds. Because you&amp;rsquo;re flipping a pointer, not copying bytes.&lt;/p>
&lt;p>This isn&amp;rsquo;t a marginal improvement. It fundamentally changes when WAP is worth implementing. When publish is expensive, you think twice about adding WAP to every pipeline. When publish is free, the question flips — why &lt;em>wouldn&amp;rsquo;t&lt;/em> you gate every production write with quality checks?&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-conceptual-model-how-wap-works-on-iceberg">The Conceptual Model: How WAP Works on Iceberg&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s the flow we&amp;rsquo;re going to build. Every step maps to actual SQL you can run in Snowflake today:&lt;/p>



&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 504 313"
 >
 &lt;g transform='translate(8,16)'>
&lt;path d='M 424,208 L 432,192' fill='none' stroke='currentColor'>&lt;/path>
&lt;polygon points='128.000000,128.000000 116.000000,122.400002 116.000000,133.600006' fill='currentColor' transform='rotate(90.000000, 120.000000, 128.000000)'>&lt;/polygon>
&lt;circle cx='432' cy='192' r='6' stroke='currentColor' fill='#fff'>&lt;/circle>
&lt;text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>U&lt;/text>
&lt;text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>U&lt;/text>
&lt;text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>U&lt;/text>
&lt;text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='96' y='244' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='96' y='260' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='104' y='196' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='104' y='260' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>H&lt;/text>
&lt;text text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='112' y='260' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>z&lt;/text>
&lt;text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'>✅&lt;/text>
&lt;text text-anchor='middle' x='128' y='260' fill='currentColor' style='font-size:1em'>❌&lt;/text>
&lt;text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='144' y='196' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='144' y='260' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='152' y='212' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='152' y='228' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='152' y='260' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='152' y='276' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='152' y='292' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='160' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='160' y='212' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='160' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='160' y='260' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='160' y='276' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='168' y='212' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='168' y='276' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='168' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='176' y='116' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='176' y='196' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='176' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='176' y='228' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='176' y='260' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='176' y='292' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='184' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='184' y='196' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='184' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='184' y='260' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='184' y='276' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='192' y='36' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='192' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='116' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='192' y='164' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='192' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='212' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='192' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='192' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='276' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='192' y='292' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='200' y='116' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='132' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='200' y='164' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='200' y='196' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='200' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='200' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='200' y='260' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='200' y='276' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='200' y='292' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='208' y='116' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='208' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='208' y='164' fill='currentColor' style='font-size:1em'>K&lt;/text>
&lt;text text-anchor='middle' x='208' y='196' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='208' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='208' y='228' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='208' y='260' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='208' y='276' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='208' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='216' y='116' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='216' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='216' y='164' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='216' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='216' y='276' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='224' y='36' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='224' y='116' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='224' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='224' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='260' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='224' y='276' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='224' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='116' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='232' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='196' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='232' y='212' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='232' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='232' y='260' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='232' y='276' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='292' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='240' y='132' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='240' y='196' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='240' y='212' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='240' y='228' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='240' y='260' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='240' y='276' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='240' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='248' y='68' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='248' y='116' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='248' y='132' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='248' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='248' y='212' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='248' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='248' y='260' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='248' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='256' y='36' fill='currentColor' style='font-size:1em'>→&lt;/text>
&lt;text text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='256' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='256' y='212' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='256' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='256' y='260' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='256' y='276' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='256' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='52' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='264' y='68' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='264' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='264' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='264' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='264' y='276' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='272' y='36' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='272' y='116' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='272' y='196' fill='currentColor' style='font-size:1em'>→&lt;/text>
&lt;text text-anchor='middle' x='272' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='272' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='260' fill='currentColor' style='font-size:1em'>→&lt;/text>
&lt;text text-anchor='middle' x='272' y='276' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='292' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='280' y='36' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='280' y='52' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='280' y='116' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='280' y='212' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='280' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='280' y='292' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='288' y='52' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='196' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='288' y='212' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='288' y='228' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='288' y='260' fill='currentColor' style='font-size:1em'>Q&lt;/text>
&lt;text text-anchor='middle' x='288' y='276' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='292' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='296' y='116' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='296' y='196' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='296' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='296' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='260' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='296' y='276' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='296' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='304' y='36' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='304' y='52' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='196' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='304' y='212' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='304' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='304' y='260' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='304' y='276' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='304' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='312' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='52' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='312' y='116' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='196' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='312' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='260' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='312' y='276' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='292' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='320' y='36' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='320' y='52' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='320' y='116' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='320' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='320' y='212' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='320' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='260' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='320' y='276' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='328' y='52' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='328' y='196' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='328' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='328' y='228' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='328' y='260' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='328' y='276' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='292' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='36' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='336' y='100' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='336' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='336' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='336' y='212' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='260' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='276' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='336' y='292' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='344' y='36' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='344' y='100' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='344' y='116' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='344' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='344' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='260' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='344' y='276' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='344' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='352' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='352' y='100' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='352' y='196' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='352' y='212' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='352' y='260' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='352' y='276' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='352' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='100' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='116' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='360' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='360' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='360' y='276' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='100' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='116' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='368' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='368' y='212' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='276' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='368' y='292' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='376' y='116' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='376' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='376' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='376' y='212' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='376' y='260' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='376' y='276' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='376' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='384' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='196' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='260' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='384' y='276' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='292' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='392' y='116' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='392' y='132' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='392' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='392' y='212' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='392' y='260' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='392' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='400' y='116' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='400' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='400' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='400' y='212' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='400' y='292' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='408' y='116' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='408' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='260' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='408' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='416' y='116' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='196' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='416' y='212' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='416' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='292' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='424' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='424' y='132' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='424' y='196' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='424' y='260' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='424' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='116' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='432' y='132' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='432' y='212' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='432' y='260' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='432' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='440' y='116' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='440' y='196' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='440' y='212' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='440' y='260' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='440' y='292' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='448' y='196' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='448' y='260' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='456' y='196' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='456' y='260' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='464' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='472' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='480' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='488' y='196' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div>
&lt;p>The beauty of this model is that production is &lt;strong>never exposed to unvalidated data&lt;/strong>. The clone is a sandbox. Downstream consumers querying &lt;code>fct_orders&lt;/code> see the same clean data they saw yesterday, right up until the moment we swap — and the swap is atomic. There&amp;rsquo;s no window of inconsistency. No partial writes. No &amp;ldquo;oops, the pipeline failed halfway through the publish.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="setting-up-the-foundation">Setting Up the Foundation&lt;/h3>
&lt;br>
&lt;p>Let&amp;rsquo;s build this thing. You&amp;rsquo;ll need a Snowflake account with access to create Iceberg tables, which means an &lt;strong>external volume&lt;/strong> pointing to your cloud storage and the appropriate privileges.&lt;/p>
&lt;h4 id="prerequisites">Prerequisites&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create our tutorial workspace
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">DATABASE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> wap_tutorial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> wap_tutorial.iceberg_wap;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>USE &lt;span style="color:#66d9ef">DATABASE&lt;/span> wap_tutorial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>USE &lt;span style="color:#66d9ef">SCHEMA&lt;/span> iceberg_wap;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create external volume (adjust for your cloud provider)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- You&amp;#39;ll need to configure the IAM trust relationship first
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- See Snowflake docs for the cloud-specific IAM setup
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">EXTERNAL&lt;/span> VOLUME wap_ext_vol
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> STORAGE_LOCATIONS &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NAME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;wap-iceberg-storage&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> STORAGE_BASE_URL &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://your-bucket/iceberg-wap/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> STORAGE_PROVIDER &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;S3&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> STORAGE_AWS_ROLE_ARN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;arn:aws:iam::123456789012:role/snowflake-iceberg-role&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Verify the volume is healthy
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DESC&lt;/span> &lt;span style="color:#66d9ef">EXTERNAL&lt;/span> VOLUME wap_ext_vol;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If the external volume setup feels unfamiliar, don&amp;rsquo;t stress — the Snowflake documentation for this is thorough. The IAM configuration is the fiddly part, but it&amp;rsquo;s a one-time cost. Once the volume exists, everything downstream is straightforward SQL.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="step-1-write--creating-the-iceberg-table-and-synthetic-data">Step 1: WRITE — Creating the Iceberg Table and Synthetic Data&lt;/h3>
&lt;br>
&lt;p>We&amp;rsquo;re going to simulate an e-commerce order pipeline. This is a scenario practically every data engineer has encountered — daily order data flowing from a transactional system into the warehouse.&lt;/p>
&lt;p>First, create the Iceberg table using Snowflake as the catalog:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create the Iceberg table
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Using Snowflake as the catalog means Snowflake handles
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- all lifecycle maintenance (compaction, metadata updates)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> ICEBERG &lt;span style="color:#66d9ef">TABLE&lt;/span> fct_orders (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> product_id INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> quantity INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unit_price DECIMAL(&lt;span style="color:#ae81ff">10&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currency_code VARCHAR(&lt;span style="color:#ae81ff">3&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_amount DECIMAL(&lt;span style="color:#ae81ff">12&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_status VARCHAR(&lt;span style="color:#ae81ff">20&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> region VARCHAR(&lt;span style="color:#ae81ff">50&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loaded_at TIMESTAMP_NTZ &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CATALOG&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SNOWFLAKE&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EXTERNAL_VOLUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;wap_ext_vol&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BASE_LOCATION &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;fct_orders/&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now let&amp;rsquo;s generate our initial &amp;ldquo;production&amp;rdquo; data. This represents the clean, trusted baseline that&amp;rsquo;s been accumulating for the past 30 days. Five thousand orders, all in USD, no nulls, no negative amounts — a pristine starting point:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Generate 30 days of clean, synthetic order data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- This is our trusted production baseline
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> fct_orders (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id, customer_id, order_date, product_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> quantity, unit_price, currency_code, total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_status, region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROW_NUMBER() OVER (&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> SEQ4()) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">10000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">500&lt;/span>, RANDOM()) &lt;span style="color:#66d9ef">AS&lt;/span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>UNIFORM(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">29&lt;/span>, RANDOM()), &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()) &lt;span style="color:#66d9ef">AS&lt;/span> order_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">50&lt;/span>, RANDOM()) &lt;span style="color:#66d9ef">AS&lt;/span> product_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>, RANDOM()) &lt;span style="color:#66d9ef">AS&lt;/span> quantity,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(UNIFORM(&lt;span style="color:#ae81ff">5&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, RANDOM())::DECIMAL(&lt;span style="color:#ae81ff">10&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> unit_price,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> currency_code,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- total_amount = quantity * unit_price (clean, correct calculation)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ROUND(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>, RANDOM()) &lt;span style="color:#f92672">*&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">5&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, RANDOM())::DECIMAL(&lt;span style="color:#ae81ff">12&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>, RANDOM())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PENDING&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CONFIRMED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SHIPPED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;DELIVERED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CANCELLED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> order_status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>, RANDOM())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;APAC&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;EMEA&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;AMERICAS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;LATAM&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ANZ&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span>(GENERATOR(ROWCOUNT &lt;span style="color:#f92672">=&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">5000&lt;/span>));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s verify our baseline is healthy before we mess it up:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Quick sanity check on our production data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> total_orders,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> customer_id) &lt;span style="color:#66d9ef">AS&lt;/span> unique_customers,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MIN&lt;/span>(order_date) &lt;span style="color:#66d9ef">AS&lt;/span> earliest_order,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(order_date) &lt;span style="color:#66d9ef">AS&lt;/span> latest_order,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(&lt;span style="color:#66d9ef">AVG&lt;/span>(total_amount), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> avg_order_value,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> negative_amounts,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> currency_code &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> non_usd_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> fct_orders;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should see 5,000 orders, zero negative amounts, and zero non-USD orders. This is clean production data. Remember these numbers — because we&amp;rsquo;re about to introduce some chaos.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="simulating-the-change-when-bad-data-arrives">Simulating the Change: When Bad Data Arrives&lt;/h3>
&lt;br>
&lt;p>Now the realistic part. A new batch of 500 orders arrives from the source system. But this batch has problems — the kind of subtle, infuriating problems that slip past casual inspection but wreck downstream analytics.&lt;/p>
&lt;p>First, we create the zero-copy clone. This is our staging sandbox:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a zero-copy clone of production
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- This is a METADATA operation — no Parquet files are copied
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- The clone shares the same underlying data files as production
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> ICEBERG &lt;span style="color:#66d9ef">TABLE&lt;/span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CLONE fct_orders;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That &lt;code>CLONE&lt;/code> just happened in under a second regardless of whether your table is 5,000 rows or 500 million. No data moved. The staging clone points to the same Parquet files in your external storage. Snowflake just created a new metadata entry that says &amp;ldquo;this table currently looks like &lt;em>that&lt;/em> table.&amp;rdquo;&lt;/p>
&lt;p>Now let&amp;rsquo;s load the problematic batch into our staging clone:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Simulate incoming data with realistic bugs:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 1. Some NULL currency codes (source system migration issue)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 2. Some invalid currency codes (mapping error)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 3. Some negative total_amounts (conversion bug)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 4. Some EUR amounts that weren&amp;#39;t actually converted
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> fct_orders_staging (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id, customer_id, order_date, product_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> quantity, unit_price, currency_code, total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_status, region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROW_NUMBER() OVER (&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> SEQ4()) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">20000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">500&lt;/span>, RANDOM()) &lt;span style="color:#66d9ef">AS&lt;/span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>() &lt;span style="color:#66d9ef">AS&lt;/span> order_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">50&lt;/span>, RANDOM()) &lt;span style="color:#66d9ef">AS&lt;/span> product_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>, RANDOM()) &lt;span style="color:#66d9ef">AS&lt;/span> quantity,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(UNIFORM(&lt;span style="color:#ae81ff">5&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, RANDOM())::DECIMAL(&lt;span style="color:#ae81ff">10&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> unit_price,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- HERE&amp;#39;S THE BUG: some orders arrive with wrong currency codes
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">100&lt;/span>, RANDOM()) &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">85&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">100&lt;/span>, RANDOM()) &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#75715e">-- Null currency!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">100&lt;/span>, RANDOM()) &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;INVALID&amp;#39;&lt;/span> &lt;span style="color:#75715e">-- Invalid code!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span> &lt;span style="color:#75715e">-- EUR amount but never converted to USD
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> currency_code,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- And some totals went negative due to a sign error
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">100&lt;/span>, RANDOM()) &lt;span style="color:#f92672">&amp;lt;=&lt;/span> &lt;span style="color:#ae81ff">90&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> ROUND(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>, RANDOM())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">5&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, RANDOM())::DECIMAL(&lt;span style="color:#ae81ff">12&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>), &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> ROUND(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">*&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>, RANDOM())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">5&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span>, RANDOM())::DECIMAL(&lt;span style="color:#ae81ff">12&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>), &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>, RANDOM())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PENDING&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CONFIRMED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SHIPPED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;DELIVERED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CANCELLED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> order_status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> UNIFORM(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">5&lt;/span>, RANDOM())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;APAC&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;EMEA&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;AMERICAS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;LATAM&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ANZ&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">TABLE&lt;/span>(GENERATOR(ROWCOUNT &lt;span style="color:#f92672">=&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The write step is done. We have 500 new orders in our staging clone — some clean, some dirty. And critically, production is completely untouched. Anyone querying &lt;code>fct_orders&lt;/code> right now sees exactly what they saw before. No Department Head is going to message you on Teams this morning about phantom Total Portfolio growth.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="step-2-audit--the-quality-gate-battery">Step 2: AUDIT — The Quality Gate Battery&lt;/h3>
&lt;br>
&lt;p>This is where WAP earns its keep. We run every check we care about against the staging clone. If anything fails, we either fix it or drop the clone and production never sees the bad data. As Zach puts it — downstream pipelines wait on production data to be &lt;em>ready&lt;/em>. Not just &lt;em>present&lt;/em>.&lt;/p>
&lt;p>Let&amp;rsquo;s build a comprehensive audit battery. Each check targets a different failure mode:&lt;/p>
&lt;h4 id="check-1-null-detection-on-critical-fields">Check 1: Null Detection on Critical Fields&lt;/h4>
&lt;p>Nulls in currency or amount fields are deal-breakers. A null currency means we can&amp;rsquo;t convert the amount. A null amount means we can&amp;rsquo;t aggregate Total Portfolio numbers. Either one poisons everything downstream.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NULL_CHECK&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> null_currency,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> total_amount &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> null_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> order_id &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> null_order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> total_amount &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> order_id &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="check-2-valid-currency-codes">Check 2: Valid Currency Codes&lt;/h4>
&lt;p>Only accepted ISO currency codes should appear. Anything else means a mapping went sideways upstream.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;CURRENCY_VALIDATION&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> invalid_currency_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LISTAGG(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> currency_code, &lt;span style="color:#e6db74">&amp;#39;, &amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> invalid_values,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> currency_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;GBP&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;AUD&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;CAD&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;JPY&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="check-3-no-negative-amounts">Check 3: No Negative Amounts&lt;/h4>
&lt;p>A negative &lt;code>total_amount&lt;/code> on an order that isn&amp;rsquo;t a refund is a sign of a calculation bug. This is exactly the kind of error that inflated our Total Portfolio numbers that Tuesday morning.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NEGATIVE_AMOUNT_CHECK&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> negative_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MIN&lt;/span>(total_amount) &lt;span style="color:#66d9ef">AS&lt;/span> worst_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="check-4-duplicate-detection">Check 4: Duplicate Detection&lt;/h4>
&lt;p>Duplicate order IDs mean the source system sent the same batch twice, or our pipeline ran a retry without proper idempotency.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;DUPLICATE_CHECK&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> order_id) &lt;span style="color:#66d9ef">AS&lt;/span> duplicate_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> order_id) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="check-5-volume-anomaly-detection">Check 5: Volume Anomaly Detection&lt;/h4>
&lt;p>If today&amp;rsquo;s batch is wildly larger or smaller than the historical daily average, something structural has changed. Maybe a filter broke, maybe a new source came online, maybe someone&amp;rsquo;s test data leaked into production.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> daily_volumes &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> daily_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>today_volume &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> today_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;VOLUME_ANOMALY&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.today_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(&lt;span style="color:#66d9ef">AVG&lt;/span>(d.daily_count), &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> avg_daily_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> t.today_count &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">AVG&lt;/span>(d.daily_count) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> t.today_count &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#66d9ef">AVG&lt;/span>(d.daily_count) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>.&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_result
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_volumes d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CROSS&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> today_volume t
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.today_count;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="aggregating-the-verdict">Aggregating the Verdict&lt;/h4>
&lt;p>In a real pipeline, you&amp;rsquo;d wrap these checks in a stored procedure or Snowflake Task. Here&amp;rsquo;s the aggregation logic that gives you a single pass/fail decision:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Aggregate all quality checks into a single verdict
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">TEMPORARY&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> audit_results &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> checks &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Null check
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;NULL_CHECK&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> check_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> total_amount &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> &lt;span style="color:#66d9ef">result&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Negative amounts
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;NEGATIVE_AMOUNTS&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>() &lt;span style="color:#66d9ef">AND&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Invalid currency
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;INVALID_CURRENCY&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> (currency_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;GBP&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;AUD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;CAD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;JPY&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Duplicates
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;DUPLICATES&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> order_id) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> checks;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- See the full verdict
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> audit_results;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Check if we&amp;#39;re clear to publish
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> total_checks,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">result&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PASS&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> passed,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">result&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> failed,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">SUM&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">result&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;FAIL&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CLEAR TO PUBLISH&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;BLOCKED — FIX REQUIRED&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> verdict
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> audit_results;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With our deliberately corrupted data, you&amp;rsquo;ll see multiple failures here. Null currencies, invalid codes, negative amounts — exactly the problems we planted. The audit caught everything. Production is still clean.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="step-3-publish--the-happy-path-when-audits-pass">Step 3: PUBLISH — The Happy Path (When Audits Pass)&lt;/h3>
&lt;br>
&lt;p>Let&amp;rsquo;s first walk through what happens when everything is clean. This is the path your pipeline will take 95% of the time — and it&amp;rsquo;s where the Iceberg zero-copy advantage really shines.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- PUBLISH: Swap the validated clone into production
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- This is a METADATA OPERATION — no Parquet files are copied or moved
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- The swap is ATOMIC — downstream sees old data or new data, never partial
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> ICEBERG &lt;span style="color:#66d9ef">TABLE&lt;/span> fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SWAP &lt;span style="color:#66d9ef">WITH&lt;/span> fct_orders_staging;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Clean up — the old production reference is now the staging table name
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> fct_orders_staging;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. Two statements. The &lt;code>SWAP&lt;/code> updated which metadata pointer the table name &lt;code>fct_orders&lt;/code> resolves to. The Parquet files sitting in your S3 bucket didn&amp;rsquo;t budge. No new files were written. No compute was burned copying data. No warehouse credits were consumed on I/O.&lt;/p>
&lt;p>Think about what this means for pipeline design. In a traditional WAP implementation with standard Snowflake tables, the publish step is the expensive one — it&amp;rsquo;s the &lt;code>INSERT INTO ... SELECT *&lt;/code> or the &lt;code>CREATE TABLE AS SELECT&lt;/code> that physically writes data. That cost scales linearly with data volume. Double the table size, double the publish time and cost.&lt;/p>
&lt;p>With Iceberg&amp;rsquo;s zero-copy swap, publish cost is &lt;strong>constant regardless of table size&lt;/strong>. A 10 MB table and a 10 TB table swap in the same amount of time. That&amp;rsquo;s not a rounding error. That&amp;rsquo;s a fundamental shift in the economics of data quality enforcement.&lt;/p>
&lt;p>For context, I timed this on a table with ~200 million rows across several hundred Parquet files. The clone took about a second. The audit checks took about 40 seconds. The swap? Sub-second. The most expensive part of the entire WAP pipeline was the actual data quality logic — which is exactly where you &lt;em>want&lt;/em> the time to be spent.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="step-3-alternate-quarantine--when-audits-fail">Step 3 (Alternate): QUARANTINE — When Audits Fail&lt;/h3>
&lt;br>
&lt;p>Now the realistic scenario. Our audit found problems. We have options.&lt;/p>
&lt;p>&lt;strong>Option A: Drop everything, investigate, retry.&lt;/strong> This is the nuclear option. Production stays on yesterday&amp;rsquo;s data until the source is fixed and a clean batch arrives.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Nuclear option — reject the entire batch
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> fct_orders_staging;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Production is exactly as it was. Nothing changed.
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Option B: Quarantine the bad records, publish the clean ones.&lt;/strong> This is more practical for most teams. You log the failures for investigation, surgically remove the bad data, re-audit, and publish what&amp;rsquo;s clean.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- First, capture the failures for investigation
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- This gives your team evidence to fix the source system
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> quarantine_log &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>() &lt;span style="color:#66d9ef">AS&lt;/span> quarantined_at,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;NULL_CURRENCY&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> currency_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;GBP&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;AUD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;CAD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;JPY&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;INVALID_CURRENCY&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#e6db74">&amp;#39;NEGATIVE_AMOUNT&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> failure_reason
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> currency_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;GBP&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;AUD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;CAD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;JPY&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- See what we caught
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span> failure_reason, &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> record_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> quarantine_log
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> failure_reason
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> record_count &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now remove the bad records from the staging clone and re-validate:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Surgically remove the bad records
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DELETE&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> fct_orders_staging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> currency_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;GBP&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;AUD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;CAD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;JPY&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Re-run the audit against the cleaned staging data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- (Repeat the audit_results query from Step 2)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- If it passes now, publish the clean subset
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> ICEBERG &lt;span style="color:#66d9ef">TABLE&lt;/span> fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SWAP &lt;span style="color:#66d9ef">WITH&lt;/span> fct_orders_staging;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> fct_orders_staging;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The quarantine log is important. It&amp;rsquo;s not enough to just filter out bad data — you need to understand &lt;em>why&lt;/em> it was bad so you can fix the source. Every quarantined record is a symptom. The disease is upstream.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="putting-it-all-together-the-orchestrated-pipeline">Putting It All Together: The Orchestrated Pipeline&lt;/h3>
&lt;br>
&lt;p>In practice, you&amp;rsquo;d wrap this entire flow in a Snowflake Task or an external orchestrator. Here&amp;rsquo;s a stored procedure that encapsulates the full WAP pattern. This is something you can adapt and parameterise for any Iceberg table in your warehouse:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">PROCEDURE&lt;/span> run_wap_pipeline(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target_table VARCHAR,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> staging_table VARCHAR
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">RETURNS&lt;/span> VARCHAR
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LANGUAGE&lt;/span> &lt;span style="color:#66d9ef">SQL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">$$&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">DECLARE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fail_count INT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result_msg VARCHAR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">BEGIN&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- WRITE: Clone production
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">EXECUTE&lt;/span> &lt;span style="color:#66d9ef">IMMEDIATE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;CREATE OR REPLACE ICEBERG TABLE &amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">||&lt;/span> staging_table &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39; CLONE &amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> target_table;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- (Your data loading logic goes here)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">-- INSERT INTO staging_table SELECT ... FROM source
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- AUDIT: Run quality checks
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">INTO&lt;/span> fail_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Null check
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">WHERE&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> IDENTIFIER(:staging_table)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> (currency_code &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> total_amount &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Negative amount check
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">WHERE&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> IDENTIFIER(:staging_table)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> total_amount &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Invalid currency check
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">WHERE&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> IDENTIFIER(:staging_table)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> order_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> currency_code &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;USD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;EUR&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;GBP&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;AUD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;CAD&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;JPY&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">IF&lt;/span> (fail_count &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#66d9ef">THEN&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- PUBLISH: Swap (zero-copy, instant)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">EXECUTE&lt;/span> &lt;span style="color:#66d9ef">IMMEDIATE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ALTER ICEBERG TABLE &amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">||&lt;/span> target_table &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39; SWAP WITH &amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> staging_table;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EXECUTE&lt;/span> &lt;span style="color:#66d9ef">IMMEDIATE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;DROP TABLE IF EXISTS &amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> staging_table;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result_msg :&lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;PUBLISHED — All checks passed. Swap completed.&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- ROLLBACK: Drop the clone
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">EXECUTE&lt;/span> &lt;span style="color:#66d9ef">IMMEDIATE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;DROP TABLE IF EXISTS &amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> staging_table;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result_msg :&lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;BLOCKED — &amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> fail_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39; check(s) failed. Production unchanged.&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">RETURN&lt;/span> result_msg;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">END&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">$$&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Run it
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CALL&lt;/span> run_wap_pipeline(&lt;span style="color:#e6db74">&amp;#39;fct_orders&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;fct_orders_staging&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="a-note-on-iceberg-branching--the-future-path">A Note on Iceberg Branching — The Future Path&lt;/h3>
&lt;br>
&lt;p>If you&amp;rsquo;ve been following the Iceberg ecosystem, you might be wondering about &lt;strong>branching&lt;/strong>. Apache Iceberg&amp;rsquo;s spec includes a branching feature that&amp;rsquo;s purpose-built for WAP — you create a named branch within the same table, write to it in isolation, and fast-forward the main branch when audits pass. It&amp;rsquo;s like a Git pull request for your data.&lt;/p>
&lt;p>The feature exists and it works beautifully in Spark and Flink. You can create branches with &lt;code>ALTER TABLE ... CREATE BRANCH&lt;/code>, write to them via the &lt;code>spark.wap.branch&lt;/code> session config, and publish with &lt;code>CALL system.fast_forward()&lt;/code>. The semantics are cleaner than the clone-and-swap approach because the branch &lt;em>is&lt;/em> the table — just a different lineage of snapshots within the same metadata.&lt;/p>
&lt;p>As of writing this it appears &lt;strong>Snowflake does not natively support Iceberg branching through its SQL engine today.&lt;/strong> The branching and tagging metadata operations are Iceberg spec features implemented in the Java library, with engine integrations for Spark and Flink. Snowflake&amp;rsquo;s Iceberg support gives you full DML, Time Travel, and schema evolution — but not yet the &lt;code>CREATE BRANCH&lt;/code> or &lt;code>fast_forward&lt;/code> operations.&lt;/p>
&lt;p>For teams already running Spark workloads alongside Snowflake, the branching approach is worth exploring now. You run the WAP pipeline in Spark with native Iceberg branching, and Snowflake reads the published data as an externally managed Iceberg table. It&amp;rsquo;s a composable architecture where you push quality enforcement to cheaper compute and keep the warehouse for analytics.&lt;/p>
&lt;p>For teams that are Snowflake-native, the clone-and-swap approach we&amp;rsquo;ve built in this tutorial gives you the vast majority of the benefits today. When Snowflake adds native branching support — and given the competitive landscape with Databricks, I&amp;rsquo;d expect it — we can revisit this with a cleaner implementation. The quality checks and audit battery you build now will carry forward regardless of the underlying publish mechanism.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="best-practices-thatll-save-you-pain">Best Practices That&amp;rsquo;ll Save You Pain&lt;/h3>
&lt;br>
&lt;p>After running WAP patterns in various forms across multiple teams, these are the lessons that kept surfacing.&lt;/p>
&lt;p>&lt;strong>Start with hard gates, evolve to soft gates.&lt;/strong> Initially, any check failure should block publication. Full stop. As your quality checks mature and you understand your data better, you&amp;rsquo;ll learn which checks are hard blockers — null primary keys, negative amounts, schema violations — and which are warnings worth logging but not blocking on — volume slightly above the historical average, a handful of records with an unusual but valid status. Build your audit results as structured data with severity levels so the publish decision can be nuanced.&lt;/p>
&lt;p>&lt;strong>Log quarantined records, always.&lt;/strong> Even when you drop a staging clone, capture the failing records somewhere for investigation. A quarantine table or a dead-letter S3 prefix gives your team the evidence they need to fix the source system — which is the real solution, not just filtering out bad data forever. Every quarantined record should answer three questions: what failed, which check caught it, and when it arrived.&lt;/p>
&lt;p>&lt;strong>Make your quality checks idempotent.&lt;/strong> The audit battery should be safe to run multiple times without side effects. No state mutations, no &amp;ldquo;mark as audited&amp;rdquo; flags that prevent re-runs. If the pipeline fails halfway through auditing, you should be able to restart from the top of the audit phase without creating a new clone.&lt;/p>
&lt;p>&lt;strong>Monitor your audit pass rates over time.&lt;/strong> A pipeline that fails WAP checks once a month is healthy — the pattern is catching occasional source issues. A pipeline that fails WAP checks every other day is telling you something about your upstream data source that needs fixing. Track the pass/fail ratio, the specific checks that fail most often, and the volume of quarantined records. This data is ammunition for the conversation with the source system owners.&lt;/p>
&lt;p>&lt;strong>Don&amp;rsquo;t forget the cleanup.&lt;/strong> Snowflake&amp;rsquo;s &lt;code>CLONE&lt;/code> creates metadata that references the same underlying Parquet files. If you create clones and forget to drop them, you&amp;rsquo;re not duplicating storage (the Parquet files are shared), but you are accumulating metadata that Snowflake tracks. Expired clones should be dropped promptly. Set up a cleanup task that sweeps for orphaned staging tables older than your retention threshold.&lt;/p>
&lt;p>&lt;strong>Tag your successful publishes.&lt;/strong> Not Iceberg tags (Snowflake doesn&amp;rsquo;t support those natively yet), but a metadata log. Keep a table that records every successful WAP publish — the timestamp, the table name, the row count of the new batch, and the audit results. When something goes wrong downstream three weeks from now and someone asks &amp;ldquo;what changed?&amp;rdquo;, that log is your first stop.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-bigger-picture-data-quality-as-a-deploy-gate">The Bigger Picture: Data Quality as a Deploy Gate&lt;/h3>
&lt;br>
&lt;p>Zach Wilson has a line that sticks with me: &amp;ldquo;Data engineering shouldn&amp;rsquo;t have any less engineering rigor than software engineering.&amp;rdquo; He&amp;rsquo;s right. We&amp;rsquo;ve spent years building CI/CD pipelines for application code — linting, unit tests, integration tests, staging environments, approval gates — and then we load data straight into production with a prayer and a Teams message that says &amp;ldquo;run looks good.&amp;rdquo;&lt;/p>
&lt;p>WAP is the data equivalent of a deployment gate. You wouldn&amp;rsquo;t merge a pull request that fails tests. You shouldn&amp;rsquo;t publish data that fails quality checks. The mechanics are different — we&amp;rsquo;re swapping table metadata instead of deploying container images — but the principle is identical: validate before you publish, and make the publish step as cheap and reversible as possible.&lt;/p>
&lt;p>Iceberg tables make the &amp;ldquo;as cheap as possible&amp;rdquo; part genuinely achievable. When publish is a zero-copy metadata operation that completes in under a second regardless of table size, the overhead of WAP effectively disappears. The only cost is the compute for your quality checks — and that&amp;rsquo;s compute you &lt;em>want&lt;/em> to be spending.&lt;/p>
&lt;p>That Tuesday afternoon with the phantom Total Portfolio growth? It cost us three days of investigation, two days of reprocessing historical data, and an unknowable amount of credibility with the Department Head and her team. Implementing WAP on that pipeline would have cost us nothing but a few lines of SQL and a staging clone that gets created and destroyed automatically every morning.&lt;/p>
&lt;p>The pattern works. The tooling is ready. Start where you are, build the quality gates, and let the zero-copy publish mechanics handle the rest.&lt;/p>
&lt;p>Your production data — and your Department Head — will thank you for it.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Quality</category><category>Cloud Architecture</category><category>Apache Iceberg</category><category>Snowflake</category><category>WAP Pattern</category><category>Data Quality</category><category>SQL</category><category>Lakehouse</category><category>Data Pipelines</category><category>Best Practices</category></item><item><title>Healing Tables: When Day-by-Day Backfills Become a Slow-Motion Disaster</title><link>https://ghostinthedata.info/posts/2026/2026-02-07-self-healing/</link><pubDate>Sat, 07 Feb 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-02-07-self-healing/</guid><author>Chris Hillman</author><description>Traditional SCD2 backfilling processes one day at a time, compounding errors and taking forever. The Healing Tables framework offers a better way—rebuild-capable dimensions that can recover from accumulated data quality issues in a single pass.</description><content:encoded>&lt;p>It was 2 AM on a Saturday when I realized we&amp;rsquo;d been loading data wrong for six months.&lt;/p>
&lt;br>
&lt;p>The situation: a customer dimension with three years of history needed to be backfilled after a source system migration. The previous team&amp;rsquo;s approach was straightforward—run the daily incremental process 1,095 times, once for each day of history. They estimated three weeks to complete.&lt;/p>
&lt;p>What they hadn&amp;rsquo;t accounted for was how errors compound. By the time I looked at the data, we had 47,000 records with overlapping date ranges, 12,000 timeline gaps where customers seemed to vanish and reappear, and an unknowable number of missed changes from when source systems updated the same record multiple times in a single day.&lt;/p>
&lt;p>The dimension wasn&amp;rsquo;t just wrong. It was &lt;em>unfixably&lt;/em> wrong using traditional methods. Every incremental run had layered new errors on top of old ones, creating a Jenga tower of data quality issues that couldn&amp;rsquo;t be untangled without starting over.&lt;/p>
&lt;p>That night, I started building what I now call a &lt;strong>Healing Table&lt;/strong>—a dimension that can be completely rebuilt from source data at any point, &amp;ldquo;healing&amp;rdquo; whatever accumulated inconsistencies have crept in over months or years of incremental loads.&lt;/p>
&lt;p>This isn&amp;rsquo;t just a technique for disaster recovery. It&amp;rsquo;s a fundamentally different approach to SCD Type 2 that separates change detection from period construction, processes entire historical ranges in a single pass, and produces dimensions that are deterministically reproducible from source data.&lt;/p>
&lt;p>Here&amp;rsquo;s how it works.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="why-day-by-day-backfills-fail">Why Day-by-Day Backfills Fail&lt;/h2>
&lt;br>
&lt;p>The traditional approach to backfilling historical SCD2 dimensions follows a pattern most data engineers know well: take your daily incremental process, wrap it in a loop, and execute it once for each historical date. Simple, reuses existing code, and catastrophically fragile.&lt;/p>
&lt;p>The problems start immediately.&lt;/p>
&lt;p>&lt;strong>Errors compound across iterations.&lt;/strong> If your change detection logic misses an edge case on January 3rd, that incorrect record becomes the baseline for January 4th&amp;rsquo;s comparison. The error doesn&amp;rsquo;t stay contained—it propagates forward through every subsequent run. By the time you discover the issue months later, you can&amp;rsquo;t simply fix January 3rd because hundreds of downstream records depend on that incorrect state.&lt;/p>
&lt;p>&lt;strong>Performance degrades non-linearly.&lt;/strong> Each daily run needs to compare incoming records against the existing dimension. As the dimension grows through backfill, comparison costs increase. A process that takes 5 minutes for a single day&amp;rsquo;s delta might take 45 minutes per day when the target table contains three years of history. That three-week estimate becomes three months.&lt;/p>
&lt;p>&lt;strong>Source system quirks multiply.&lt;/strong> Real source systems don&amp;rsquo;t change data once per day at a predictable time. They update records multiple times, delete and recreate rows, and occasionally backdate changes. Day-by-day processing either misses these patterns entirely or handles them inconsistently across runs.&lt;/p>
&lt;p>&lt;strong>Recovery requires complete rebuild anyway.&lt;/strong> When—not if—something goes wrong, fixing it requires blowing away the dimension and starting over. But if you&amp;rsquo;re going to rebuild from scratch regardless, why not design for that from the beginning?&lt;/p>
&lt;p>The Healing Tables framework embraces this reality. Instead of trying to prevent rebuilds, it makes them fast, reliable, and deterministic.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="the-six-step-framework">The Six-Step Framework&lt;/h2>
&lt;br>
&lt;p>Healing Tables work by separating two concerns that traditional SCD2 implementations conflate: &lt;strong>change detection&lt;/strong> (identifying when attributes changed) and &lt;strong>period construction&lt;/strong> (building valid time slices with proper start and end dates).&lt;/p>
&lt;p>Traditional approaches detect changes and construct periods simultaneously, comparing incoming records to existing dimension state. This creates tight coupling between current dimension contents and processing logic—if the dimension is wrong, future processing will also be wrong.&lt;/p>
&lt;p>Healing Tables decouple these concerns through a six-step pipeline that operates entirely on source data, constructing the dimension from scratch without reference to any existing target state:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Effectivity Table Creation&lt;/strong> — Extract all change points from sources&lt;/li>
&lt;li>&lt;strong>Time Slice Generation&lt;/strong> — Build date ranges with proper valid_from/valid_to&lt;/li>
&lt;li>&lt;strong>Source Table Joining&lt;/strong> — Conform attributes from multiple sources&lt;/li>
&lt;li>&lt;strong>Hash Computation&lt;/strong> — Enable efficient change detection&lt;/li>
&lt;li>&lt;strong>Row Compression&lt;/strong> — Eliminate consecutive identical states&lt;/li>
&lt;li>&lt;strong>Validation Testing&lt;/strong> — Verify temporal integrity before loading&lt;/li>
&lt;/ol>
&lt;p>Let me walk through each step.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="step-1-building-the-effectivity-table">Step 1: Building the Effectivity Table&lt;/h2>
&lt;br>
&lt;p>The Effectivity Table captures every moment when any tracked attribute changed for any business key. Think of it as a timeline of &amp;ldquo;something happened here&amp;rdquo; markers that we&amp;rsquo;ll later fill in with actual attribute values.&lt;/p>
&lt;p>For sources with explicit timestamps, extraction looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Extract all change points from source
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">WITH&lt;/span> source_ordered &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,credit_limit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,updated_at &lt;span style="color:#66d9ef">AS&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,ROW_NUMBER() OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> updated_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> version_sequence
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> raw_customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updated_at &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Detect actual changes using LAG comparison
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>change_detection &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(customer_name) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(customer_status) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(credit_limit) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_credit_limit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_ordered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Keep only first record OR records where attributes actually changed
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,credit_limit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> change_detection
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> version_sequence &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> customer_name &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">DISTINCT&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> prev_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> customer_status &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">DISTINCT&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> prev_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> credit_limit &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">DISTINCT&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> prev_credit_limit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The critical insight here is filtering to &lt;strong>actual changes only&lt;/strong>. Raw source tables often contain records where nothing meaningful changed—perhaps a batch process touched every row, or a status field was updated and then immediately reverted. Including these non-changes creates unnecessary dimension versions that complicate queries and waste storage.&lt;/p>
&lt;p>When working with daily snapshot sources (where you don&amp;rsquo;t have change timestamps, just daily extracts), the pattern shifts to detecting differences between consecutive snapshots:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> daily_ordered &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,snapshot_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(customer_name) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> snapshot_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(customer_status) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> snapshot_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_customer_snapshots
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,snapshot_date &lt;span style="color:#66d9ef">AS&lt;/span> effective_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_ordered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> prev_name &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#75715e">-- First occurrence
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> customer_name &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">DISTINCT&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> prev_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">OR&lt;/span> customer_status &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">DISTINCT&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> prev_status
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>IS DISTINCT FROM&lt;/code> operator handles NULL comparisons correctly—a common source of bugs in change detection logic. Standard &lt;code>!=&lt;/code> treats NULL as unknown, meaning &lt;code>NULL != 'value'&lt;/code> returns NULL rather than TRUE. &lt;code>IS DISTINCT FROM&lt;/code> treats NULL as a concrete value, giving us the comparison semantics we actually want.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="step-2-generating-time-slices">Step 2: Generating Time Slices&lt;/h2>
&lt;br>
&lt;p>Time Slices transform point-in-time change events into date ranges with &lt;code>valid_from&lt;/code> and &lt;code>valid_to&lt;/code> columns. The LEAD window function does the heavy lifting, calculating when each version expires based on when the next version begins:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> effectivity_data &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id, customer_name, customer_status, change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> effectivity_table
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,customer_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,change_timestamp &lt;span style="color:#66d9ef">AS&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Calculate valid_to from next version&amp;#39;s start date
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ,COALESCE(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LEAD(change_timestamp) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE &lt;span style="color:#e6db74">&amp;#39;9999-12-31&amp;#39;&lt;/span> &lt;span style="color:#75715e">-- High date for current records
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ) &lt;span style="color:#66d9ef">AS&lt;/span> valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Flag current record for easy filtering
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ,&lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> LEAD(change_timestamp) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#66d9ef">FALSE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> is_current
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> effectivity_data
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A few implementation decisions deserve attention here.&lt;/p>
&lt;p>&lt;strong>High dates versus NULLs for current records.&lt;/strong> Using &lt;code>9999-12-31&lt;/code> for current record end dates simplifies BETWEEN queries but can confuse some BI tools that try to calculate date differences. Using NULL for &lt;code>valid_to&lt;/code> has clearer semantic meaning but requires IS NULL predicates everywhere. My recommendation: use the high date for the column value, but also include an explicit &lt;code>is_current&lt;/code> boolean flag. This gives query authors flexibility without forcing them to remember magic date values.&lt;/p>
&lt;p>&lt;strong>Interval conventions matter.&lt;/strong> Use left-closed, right-open intervals where &lt;code>valid_from &amp;lt;= date &amp;lt; valid_to&lt;/code>. This ensures every point in time maps to exactly one dimension version without ambiguity at boundaries. The alternative—inclusive on both ends—creates situations where midnight timestamps belong to two periods simultaneously.&lt;/p>
&lt;p>&lt;strong>Point-in-time lookup pattern:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> d.&lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">@&lt;/span>lookup_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> d.valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> &lt;span style="color:#f92672">@&lt;/span>lookup_date &lt;span style="color:#f92672">&amp;lt;&lt;/span> d.valid_to
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="step-3-joining-multiple-sources">Step 3: Joining Multiple Sources&lt;/h2>
&lt;br>
&lt;p>When dimension attributes come from multiple systems, you need to conform those sources before SCD2 processing begins. The temptation is to load directly from multiple sources into a single target—don&amp;rsquo;t do this. You&amp;rsquo;ll create conflicting versions where the same timestamp has different attribute combinations depending on which source processed first.&lt;/p>
&lt;p>The solution is a &lt;strong>Unified Timeline&lt;/strong> that collects all change points from all sources, then joins each source back using as-of-date logic:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Step 1: Collect ALL change points from ALL sources
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">WITH&lt;/span> unified_change_points &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> customer_id, change_timestamp &lt;span style="color:#66d9ef">FROM&lt;/span> crm_customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> customer_id, change_timestamp &lt;span style="color:#66d9ef">FROM&lt;/span> erp_customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">UNION&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> customer_id, change_timestamp &lt;span style="color:#66d9ef">FROM&lt;/span> web_profiles
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Step 2: Build time slices from unified timeline
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>unified_time_slices &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,change_timestamp &lt;span style="color:#66d9ef">AS&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,COALESCE(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LEAD(change_timestamp) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> change_timestamp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span> &lt;span style="color:#e6db74">&amp;#39;9999-12-31 23:59:59&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unified_change_points
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Step 3: Join each source back using as-of logic
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>final_dimension &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ts.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,ts.valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,ts.valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- CRM attributes (authoritative for name)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ,crm.customer_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- ERP attributes (authoritative for financial data)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ,erp.credit_limit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,erp.payment_terms
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Web attributes (authoritative for contact preferences)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ,web.email_opt_in
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,web.preferred_contact_method
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unified_time_slices ts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> crm_customers crm
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> ts.customer_id &lt;span style="color:#f92672">=&lt;/span> crm.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> crm.change_timestamp &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(change_timestamp)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> crm_customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> customer_id &lt;span style="color:#f92672">=&lt;/span> ts.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> change_timestamp &lt;span style="color:#f92672">&amp;lt;=&lt;/span> ts.valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> erp_customers erp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> ts.customer_id &lt;span style="color:#f92672">=&lt;/span> erp.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> erp.change_timestamp &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(change_timestamp)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> erp_customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> customer_id &lt;span style="color:#f92672">=&lt;/span> ts.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> change_timestamp &lt;span style="color:#f92672">&amp;lt;=&lt;/span> ts.valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> web_profiles web
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> ts.customer_id &lt;span style="color:#f92672">=&lt;/span> web.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> web.change_timestamp &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(change_timestamp)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> web_profiles
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span> customer_id &lt;span style="color:#f92672">=&lt;/span> ts.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> change_timestamp &lt;span style="color:#f92672">&amp;lt;=&lt;/span> ts.valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> final_dimension
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notice the correlated subqueries for each join—we&amp;rsquo;re finding the most recent version from each source that was effective at each unified timeline point. This handles cases where one source updates frequently while another updates rarely.&lt;/p>
&lt;p>&lt;strong>Document attribute ownership explicitly.&lt;/strong> When CRM and ERP both have a customer name field, which wins? When web profile email conflicts with CRM email, which is authoritative? These decisions belong in documentation and code comments, not in tribal knowledge. Build a table:&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Attribute&lt;/th>
 &lt;th>Primary Source&lt;/th>
 &lt;th>Fallback&lt;/th>
 &lt;th>Resolution Rule&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Customer Name&lt;/td>
 &lt;td>CRM&lt;/td>
 &lt;td>ERP&lt;/td>
 &lt;td>CRM always wins&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Credit Limit&lt;/td>
 &lt;td>ERP&lt;/td>
 &lt;td>None&lt;/td>
 &lt;td>ERP authoritative&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Email&lt;/td>
 &lt;td>Web&lt;/td>
 &lt;td>CRM&lt;/td>
 &lt;td>Most recent wins&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Then implement those rules explicitly in your SQL using COALESCE or CASE expressions.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="step-4-hash-computation">Step 4: Hash Computation&lt;/h2>
&lt;br>
&lt;p>Hash-based change detection replaces expensive multi-column comparisons with single-column hash comparisons. The Healing Tables framework uses a &lt;strong>two-hash strategy&lt;/strong>: one hash for business key identification, another for attribute change detection.&lt;/p>
&lt;p>&lt;strong>key_hash&lt;/strong> identifies the business entity. Hash the natural key columns together:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Trino/Starburst syntax
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">lower&lt;/span>(to_hex(sha256(to_utf8(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COALESCE(&lt;span style="color:#66d9ef">CAST&lt;/span>(source_system &lt;span style="color:#66d9ef">AS&lt;/span> VARCHAR), &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>) &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39;|&amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COALESCE(&lt;span style="color:#66d9ef">CAST&lt;/span>(customer_id &lt;span style="color:#66d9ef">AS&lt;/span> VARCHAR), &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)))) &lt;span style="color:#66d9ef">AS&lt;/span> key_hash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>row_hash&lt;/strong> detects when any tracked attribute changes. Hash all business attributes excluding keys and metadata:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">lower&lt;/span>(to_hex(sha256(to_utf8(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COALESCE(customer_name, &lt;span style="color:#e6db74">&amp;#39;^^NULL^^&amp;#39;&lt;/span>) &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39;|&amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COALESCE(customer_status, &lt;span style="color:#e6db74">&amp;#39;^^NULL^^&amp;#39;&lt;/span>) &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39;|&amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COALESCE(&lt;span style="color:#66d9ef">CAST&lt;/span>(credit_limit &lt;span style="color:#66d9ef">AS&lt;/span> VARCHAR), &lt;span style="color:#e6db74">&amp;#39;^^NULL^^&amp;#39;&lt;/span>) &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39;|&amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COALESCE(&lt;span style="color:#66d9ef">CAST&lt;/span>(birth_date &lt;span style="color:#66d9ef">AS&lt;/span> VARCHAR), &lt;span style="color:#e6db74">&amp;#39;^^NULL^^&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)))) &lt;span style="color:#66d9ef">AS&lt;/span> row_hash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>^^NULL^^&lt;/code> placeholder distinguishes NULL values from empty strings—a critical detail that trips up many implementations. Without this, &lt;code>('John', NULL, 'Active')&lt;/code> and &lt;code>('John', '', 'Active')&lt;/code> produce identical hashes despite having semantically different data.&lt;/p>
&lt;p>&lt;strong>Why not use the built-in CONCAT_WS?&lt;/strong> Because CONCAT_WS skips NULL values entirely. The inputs &lt;code>('A', NULL, 'B')&lt;/code> and &lt;code>('A', 'B')&lt;/code> produce identical output, creating false positive matches where none exist. Always wrap columns in COALESCE before concatenation.&lt;/p>
&lt;p>&lt;strong>Column ordering best practice&lt;/strong>: concatenate in alphabetical order. This creates deterministic, reproducible hashes that survive schema evolution—adding a new column doesn&amp;rsquo;t change the hash of existing columns.&lt;/p>
&lt;p>Platform-specific hash functions vary:&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Platform&lt;/th>
 &lt;th>Function&lt;/th>
 &lt;th>Syntax&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Snowflake&lt;/td>
 &lt;td>SHA2&lt;/td>
 &lt;td>&lt;code>SHA2(expression, 256)&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>BigQuery&lt;/td>
 &lt;td>SHA256 + TO_HEX&lt;/td>
 &lt;td>&lt;code>TO_HEX(SHA256(expression))&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Trino/Starburst&lt;/td>
 &lt;td>sha256 + to_hex&lt;/td>
 &lt;td>&lt;code>to_hex(sha256(to_utf8(expression)))&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SQL Server&lt;/td>
 &lt;td>HASHBYTES&lt;/td>
 &lt;td>&lt;code>HASHBYTES('SHA2_256', expression)&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;br>
&lt;br>
&lt;h2 id="step-5-row-compression">Step 5: Row Compression&lt;/h2>
&lt;br>
&lt;p>Row compression eliminates &lt;strong>consecutive duplicate states&lt;/strong>—adjacent time periods where all attributes are identical. This happens more often than you&amp;rsquo;d expect: source systems frequently touch records without changing meaningful data, or changes get reverted within the same processing window.&lt;/p>
&lt;p>The technique is a classic &lt;strong>Islands and Gaps&lt;/strong> pattern. Assign a group identifier to runs of consecutive identical rows, then collapse each group into a single time slice:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Step 1: Detect group boundaries
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">WITH&lt;/span> boundary_detection &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- New group if: periods don&amp;#39;t connect OR attributes changed
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> LAG(valid_to) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#f92672">=&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> row_hash &lt;span style="color:#f92672">=&lt;/span> LAG(row_hash) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#75715e">-- Continuation of previous group
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#75715e">-- Start of new group
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> is_group_start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dimension_with_hashes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Step 2: Assign group IDs via running sum
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>group_assignment &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">SUM&lt;/span>(is_group_start) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ROWS&lt;/span> UNBOUNDED PRECEDING
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> group_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> boundary_detection
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Step 3: Collapse each group to single row
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">MIN&lt;/span>(valid_from) &lt;span style="color:#66d9ef">AS&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">MAX&lt;/span>(valid_to) &lt;span style="color:#66d9ef">AS&lt;/span> valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">MAX&lt;/span>(&lt;span style="color:#66d9ef">CASE&lt;/span> &lt;span style="color:#66d9ef">WHEN&lt;/span> is_current &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">END&lt;/span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> is_current
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">MAX&lt;/span>(customer_name) &lt;span style="color:#66d9ef">AS&lt;/span> customer_name &lt;span style="color:#75715e">-- All identical within group
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ,&lt;span style="color:#66d9ef">MAX&lt;/span>(customer_status) &lt;span style="color:#66d9ef">AS&lt;/span> customer_status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">MAX&lt;/span>(row_hash) &lt;span style="color:#66d9ef">AS&lt;/span> row_hash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">MAX&lt;/span>(key_hash) &lt;span style="color:#66d9ef">AS&lt;/span> key_hash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> compressed_row_count &lt;span style="color:#75715e">-- Audit trail
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> group_assignment
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id, group_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id, valid_from
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Two conditions must &lt;strong>both&lt;/strong> be true for rows to compress:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Temporal contiguity&lt;/strong>: Previous row&amp;rsquo;s &lt;code>valid_to&lt;/code> equals current row&amp;rsquo;s &lt;code>valid_from&lt;/code>&lt;/li>
&lt;li>&lt;strong>Attribute identity&lt;/strong>: &lt;code>row_hash&lt;/code> values match&lt;/li>
&lt;/ol>
&lt;p>The contiguity check prevents incorrectly merging records that happen to have identical attributes but represent separate valid periods. If a customer had credit limit 10000 from January to March, then credit limit 5000 from March to June, then credit limit 10000 again from June to December—those are three distinct business states, not one, even though the first and third have identical attribute values.&lt;/p>
&lt;p>Before compression (6 rows):&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>customer_id&lt;/th>
 &lt;th>valid_from&lt;/th>
 &lt;th>valid_to&lt;/th>
 &lt;th>credit_limit&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2020-01-01&lt;/td>
 &lt;td>2020-01-05&lt;/td>
 &lt;td>40000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2020-01-05&lt;/td>
 &lt;td>2020-01-09&lt;/td>
 &lt;td>40000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2020-01-09&lt;/td>
 &lt;td>2020-01-11&lt;/td>
 &lt;td>30000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2022-01-01&lt;/td>
 &lt;td>2022-03-01&lt;/td>
 &lt;td>30000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2022-03-01&lt;/td>
 &lt;td>2022-05-01&lt;/td>
 &lt;td>30000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2022-05-01&lt;/td>
 &lt;td>2022-06-01&lt;/td>
 &lt;td>30000&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>After compression (3 rows):&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>customer_id&lt;/th>
 &lt;th>valid_from&lt;/th>
 &lt;th>valid_to&lt;/th>
 &lt;th>credit_limit&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2020-01-01&lt;/td>
 &lt;td>2020-01-09&lt;/td>
 &lt;td>40000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2020-01-09&lt;/td>
 &lt;td>2020-01-11&lt;/td>
 &lt;td>30000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1002&lt;/td>
 &lt;td>2022-01-01&lt;/td>
 &lt;td>2022-06-01&lt;/td>
 &lt;td>30000&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>The gap between 2020-01-11 and 2022-01-01 correctly prevents merging the two &amp;ldquo;30000&amp;rdquo; groups—they represent distinct validity periods separated by time when the customer didn&amp;rsquo;t exist in source data.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="step-6-validation-testing">Step 6: Validation Testing&lt;/h2>
&lt;br>
&lt;p>Production SCD2 dimensions require comprehensive validation to ensure temporal integrity. These tests should run after every load and &lt;strong>block pipeline completion on failure&lt;/strong>. Silent failures in dimension loading corrupt downstream fact tables in ways that are extremely difficult to diagnose later.&lt;/p>
&lt;p>&lt;strong>Test 1: Single current record per key&lt;/strong>&lt;/p>
&lt;p>Each business key must have exactly one active (current) record:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- CRITICAL: Fails if any key has multiple current records
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,&lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> current_record_count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> is_current &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">HAVING&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expected: Zero rows returned
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Test 2: No overlapping date ranges&lt;/strong>&lt;/p>
&lt;p>Time periods for the same key must not overlap. This is where I learned a painful lesson: the obvious LEAD-based check is &lt;strong>insufficient&lt;/strong>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- INSUFFICIENT - Only checks adjacent records
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">WITH&lt;/span> overlap_check &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LEAD(valid_from) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> next_valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> overlap_check
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> next_valid_from &lt;span style="color:#f92672">&amp;lt;=&lt;/span> valid_to
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This misses overlaps between non-adjacent records. Records at positions 1 and 3 could overlap even if record 2 doesn&amp;rsquo;t overlap with either. The comprehensive check requires a self-join:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- COMPREHENSIVE - Checks all pairs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span> a.&lt;span style="color:#f92672">*&lt;/span>, b.&lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">INNER&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> dim_customer b
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> a.customer_id &lt;span style="color:#f92672">=&lt;/span> b.customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> a.dbt_scd_id &lt;span style="color:#f92672">!=&lt;/span> b.dbt_scd_id &lt;span style="color:#75715e">-- Different records
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a.valid_from &lt;span style="color:#f92672">&amp;lt;&lt;/span> b.valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> a.valid_to &lt;span style="color:#f92672">&amp;gt;&lt;/span> b.valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expected: Zero rows returned
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Test 3: No timeline gaps&lt;/strong>&lt;/p>
&lt;p>For dimensions requiring continuous history, adjacent periods must connect:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> gap_check &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(valid_to) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,prev_valid_to &lt;span style="color:#66d9ef">AS&lt;/span> gap_start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,valid_from &lt;span style="color:#66d9ef">AS&lt;/span> gap_end
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gap_check
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> prev_valid_to &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> prev_valid_to &lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expected: Zero rows (or documented acceptable gaps)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Test 4: Valid date ordering&lt;/strong>&lt;/p>
&lt;p>Every record must have &lt;code>valid_from &amp;lt; valid_to&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> valid_from &lt;span style="color:#f92672">&amp;gt;=&lt;/span> valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expected: Zero rows
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Test 5: No consecutive duplicate versions&lt;/strong>&lt;/p>
&lt;p>After compression, no adjacent rows should have identical attributes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> version_check &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,row_hash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(row_hash) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_row_hash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ,LAG(valid_to) OVER (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARTITION &lt;span style="color:#66d9ef">BY&lt;/span> customer_id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> valid_from
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> prev_valid_to
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_customer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> version_check
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> row_hash &lt;span style="color:#f92672">=&lt;/span> prev_row_hash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> valid_from &lt;span style="color:#f92672">=&lt;/span> prev_valid_to &lt;span style="color:#75715e">-- Contiguous AND identical
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expected: Zero rows
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In dbt, implement these as reusable tests:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># schema.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">dim_customer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">dbt_utils.unique_combination_of_columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">combination_of_columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">customer_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">valid_from&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">customer_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">unique&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">config&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">where&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;is_current = TRUE&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">valid_from&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">not_null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">valid_to&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">not_null&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="the-self-healing-property">The Self-Healing Property&lt;/h2>
&lt;br>
&lt;p>The name &amp;ldquo;Healing Tables&amp;rdquo; comes from a property that emerges from this design: dimensions built this way can recover from accumulated data quality issues simply by reprocessing source data.&lt;/p>
&lt;p>Traditional incremental SCD2 implementations are &lt;strong>path-dependent&lt;/strong>—the current state depends on the exact sequence of historical loads. If Tuesday&amp;rsquo;s load had a bug, and Wednesday through Friday ran correctly, you can&amp;rsquo;t just re-run Tuesday. Those later loads built on Tuesday&amp;rsquo;s incorrect state.&lt;/p>
&lt;p>Healing Tables are &lt;strong>path-independent&lt;/strong>. The dimension produced depends only on source data contents, not on the history of load operations. Re-run any date range with corrected logic, and the result is identical to what you&amp;rsquo;d get from a full rebuild.&lt;/p>
&lt;p>This property transforms how teams think about data quality issues:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Bug in change detection logic?&lt;/strong> Fix it, reprocess affected date range, done.&lt;/li>
&lt;li>&lt;strong>Source system correction applied retroactively?&lt;/strong> Reprocess includes those corrections automatically.&lt;/li>
&lt;li>&lt;strong>New attribute added to dimension?&lt;/strong> Reprocess rebuilds all historical versions with the new column.&lt;/li>
&lt;li>&lt;strong>Suspected data corruption?&lt;/strong> Rebuild and compare—if results differ, you&amp;rsquo;ve found your problem.&lt;/li>
&lt;/ul>
&lt;p>The dimension becomes &lt;strong>reproducible&lt;/strong> in the same way good software builds are reproducible. Given the same inputs and logic, you always get the same outputs. This isn&amp;rsquo;t just convenient for debugging—it&amp;rsquo;s a fundamental quality characteristic that enables reliable data pipelines.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="when-to-use-this-approach">When to Use This Approach&lt;/h2>
&lt;br>
&lt;p>Healing Tables aren&amp;rsquo;t appropriate for every situation. They work best when:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>You have access to complete source history.&lt;/strong> The pattern assumes you can extract all historical changes, not just deltas. If your source system only provides &amp;ldquo;current state&amp;rdquo; with no history, you&amp;rsquo;ll need a different approach.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Source data is contiguous.&lt;/strong> The timeline construction assumes no gaps in source timestamps. If customers can appear, disappear, and reappear in source data, you&amp;rsquo;ll need additional logic to handle these discontinuities.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Rebuild time is acceptable.&lt;/strong> Processing years of history takes longer than processing a single day&amp;rsquo;s changes. For dimensions with billions of historical rows, full rebuilds might be impractical even with optimization.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Your sources don&amp;rsquo;t have hard deletes.&lt;/strong> The pattern assumes deletions are logical (flagged) rather than physical. If source systems physically remove rows, you&amp;rsquo;ll lose that history.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Traditional incremental approaches remain appropriate for:&lt;/p>
&lt;ul>
&lt;li>Very high-volume dimensions where full rebuilds are impractical&lt;/li>
&lt;li>Real-time or near-real-time requirements&lt;/li>
&lt;li>Situations where source history is unavailable&lt;/li>
&lt;li>Simple dimensions with stable, well-understood source systems&lt;/li>
&lt;/ul>
&lt;p>The choice isn&amp;rsquo;t binary. Many teams use Healing Tables for periodic &amp;ldquo;refresh&amp;rdquo; operations while running standard incremental processes daily. If the daily process accumulates issues, the weekly or monthly heal repairs them.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h2 id="wrapping-up">Wrapping Up&lt;/h2>
&lt;br>
&lt;p>That 2 AM debugging session taught me something important: the traditional approach to SCD2—incremental changes applied to existing dimension state—creates tight coupling between historical load operations and current data quality. Every bug, every edge case, every source system quirk gets baked into the dimension permanently.&lt;/p>
&lt;p>Healing Tables break that coupling. By extracting change points from source data, constructing time slices independently, compressing duplicate states, and validating results before loading, you create dimensions that are deterministically reproducible from source data.&lt;/p>
&lt;p>The six-step framework isn&amp;rsquo;t complicated, but it requires discipline:&lt;/p>
&lt;ol>
&lt;li>Build the Effectivity Table with actual changes only&lt;/li>
&lt;li>Generate Time Slices with proper valid_from/valid_to boundaries&lt;/li>
&lt;li>Join multiple sources using unified timeline logic&lt;/li>
&lt;li>Compute hashes for efficient change detection&lt;/li>
&lt;li>Compress consecutive identical states&lt;/li>
&lt;li>Validate temporal integrity before loading&lt;/li>
&lt;/ol>
&lt;p>Implement those steps correctly, and you&amp;rsquo;ll have dimensions that can heal themselves from whatever accumulated issues have crept in over months or years of operation.&lt;/p>
&lt;p>The next time someone asks you to backfill three years of history by running the daily process 1,095 times, you&amp;rsquo;ll have a better answer.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Data Warehousing</category><category>Data Modelling</category><category>SCD</category><category>Historical Load</category><category>dbt</category><category>SQL</category><category>Data Quality</category><category>Dimensional Modeling</category><category>Delta Lake</category><category>Best Practices</category></item><item><title>For Sooty</title><link>https://ghostinthedata.info/posts/2026/2026-02-03-for-sooty/</link><pubDate>Tue, 03 Feb 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-02-03-for-sooty/</guid><author>Chris Hillman</author><description>A farewell to my best friend. Fourteen years of unconditional love, morning walks, and a warm head on my lap. This one isn't about data.</description><content:encoded>&lt;p>This one isn&amp;rsquo;t about data pipelines. There&amp;rsquo;s no framework, no architecture diagram, no code snippet at the end.&lt;/p>
&lt;p>Yesterday I said goodbye to my best friend.&lt;/p>
&lt;p>Sooty was a miniature schnauzer — nine kilograms of stubbornness, loyalty, and heart. Born October 2011. Gone February 2026. Fourteen years that changed the shape of everything.&lt;/p>
&lt;p>I don&amp;rsquo;t have the right words for this. I&amp;rsquo;m not sure anyone does. But I tried to write something that comes close, and I wanted to share it here — because this is my corner of the internet, and she deserves a place in it.&lt;/p>
&lt;hr>
&lt;br>
&lt;h2 id="sooty">Sooty&lt;/h2>
&lt;br>
&lt;p>There is something about having a dog that nobody warns you about. They don&amp;rsquo;t tell you that the first time she falls asleep with her head on your lap, something inside your chest will rearrange itself, and from then on there will always be a space in you that is exactly the shape of her.&lt;/p>
&lt;p>She is going to know where you are. By the sound of your keys in the door, by the way you sigh when you sit down, by the particular shuffle of your feet on the kitchen floor at six in the morning. She will never once say your name and you will never once doubt that she knows it.&lt;/p>
&lt;p>And every time you come home — every single time, whether you were gone for eight hours or eight minutes — she will greet you at the door with the excitement and joy and emotion of a relative she hasn&amp;rsquo;t seen in years. As though your return is the most remarkable thing that has ever happened. As though the world had gone quiet without you and now, finally, it is loud again.&lt;/p>
&lt;p>She will follow you throughout the house, every room, no matter how small the journey — just to check you&amp;rsquo;re still there. She will sit beside you during every bad movie, every 2am bout of insomnia when the ceiling has nothing useful to offer. She will follow you into rooms you didn&amp;rsquo;t even mean to walk into and look up at you as if to say, &lt;em>Well? We&amp;rsquo;re here now. What&amp;rsquo;s the plan?&lt;/em>&lt;/p>
&lt;p>She is your partner in crime before you even commit one. She will be at the door before you even reach for the leash. She will bark at the mailman every time she sees him, because someone has to warn him — &lt;em>my house&lt;/em> — and she has appointed herself to the role.&lt;/p>
&lt;p>And there will be days when the world does what the world does best, which is to come at you sideways and keep swinging. Days when the meetings don&amp;rsquo;t stop, when the inbox is on fire, when your body feels like it was assembled wrong.&lt;/p>
&lt;p>And on those days, she will not ask you what happened. She will not offer advice. She will press her whole warm ridiculous body against yours and stay. Just stay. And occasionally lick your hand — not to fix you, but to say &lt;em>I&amp;rsquo;m checking. I&amp;rsquo;m still here. Are you still here?&lt;/em>&lt;/p>
&lt;p>And yes, she will also be the reason you cannot sleep in. Every morning a fresh day — a stretch with energy, a shake, and a tail wag, sometimes all at once, as though the night never happened and everything is new again.&lt;/p>
&lt;p>She will remind you that the sun has come up and that this, apparently, is cause for celebration. She will remind you that her bowl is empty and that this, apparently, is an emergency.&lt;/p>
&lt;p>She will stand at the door and look back at you with those unbearable eyes and you will put on your shoes even when you don&amp;rsquo;t want to, and you will step outside even when the sky is grey, and you will walk — because she asked, because she needs you, because somehow the needing is the gift. And every walk a new adventure. She knew where to go. Lead on.&lt;/p>
&lt;p>Sometimes she sniffed where she shouldn&amp;rsquo;t have. Under the fence — and got bitten. The neighbour&amp;rsquo;s cat — and got a claw slap across the nose. But she bounced back up every time, because that is the thing about dogs: they do not hold grudges against the world for being sharp. They just shake it off and go looking for the next thing to love.&lt;/p>
&lt;p>When she was young she would whimsically play throughout the house, tearing from room to room like joy had legs, and then — no matter where she was, middle of the floor en route to the bowl, or outside in the windy grass — she would zonk out. Batteries depleted. Gone. As though living that hard required sleeping that deep.&lt;/p>
&lt;p>And as she got older she would remind you — &lt;em>time for bed, come on&lt;/em> — at 7pm. A bit too early for you, but the right time for her. And you would learn that she was not wrong about most things, and maybe not wrong about this either.&lt;/p>
&lt;p>She will see you through the worst of it. She will be there when the phone call comes. She will be there when you can&amp;rsquo;t get off the couch. She will be there when you forget what it feels like to be okay, lying beside you like a kind of proof — that warmth exists, that loyalty is not a theory, that someone in this world looked at you and thought &lt;em>yes, this one, always this one, no matter what.&lt;/em>&lt;/p>
&lt;p>And she will be there for the best of it too. She will be in the background of your happiest memories, tail going, ears up, a blur of joy in the corner of every photograph. She will teach you that happiness is not complicated. That it lives in tennis balls and puddles and the space between your hand and the top of her head.&lt;/p>
&lt;p>And here is the part they really should warn you about.&lt;/p>
&lt;p>She will teach you that love does not need language. That it does not need to be earned or explained or defended. That it can just exist, simple and enormous, in the weight of a head on your chest, in the sound of breathing that is not yours but that has become as necessary as your own.&lt;/p>
&lt;p>She will teach you all of this, and she will teach you so well that by the time you have to let her go, you will understand exactly what you are losing.&lt;/p>
&lt;p>And I think that is why it is so hard to say goodbye. Not because we didn&amp;rsquo;t know it was coming. But because they spent every single day of their lives making sure we knew exactly how much there was to love.&lt;/p>
&lt;p>And they were right. And we did. And it was everything.&lt;/p>
&lt;br>
&lt;hr>
&lt;p>Rest easy, Sooty. You were the best girl.&lt;/p>
&lt;p>&lt;img src="https://ghostinthedata.info/posts/2026/2026-02-03-for-sooty/images/Sooty2.JPG" alt="Sooty" title="You were the best girl.">&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Personal</category><category>Personal</category><category>Life</category><category>Loss</category><category>Grief</category></item><item><title>What an NBA Coach Can Teach Data Leaders About Building Teams That Actually Work</title><link>https://ghostinthedata.info/posts/2026/2026-02-02-nba-coach-lessons-for-leaders/</link><pubDate>Mon, 02 Feb 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-02-02-nba-coach-lessons-for-leaders/</guid><author>Chris Hillman</author><description>Gregg Popovich built the most sustained dynasty in professional sports through dinners, emotional presence, and relentless standards. Here's how data engineering leaders can apply his approach in an era of remote work and technical ego.</description><content:encoded>&lt;p>I was three hours into a retrospective that had devolved into blame-shifting when the most senior engineer on the team finally spoke up. &amp;ldquo;Look,&amp;rdquo; he said, &amp;ldquo;we can keep pointing fingers at the data model, or we can admit we don&amp;rsquo;t actually trust each other enough to have an honest conversation about what went wrong.&amp;rdquo;&lt;/p>
&lt;p>The room went quiet. He was right.&lt;/p>
&lt;p>That moment stuck with me because it exposed something I&amp;rsquo;ve seen destroy more data teams than bad architecture ever could: the absence of genuine connection between people who spend forty-plus hours a week depending on each other.&lt;/p>
&lt;p>We obsess over tool selection. We debate medallion architectures until our Teams threads reach scroll-fatigue territory. We write detailed runbooks and build elaborate monitoring systems. But we rarely invest in the thing that actually determines whether a data team succeeds or fails—whether the people on it feel like they &lt;em>belong&lt;/em> to something worth sacrificing for.&lt;/p>
&lt;p>A while back, I stumbled across a deep dive into Gregg Popovich&amp;rsquo;s leadership approach. If you don&amp;rsquo;t follow basketball, Pop coached the San Antonio Spurs to five NBA championships across twenty-two consecutive playoff appearances—the most sustained dynasty in modern professional sports. His winning percentage over three decades is unmatched in North American sports.&lt;/p>
&lt;p>And his secret? It wasn&amp;rsquo;t X&amp;rsquo;s and O&amp;rsquo;s. It was wine, tears, and showing up at a player&amp;rsquo;s hotel room at 2 AM to cry with him after his father died.&lt;/p>
&lt;p>What I found wasn&amp;rsquo;t a basketball story at all. It was a masterclass in building teams that actually work. And everything in it applies to leading data engineers—if you&amp;rsquo;re willing to do the uncomfortable work.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-problem-with-how-we-lead-data-teams">The problem with how we lead data teams&lt;/h3>
&lt;p>When managing data team&amp;rsquo;s: we&amp;rsquo;ve inherited a leadership model built for factory floors and sales quotas, then awkwardly grafted it onto creative, highly collaborative technical work.&lt;/p>
&lt;p>The standard playbook goes something like this:&lt;/p>
&lt;p>&lt;strong>Set clear goals.&lt;/strong> Define KPIs. Track story points. Measure velocity. Hold quarterly reviews where you show charts that go up and to the right (or explain why they didn&amp;rsquo;t).&lt;/p>
&lt;p>&lt;strong>Provide feedback.&lt;/strong> Annual performance reviews. Maybe some lightweight 360 input. A few uncomfortable conversations about &amp;ldquo;areas for growth.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Remove blockers.&lt;/strong> Clear the technical debt backlog. Negotiate for headcount. Shield the team from stakeholder noise.&lt;/p>
&lt;p>None of this is wrong, exactly. But it&amp;rsquo;s not sufficient.&lt;/p>
&lt;p>Data teams live in a peculiar tension. The work is deeply technical—you need people who genuinely understand distributed systems, query optimization, and schema design. But the &lt;em>outcomes&lt;/em> are entirely social—dashboards nobody uses, pipelines that don&amp;rsquo;t answer the questions stakeholders actually have, data quality that erodes trust with every &amp;ldquo;Why do these numbers look different?&amp;rdquo; conversation.&lt;/p>
&lt;p>Success requires people who can hold both realities simultaneously. And that kind of cognitive flexibility only emerges in environments where people feel safe enough to be vulnerable, connected enough to sacrifice personal metrics for collective success, and invested enough to push back when the team is heading in the wrong direction.&lt;/p>
&lt;p>Popovich understood this intuitively. His teams consistently featured players who took less money, accepted smaller roles, and subordinated personal statistics for championships. How? Not through inspirational speeches or performance bonuses. Through dinners, emotional presence, and a relentless refusal to separate the person from the player.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="belonging-cues-the-invisible-architecture-of-high-performing-teams">Belonging cues: the invisible architecture of high-performing teams&lt;/h3>
&lt;p>Daniel Coyle, who studied Popovich extensively for his book &lt;em>The Culture Code&lt;/em>, identified three types of signals that create what he calls &amp;ldquo;belonging cues&amp;rdquo;:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Personal, up-close connection&lt;/strong> — behavior that communicates &amp;ldquo;I care about you as a person&amp;rdquo;&lt;/li>
&lt;li>&lt;strong>Performance feedback&lt;/strong> — relentless criticism that says &amp;ldquo;we have high standards here&amp;rdquo;&lt;/li>
&lt;li>&lt;strong>Big-picture perspective&lt;/strong> — conversations that say &amp;ldquo;life is bigger than this immediate task&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Most data leaders nail one of these and completely miss the others.&lt;/p>
&lt;p>The technically-focused leader provides excellent feedback on code quality and architecture decisions but treats personal connection as optional—something that happens organically in Teams channels or maybe at the occasional team happy hour.&lt;/p>
&lt;p>The people-focused leader builds genuine relationships but struggles to deliver hard feedback, allowing mediocrity to persist because confrontation feels mean.&lt;/p>
&lt;p>The visionary leader connects everything to grand purpose—&amp;ldquo;we&amp;rsquo;re democratizing data access across the organization!&amp;quot;—but the lofty framing feels hollow when day-to-day work involves debugging pipeline failures at 10 PM on a Friday.&lt;/p>
&lt;p>Popovich weaves all three together constantly. The same evening might include crying with a player over a family tragedy, reviewing film and yelling about a missed rotation, and discussing the Civil Rights Movement. As one assistant coach put it: &amp;ldquo;He&amp;rsquo;ll tell you the truth, with no bullshit, and then he&amp;rsquo;ll love you to death.&amp;rdquo;&lt;/p>
&lt;p>This isn&amp;rsquo;t contradictory. It&amp;rsquo;s complete.&lt;/p>
&lt;p>Sound familiar? It should. Google&amp;rsquo;s Project Aristotle research found essentially the same thing—that &lt;strong>psychological safety&lt;/strong> (the belief that you won&amp;rsquo;t be punished for taking risks or admitting mistakes) was the single strongest predictor of team performance. But psychological safety isn&amp;rsquo;t warm fuzziness. It&amp;rsquo;s the confidence that your team has high standards &lt;em>and&lt;/em> will support you through the inevitable failures that come from pushing against those standards.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="get-over-yourself--the-four-word-philosophy-that-builds-dynasties">&amp;ldquo;Get over yourself&amp;rdquo; — the four-word philosophy that builds dynasties&lt;/h3>
&lt;p>Popovich&amp;rsquo;s core belief reduces to four words: &lt;strong>&amp;ldquo;Get over yourself.&amp;rdquo;&lt;/strong>&lt;/p>
&lt;p>It sounds harsh. But it&amp;rsquo;s actually an invitation to freedom.&lt;/p>
&lt;p>&amp;ldquo;It&amp;rsquo;s not about any one person,&amp;rdquo; he explains. &amp;ldquo;You&amp;rsquo;ve got to get over yourself and realize that it takes a group to get this thing done.&amp;rdquo;&lt;/p>
&lt;p>When evaluating potential players, he watched for this quality specifically: &amp;ldquo;When there&amp;rsquo;s a guy who talks about himself all day long, you start to get the sense that he doesn&amp;rsquo;t listen real well&amp;hellip; Has this person gotten over himself? If he has then he&amp;rsquo;s going to accept parameters. He&amp;rsquo;s going to accept the role.&amp;rdquo;&lt;/p>
&lt;p>This is the hardest cultural shift for data teams to make.&lt;/p>
&lt;p>We work in a field that rewards individual technical brilliance. The engineer who builds the elegant solution gets recognition. The one who writes the clever query gets quoted in architecture reviews. We celebrate the heroic debugging session that saved the quarterly report, rarely asking why the pipeline was fragile enough to need heroics in the first place.&lt;/p>
&lt;p>Getting over yourself means accepting that the best data architecture might not showcase your technical sophistication. It means implementing the boring, proven solution instead of the interesting one. It means writing documentation instead of starting a new project. It means saying &amp;ldquo;I don&amp;rsquo;t know&amp;rdquo; instead of confidently bullshitting through a stakeholder meeting.&lt;/p>
&lt;p>Manu Ginobili—an All-Star caliber player—started only 349 of 1,057 games because the team needed him as a sixth man. When the coaching staff asked him to permanently accept the bench role, they told him honestly: if he wasn&amp;rsquo;t good with it, he was going to start. Whatever he said, they would do it. He deserved that choice.&lt;/p>
&lt;p>Ginobili agreed to come off the bench.&lt;/p>
&lt;p>Tim Duncan was &amp;ldquo;blown away&amp;rdquo;: &amp;ldquo;Are you kidding? He&amp;rsquo;s Manu! He&amp;rsquo;s a star! He can&amp;rsquo;t not start.&amp;rdquo; But Ginobili embraced the sacrifice because he understood something crucial: his ego was getting between him and the team&amp;rsquo;s success.&lt;/p>
&lt;p>How many data engineers would make that trade? How many would accept that their personal visibility, their resume-building features, their chance to work on the exciting project needed to take a back seat to what the team actually needed?&lt;/p>
&lt;p>The uncomfortable truth: they&amp;rsquo;ll only make that sacrifice if they trust you enough to believe it&amp;rsquo;s genuine. If they feel like the sacrifice is worth it because they belong to something worth sacrificing for.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-dinner-table-as-sacred-ground">The dinner table as sacred ground&lt;/h3>
&lt;p>Popovich spends an estimated seven figures annually on food and wine for team gatherings. After road games, the team frequently stays overnight specifically to share dinner, with Pop personally scouting restaurants weeks in advance.&lt;/p>
&lt;p>These aren&amp;rsquo;t casual meals. He positions himself at tables of exactly six people—&amp;ldquo;believing this number fosters diversity of conversation without people breaking off into side chats&amp;rdquo;—and works the room methodically. The dinners serve a strategic purpose: &amp;ldquo;Dinners help us have a better understanding of each individual person, which brings us closer to each other—and, on the court, understand each other better.&amp;rdquo;&lt;/p>
&lt;p>Now here&amp;rsquo;s the challenge for modern data leaders: how do you create this in a world where your team might be spread across six time zones?&lt;/p>
&lt;p>You can&amp;rsquo;t replicate the intimacy of shared meals and expensive wine. But you can replicate the &lt;em>intention&lt;/em> behind them.&lt;/p>
&lt;p>&lt;strong>What Popovich&amp;rsquo;s dinners actually accomplish:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Dedicated time for connection that isn&amp;rsquo;t about work deliverables&lt;/li>
&lt;li>Small enough groups to have real conversations&lt;/li>
&lt;li>Physical proximity and eye contact (or as close as you can get)&lt;/li>
&lt;li>Consistent repetition that builds cumulative trust&lt;/li>
&lt;li>Leader modeling vulnerability by being genuinely present&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Translation for distributed data teams:&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Intentional one-on-ones that go beyond status updates.&lt;/strong> Not &amp;ldquo;what are you working on?&amp;rdquo; but &amp;ldquo;what&amp;rsquo;s actually going on with you?&amp;rdquo; Schedule them consistently. Protect them from cancellation. Come prepared with questions about their life outside work.&lt;/p>
&lt;p>&lt;strong>Small-group video calls for real conversation.&lt;/strong> Skip the all-hands. Create rotating groups of 4-6 people for unstructured time. No agenda. No recording. Just talking.&lt;/p>
&lt;p>&lt;strong>Occasional in-person gatherings that prioritize relationship-building.&lt;/strong> If you bring the team together once a year, don&amp;rsquo;t fill the schedule with strategy sessions and team-building exercises. Create space for meals, walks, and conversations that would feel weird to have over Zoom.&lt;/p>
&lt;p>&lt;strong>Consistent rituals that build cumulative connection.&lt;/strong> A Slack channel where people share weekend plans. A monthly &amp;ldquo;show and tell&amp;rdquo; of something non-work related. Anything that repeats often enough to become meaningful.&lt;/p>
&lt;p>The point isn&amp;rsquo;t to replicate Popovich&amp;rsquo;s exact methods. The point is to be as intentional about relationship-building as you are about sprint planning.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="when-tragedy-strikes-leaders-appear">When tragedy strikes, leaders appear&lt;/h3>
&lt;p>The most powerful evidence of Popovich&amp;rsquo;s approach emerges during personal crises.&lt;/p>
&lt;p>When DeMar DeRozan&amp;rsquo;s father passed away during the 2021 season, DeRozan called the team&amp;rsquo;s GM privately—&amp;ldquo;I didn&amp;rsquo;t want nobody to know.&amp;rdquo; Ninety seconds later, Pop was knocking on his hotel room door. &amp;ldquo;Pop sat in the room with me and cried with me for about two hours. He was like, &amp;lsquo;I&amp;rsquo;m not leaving until you leave.&amp;rsquo;&amp;rdquo;&lt;/p>
&lt;p>When Dejounte Murray&amp;rsquo;s mother was shot during his rookie year, Popovich called her directly—without Murray&amp;rsquo;s knowledge—offering to move her to San Antonio on his own dime.&lt;/p>
&lt;p>When Robert Horry&amp;rsquo;s daughter was hospitalized at the start of a season, Popovich told him: &amp;ldquo;Don&amp;rsquo;t come back until she&amp;rsquo;s out of the hospital.&amp;rdquo; Three weeks later, upon returning, Pop asked if she was out. Horry said yes. Pop responded: &amp;ldquo;She ain&amp;rsquo;t out of the woods yet. Go back home. We don&amp;rsquo;t really need you right now.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Family is the most important thing.&amp;rdquo;&lt;/p>
&lt;p>These aren&amp;rsquo;t management techniques. They&amp;rsquo;re expressions of genuine care. And they&amp;rsquo;re the reason players sacrifice for Popovich in ways they wouldn&amp;rsquo;t for other coaches.&lt;/p>
&lt;p>Data leaders: when was the last time you showed up for someone beyond work boundaries?&lt;/p>
&lt;p>I&amp;rsquo;m not suggesting you need to cry with your direct reports or offer to pay for their family members&amp;rsquo; relocation. But I am suggesting that most of us dramatically underinvest in being present during the moments that actually matter.&lt;/p>
&lt;p>&lt;strong>The life events where showing up builds lasting trust:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Family emergencies and deaths&lt;/li>
&lt;li>Health crises (theirs or family members')&lt;/li>
&lt;li>Major life transitions (new babies, divorces, moves)&lt;/li>
&lt;li>Career anxieties and disappointments&lt;/li>
&lt;li>Personal struggles they&amp;rsquo;re brave enough to share&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>What &amp;ldquo;showing up&amp;rdquo; looks like in practice:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Proactively reaching out, not waiting for them to come to you&lt;/li>
&lt;li>Asking what they need, not assuming you know&lt;/li>
&lt;li>Offering flexibility without making them negotiate for it&lt;/li>
&lt;li>Following up later to see how they&amp;rsquo;re doing&lt;/li>
&lt;li>Remembering the details and checking in over time&lt;/li>
&lt;/ul>
&lt;p>The leaders I&amp;rsquo;ve worked with who inspire genuine loyalty share one trait: they remember the human stuff. They ask about your kid&amp;rsquo;s soccer tournament. They notice when you seem off. They don&amp;rsquo;t treat personal life as interference with work productivity.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="high-standards-and-deep-care-arent-opposites">High standards and deep care aren&amp;rsquo;t opposites&lt;/h3>
&lt;p>Here&amp;rsquo;s the false dichotomy that destroys most leadership: you&amp;rsquo;re either a demanding, results-oriented leader who pushes for excellence, or you&amp;rsquo;re a supportive, people-first leader who prioritizes relationships. Pick your lane.&lt;/p>
&lt;p>Popovich refuses the choice.&lt;/p>
&lt;p>Boris Diaw once visited Tony Parker at Pop&amp;rsquo;s house for Christmas dinner. After a warm meal, both Parker and Popovich disappeared. Diaw went searching and found Pop reviewing film with Tony about the game the night before—and yelling at him about missed shots and turnovers.&lt;/p>
&lt;p>Diaw&amp;rsquo;s takeaway captures the essence: &amp;ldquo;On the same night, you could have the family setting, all the love and the care, and at the same time caring about making Tony a better player.&amp;rdquo;&lt;/p>
&lt;p>The warmth makes the feedback land differently. Not softer—still direct, still pointed, still demanding. But the player receiving it knows it comes from someone who genuinely wants them to succeed, who has invested in them as a complete person, who will be there for the hard stuff outside work.&lt;/p>
&lt;p>Research backs this up. Amy Edmondson&amp;rsquo;s psychological safety research shows that high-performing teams combine high standards with high support—&amp;ldquo;learning zones&amp;rdquo; where people push themselves precisely because they feel safe enough to fail.&lt;/p>
&lt;p>For data leaders, this means:&lt;/p>
&lt;p>&lt;strong>Being specific and direct about performance issues&lt;/strong> without softening the message into meaninglessness. &amp;ldquo;The pipeline you built has reliability problems that are affecting stakeholder trust&amp;rdquo; is clearer and more respectful than &amp;ldquo;there might be some opportunities to improve things.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Providing the context behind criticism&lt;/strong> so it lands as investment rather than attack. &amp;ldquo;I&amp;rsquo;m telling you this because you&amp;rsquo;re capable of leading our data platform work, and these patterns would prevent that.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Following hard conversations with continued connection&lt;/strong> rather than distance. The relationship doesn&amp;rsquo;t end after difficult feedback—it continues.&lt;/p>
&lt;p>&lt;strong>Modeling receiving feedback yourself&lt;/strong> so the team sees that criticism flows in all directions. Pop regularly admitted his own failures publicly. Your team needs to see you do the same.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="connecting-work-to-something-larger">Connecting work to something larger&lt;/h3>
&lt;p>Before the 2014 NBA Finals, Popovich skipped X&amp;rsquo;s and O&amp;rsquo;s entirely. Instead, he told his team about Eddie Mabo Day—a significant date for Indigenous Australians—to honor point guard Patty Mills&amp;rsquo; heritage.&lt;/p>
&lt;p>This reflects Pop&amp;rsquo;s broader practice of engaging players on issues far beyond basketball. He&amp;rsquo;s taken teams to see Hamilton on Broadway. He brought civil rights activist John Carlos to speak. He gave players Ta-Nehisi Coates&amp;rsquo; &lt;em>Between the World and Me&lt;/em> to read.&lt;/p>
&lt;p>Why does this matter for building teams?&lt;/p>
&lt;p>Because it sends a message: &lt;strong>we are complete human beings who care about the world beyond our immediate work.&lt;/strong>&lt;/p>
&lt;p>Coyle noted: &amp;ldquo;Popovich would create similar conversations on the war in Syria, or a change of government in Argentina, gay marriage, institutional racism, terrorism—it doesn&amp;rsquo;t really matter, as long as it delivers the message he wants to deliver: There are bigger things than basketball to which we are all connected.&amp;rdquo;&lt;/p>
&lt;p>This is the belonging cue that most data leaders miss entirely.&lt;/p>
&lt;p>We act as if the only things worth discussing are data models, pipeline reliability, and stakeholder requirements. We treat outside interests as distractions rather than windows into who our team members actually are.&lt;/p>
&lt;p>&lt;strong>For data teams, connecting to something larger might include:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Discussions about the ethical implications of data work (privacy, algorithmic bias, surveillance)&lt;/li>
&lt;li>Conversations about industry trends and where the field is heading&lt;/li>
&lt;li>Space for people to share what they care about outside work&lt;/li>
&lt;li>Recognition of cultural backgrounds and experiences&lt;/li>
&lt;li>Honest conversation about work-life balance, burnout, and sustainability&lt;/li>
&lt;/ul>
&lt;p>None of this requires seven-figure wine budgets. It requires treating your team members as complete humans who exist in a world larger than your Jira board.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-evidence-speaks-across-decades">The evidence speaks across decades&lt;/h3>
&lt;p>Popovich&amp;rsquo;s methods produce measurable results. Five championships. Twenty-two consecutive playoff appearances. Eighteen straight 50-win seasons—unprecedented in NBA history. The best winning percentage of any major professional sports franchise in North America over a three-decade span.&lt;/p>
&lt;p>The 2014 championship team became the first since the ABA-NBA merger to win a title without any player averaging 30+ minutes per game. Four different players led the team in scoring during the playoffs. No dominant statistics—just beautiful, unselfish basketball.&lt;/p>
&lt;p>Beyond the Spurs, his coaching tree now shapes the entire league. When The Athletic surveyed NBA players on which coach they&amp;rsquo;d most want to play for, Popovich received 41%—the highest percentage by far.&lt;/p>
&lt;p>Kevin Durant captured the appeal: &amp;ldquo;When you are with somebody like that, a person who cares about all those things on top of wanting you to be the best player you can be, then you want to go to work with that person.&amp;rdquo;&lt;/p>
&lt;p>The same principle applies to data teams. The leaders who build lasting excellence aren&amp;rsquo;t the ones with the cleverest technical strategies. They&amp;rsquo;re the ones who create environments where talented people choose to stay, sacrifice, and push each other toward genuine accomplishment.&lt;/p>
&lt;hr>
&lt;br>
&lt;br>
&lt;h3 id="the-uncomfortable-question-for-data-leaders">The uncomfortable question for data leaders&lt;/h3>
&lt;p>At his Hall of Fame induction, Popovich offered unusual clarity: &amp;ldquo;Basketball doesn&amp;rsquo;t love us back, does it? We use it like a bar of soap, right? It pays our bills. It gives us a wonderful life. But I don&amp;rsquo;t remember it saying, &amp;lsquo;I love you, Pop.&amp;rsquo; It&amp;rsquo;s different. It&amp;rsquo;s the family.&amp;rdquo;&lt;/p>
&lt;p>Here&amp;rsquo;s the uncomfortable question for data leaders: &lt;strong>what&amp;rsquo;s the family equivalent for your team?&lt;/strong>&lt;/p>
&lt;p>Data pipelines don&amp;rsquo;t love you back. Dashboards don&amp;rsquo;t remember your birthday. The perfectly optimized query won&amp;rsquo;t show up at your door when your father dies.&lt;/p>
&lt;p>The work matters. Technical excellence matters. Delivering value to the organization matters.&lt;/p>
&lt;p>But none of it persists if you haven&amp;rsquo;t built the human foundation that makes sustained performance possible.&lt;/p>
&lt;p>The false choice is between being a great technical leader and being a great people leader. Popovich proves you need both—that technical excellence &lt;em>requires&lt;/em> the human foundation, not despite it.&lt;/p>
&lt;p>So the next question isn&amp;rsquo;t whether you have the right technology stack or the optimal team structure. It&amp;rsquo;s whether you&amp;rsquo;re investing in the belonging cues, the dinners (literal or figurative), the showing up during crisis, the relentless standards delivered with genuine love.&lt;/p>
&lt;p>It&amp;rsquo;s whether you&amp;rsquo;re willing to do the uncomfortable work of building something worth sacrificing for.&lt;/p>
&lt;p>Because here&amp;rsquo;s the thing: your team knows the difference. They know when connection is performative and when it&amp;rsquo;s real. They know when high standards come from caring and when they come from ego. They know whether they&amp;rsquo;re resources to be optimized or humans to be invested in.&lt;/p>
&lt;p>And they&amp;rsquo;ll calibrate their own investment accordingly.&lt;/p>
&lt;p>Tim Duncan, accepting his Hall of Fame honor, said what countless Spurs have felt about Popovich: &amp;ldquo;You showed up after I got drafted. You came to my island. You sat with my friends, my family. You talked with my dad. I thought that was normal. It&amp;rsquo;s not.&amp;rdquo;&lt;/p>
&lt;p>I&amp;rsquo;m nowhere near Pop&amp;rsquo;s level. Most of us aren&amp;rsquo;t. But stories like this matter because they remind us what good can look like—and give us something to measure ourselves against.&lt;/p>
&lt;p>Not to feel inadequate. To feel inspired.&lt;/p>
&lt;p>The gap between where we are and where Pop operates isn&amp;rsquo;t a source of shame. It&amp;rsquo;s a direction. And the first step is simply asking the question: &lt;em>Would my team say I showed up?&lt;/em>&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Leadership</category><category>Career Development</category><category>Data Engineering</category><category>Leadership</category><category>Team Building</category><category>Culture</category><category>Management</category><category>Data Teams</category><category>Remote Work</category><category>Psychological Safety</category></item><item><title>Context Engineering: The New Must-Have Skill for Data Engineers</title><link>https://ghostinthedata.info/posts/2026/2026-01-31-context-engineering/</link><pubDate>Sat, 31 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-31-context-engineering/</guid><author>Chris Hillman</author><description>How to build AI context files that remember your hard-won lessons about partitioning, testing, naming conventions, and data types—turning Claude into a genuine peer reviewer.</description><content:encoded>&lt;p>Last year I watched a colleague ask AI to help write a dbt model. The AI spit out perfectly functional SQL—clean syntax, proper CTEs, the works. Looked great.&lt;/p>
&lt;p>Then I noticed the table would eventually hold 800 million rows. No partitioning. No clustering. Just a raw, unoptimised heap waiting to turn into a query performance nightmare (that would likely become my nightmare to fix).&lt;/p>
&lt;p>The engineer wasn&amp;rsquo;t at fault. The AI wasn&amp;rsquo;t at fault either, really. The AI simply didn&amp;rsquo;t know that our environment clusters large tables by date. It didn&amp;rsquo;t know our team&amp;rsquo;s conventions around incremental models. It couldn&amp;rsquo;t know, because nobody had told it.&lt;/p>
&lt;p>Here&amp;rsquo;s the thing: most data engineers treat AI assistants like they&amp;rsquo;re infinitely capable strangers. We give them tasks, accept their output, maybe tweak a few lines. But we never invest in teaching them our context—the hard-won lessons, the team conventions, the mistakes we&amp;rsquo;ve already made (and would rather not make again).&lt;/p>
&lt;p>What if you could? What if every partitioning decision, every naming convention, every &amp;ldquo;never do this&amp;rdquo; rule you&amp;rsquo;ve learned over the years lived in a file that your AI assistant read before every single interaction?&lt;/p>
&lt;p>That&amp;rsquo;s exactly what context engineering enables. Once you set it up, your AI stops being a generic autocomplete and starts acting like a peer who actually knows your codebase.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h3 id="the-problem-with-generic-ai-assistance">The Problem With Generic AI Assistance&lt;/h3>
&lt;br>
&lt;p>I&amp;rsquo;ve been using AI coding assistants for a while. First Copilot (that was a terrible experience), then ChatGPT, and now more recently Claude. I can write up blocks faster with the right prompts.&lt;/p>
&lt;p>But something kept nagging at me.&lt;/p>
&lt;p>The AI would suggest something technically correct but contextually wrong. It would generate a model without tests. Propose a column name that violated our conventions. Use VARCHAR when we standardise on STRING. Small things, individually. Which would lead to more and more refining on the prompts.&lt;/p>
&lt;p>The root cause was simple: the AI had no memory. Every conversation started fresh. It didn&amp;rsquo;t know that we&amp;rsquo;d spent three painful weeks last quarter cleaning up inconsistent date formats. It didn&amp;rsquo;t know that our &lt;code>dim_customer&lt;/code> table had a specific SCD Type 2 pattern we&amp;rsquo;d refined over months.&lt;/p>
&lt;p>Every session, I found myself re-explaining the same context. &amp;ldquo;Remember to add tests.&amp;rdquo; &amp;ldquo;We use snake_case for everything.&amp;rdquo; &amp;ldquo;Always partition by event_date.&amp;rdquo;&lt;/p>
&lt;p>This is always a problem in work generally. You could have an equally talented data engineer sitting next to you, but you don&amp;rsquo;t share the same problems faced, the same challenges solved. You&amp;rsquo;d find out months or weeks later—we ran into this problem in that table, and we resolved it by doing this. And you&amp;rsquo;d say, oh we had the same problem elsewhere, but here&amp;rsquo;s how we tackled it.&lt;/p>
&lt;p>The solution can be better prompting, as I discussed in this article - &lt;a href="https://ghostinthedata.info/posts/2026/2026-01-04-talk/" target="_blank" rel="noopener">https://ghostinthedata.info/posts/2026/2026-01-04-talk/&lt;/a>. It can also be solved with persistent context.&lt;/p>
&lt;p>But there&amp;rsquo;s another approach that&amp;rsquo;s more fundamental: teaching your AI assistant your context once, so it remembers across every session. That&amp;rsquo;s what context engineering is about.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h3 id="what-context-engineering-actually-looks-like">What Context Engineering Actually Looks Like&lt;/h3>
&lt;br>
&lt;p>Context engineering is a file (or set of files) that contains your team&amp;rsquo;s accumulated wisdom, loaded automatically into every AI session. Think of it as institutional memory for your AI assistant.&lt;/p>
&lt;p>The core concept is straightforward: you maintain markdown files that describe your conventions, standards, and hard-won lessons. Your AI assistant reads these files at the start of every session.&lt;/p>
&lt;p>Different tools implement this differently:&lt;/p>
&lt;p>&lt;strong>Claude Code&lt;/strong> (Anthropic&amp;rsquo;s CLI tool) looks for a &lt;code>CLAUDE.md&lt;/code> file in your project root. It loads this automatically when you start a session.&lt;/p>
&lt;p>&lt;strong>Cline&lt;/strong> (the VSCode extension I use) supports custom system prompts and can reference local files for context. You can configure it to always include specific markdown files in its context window.&lt;/p>
&lt;p>&lt;strong>Cursor&lt;/strong> has similar capabilities with its rules files.&lt;/p>
&lt;p>The implementation details vary, but the principle is identical: give the AI your context &lt;em>before&lt;/em> it starts generating code.&lt;/p>
&lt;p>Here&amp;rsquo;s what a basic context file might look like for a data engineering project:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span># Analytics Pipeline Context
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## Stack
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Snowflake (Enterprise tier)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> dbt Core 1.7+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Fivetran for ingestion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Tableau for BI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## Critical Standards
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">### Partitioning &amp;amp; Clustering
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> **ALWAYS** cluster tables &amp;gt;10M rows by their primary date column
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Use &lt;span style="color:#e6db74">`cluster_by`&lt;/span> in dbt config, not raw DDL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Partition Staging tables by data_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">### Naming Conventions
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Models: &lt;span style="color:#e6db74">`stg_source__entity`&lt;/span>, &lt;span style="color:#e6db74">`fct_entity`&lt;/span>, &lt;span style="color:#e6db74">`dim_entity`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Columns: snake_case, never camelCase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Booleans: prefix with &lt;span style="color:#e6db74">`is_`&lt;/span> or &lt;span style="color:#e6db74">`has_`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Dates: suffix with &lt;span style="color:#e6db74">`_at`&lt;/span> for timestamps, &lt;span style="color:#e6db74">`_date`&lt;/span> for dates
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> IDs: suffix with &lt;span style="color:#e6db74">`_id`&lt;/span> (never &lt;span style="color:#e6db74">`_key`&lt;/span> for surrogate keys)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">### Testing Requirements
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Every model needs at least: unique, not_null on primary key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> Fact tables need referential integrity tests to all dimensions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> SCD2 dimensions need: valid_from &amp;lt; &lt;span style="color:#f92672">valid_to&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">,&lt;/span> &lt;span style="color:#a6e22e">no&lt;/span> &lt;span style="color:#a6e22e">gaps&lt;/span> &lt;span style="color:#a6e22e">or&lt;/span> &lt;span style="color:#a6e22e">overlaps&lt;/span> &lt;span style="color:#a6e22e">in&lt;/span> &lt;span style="color:#a6e22e">history&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This isn&amp;rsquo;t complicated. It&amp;rsquo;s just written-down knowledge. The magic happens when this knowledge persists across sessions.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h3 id="structuring-your-context-files">Structuring Your Context Files&lt;/h3>
&lt;br>
&lt;p>I use Cline for most of my dbt work because it integrates cleanly with VSCode and connects directly to the Claude API. Rather than one massive context file, I structure my context into logical chunks—this helps the AI (and me) find relevant information quickly.&lt;/p>
&lt;p>Here&amp;rsquo;s the directory structure I use:&lt;/p>



&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 224 137"
 >
 &lt;g transform='translate(8,16)'>
&lt;text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>│&lt;/text>
&lt;text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='84' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='100' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='8' y='116' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='100' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='16' y='116' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>├&lt;/text>
&lt;text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>└&lt;/text>
&lt;text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>─&lt;/text>
&lt;text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='192' y='68' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div>
&lt;p>When I start a session, this context is already loaded. The AI knows our conventions before I type a single character.&lt;/p>
&lt;p>The specific configuration will depend on your tool of choice—Cline, Cursor, and Claude Code each have their own settings. The important part is the content itself: clear, specific rules that capture how your team actually works.&lt;/p>
&lt;br>
&lt;br>
&lt;hr>
&lt;h3 id="the-every-mistake-becomes-a-rule-philosophy">The &amp;ldquo;Every Mistake Becomes a Rule&amp;rdquo; Philosophy&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s where context engineering gets genuinely powerful.&lt;/p>
&lt;p>Boris Cherny, who created Claude Code, maintains his context file at exactly 2,500 tokens. His practice: &amp;ldquo;Every mistake becomes a rule.&amp;rdquo; When something goes wrong that&amp;rsquo;s generalisable, it gets added to the context file immediately.&lt;/p>
&lt;p>This resonated with me because it mirrors how data engineers actually accumulate wisdom. We don&amp;rsquo;t learn by reading documentation. We learn by screwing things up, fixing them, and vowing to never make that mistake again.&lt;/p>
&lt;p>The difference now? Those lessons don&amp;rsquo;t just live in our heads. They live in a file that our AI assistant reads before every interaction.&lt;/p>
&lt;p>Last quarter, a seemingly innocent change to an SCD2 dimension caused our dashboards to double-count certain metrics. The root cause was subtle: we&amp;rsquo;d accidentally created overlapping validity windows during a backfill. It took three days to diagnose.&lt;/p>
&lt;p>The fix took an hour. But more importantly, I added this to my testing patterns file:&lt;/p>
&lt;p>&lt;strong>SCD Type 2 Validation Rules&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## SCD Type 2 Validation
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Every SCD2 dimension MUST include these tests:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">### No Overlapping Windows
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">```yaml
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&lt;/span>&lt;span style="color:#f92672">tests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">dbt_utils.unique_combination_of_columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">combination_of_columns&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">customer_id&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">valid_from&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">dbt_expectations.expect_column_pair_values_A_to_be_less_than_B&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">column_A&lt;/span>: &lt;span style="color:#ae81ff">valid_from&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">column_B&lt;/span>: &lt;span style="color:#ae81ff">valid_to&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">or_equal&lt;/span>: &lt;span style="color:#66d9ef">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">```&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>


&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 2432 1769"
 >
 &lt;g transform='translate(8,16)'>
&lt;path d='M 0,96 L 16,96' fill='none' stroke='currentColor'>&lt;/path>
&lt;path d='M 0,528 L 16,528' fill='none' stroke='currentColor'>&lt;/path>
&lt;path d='M 0,976 L 16,976' fill='none' stroke='currentColor'>&lt;/path>
&lt;path d='M 0,1408 L 16,1408' fill='none' stroke='currentColor'>&lt;/path>
&lt;polygon points='8.000000,800.000000 -4.000000,794.400024 -4.000000,805.599976' fill='currentColor' transform='rotate(0.000000, 0.000000, 800.000000)'>&lt;/polygon>
&lt;polygon points='8.000000,816.000000 -4.000000,810.400024 -4.000000,821.599976' fill='currentColor' transform='rotate(0.000000, 0.000000, 816.000000)'>&lt;/polygon>
&lt;polygon points='8.000000,832.000000 -4.000000,826.400024 -4.000000,837.599976' fill='currentColor' transform='rotate(0.000000, 0.000000, 832.000000)'>&lt;/polygon>
&lt;polygon points='8.000000,848.000000 -4.000000,842.400024 -4.000000,853.599976' fill='currentColor' transform='rotate(0.000000, 0.000000, 848.000000)'>&lt;/polygon>
&lt;polygon points='8.000000,864.000000 -4.000000,858.400024 -4.000000,869.599976' fill='currentColor' transform='rotate(0.000000, 0.000000, 864.000000)'>&lt;/polygon>
&lt;circle cx='0' cy='1104' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='0' cy='1168' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='0' cy='1232' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='0' cy='1296' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='288' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='304' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='320' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='336' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='352' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='1536' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='1552' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='1568' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='1584' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='24' cy='1600' r='6' stroke='currentColor' fill='currentColor'>&lt;/circle>
&lt;circle cx='1400' cy='656' r='6' stroke='currentColor' fill='#fff'>&lt;/circle>
&lt;text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='0' y='164' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='196' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='0' y='228' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='0' y='260' fill='currentColor' style='font-size:1em'>H&lt;/text>
&lt;text text-anchor='middle' x='0' y='292' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='0' y='308' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='0' y='324' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='0' y='340' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='0' y='356' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='0' y='388' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='0' y='420' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='0' y='452' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='484' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='500' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='564' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='0' y='596' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='628' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='660' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='0' y='692' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='724' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='0' y='756' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='0' y='788' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='900' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='932' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='948' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='1012' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='0' y='1044' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='1076' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='0' y='1140' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='0' y='1204' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='1268' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='0' y='1332' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='0' y='1364' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='1380' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='1444' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='0' y='1476' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='1508' fill='currentColor' style='font-size:1em'>Y&lt;/text>
&lt;text text-anchor='middle' x='0' y='1540' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='0' y='1556' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='0' y='1572' fill='currentColor' style='font-size:1em'>3&lt;/text>
&lt;text text-anchor='middle' x='0' y='1588' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='0' y='1604' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='0' y='1636' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='1668' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='0' y='1700' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='0' y='1732' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='0' y='1748' fill='currentColor' style='font-size:1em'>&amp;lt;&lt;/text>
&lt;text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='68' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='132' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='8' y='164' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='196' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='8' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='8' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='8' y='292' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='308' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='324' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='340' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='356' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='388' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='8' y='420' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='8' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='484' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='500' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='564' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='8' y='596' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='628' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='692' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='756' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='8' y='900' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='932' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='948' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='1012' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='8' y='1044' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='1076' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='1108' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='8' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='8' y='1172' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='8' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='1236' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='8' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='8' y='1300' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='8' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='8' y='1364' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='1380' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='1444' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='8' y='1476' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='1508' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='8' y='1540' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='1556' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='1572' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='1588' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='1604' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='8' y='1636' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='8' y='1700' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='8' y='1732' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='8' y='1748' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='132' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='16' y='260' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='16' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='484' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='500' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='564' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='16' y='596' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='16' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='16' y='788' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='16' y='820' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='16' y='836' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='16' y='852' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='16' y='868' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='16' y='900' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='16' y='932' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='948' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='1012' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='16' y='1044' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='1076' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='1108' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='16' y='1172' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='16' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='1236' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='16' y='1268' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='16' y='1300' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='16' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='16' y='1364' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='1380' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='1444' fill='currentColor' style='font-size:1em'>#&lt;/text>
&lt;text text-anchor='middle' x='16' y='1476' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='1508' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='16' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='16' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='16' y='1732' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='16' y='1748' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='164' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='24' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='24' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='24' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='24' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='24' y='484' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='500' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='596' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='24' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='24' y='756' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='24' y='788' fill='currentColor' style='font-size:1em'>H&lt;/text>
&lt;text text-anchor='middle' x='24' y='900' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='24' y='932' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='948' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='1044' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='1076' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='24' y='1108' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='24' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='24' y='1172' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='24' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='24' y='1236' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='24' y='1268' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='24' y='1300' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='24' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='24' y='1364' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='1380' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='1476' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='24' y='1732' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='24' y='1748' fill='currentColor' style='font-size:1em'>&amp;gt;&lt;/text>
&lt;text text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='32' y='196' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='32' y='260' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='32' y='292' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='308' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='324' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='340' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='356' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='388' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='32' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='32' y='564' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='32' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='32' y='692' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='32' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='32' y='820' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='32' y='836' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='32' y='852' fill='currentColor' style='font-size:1em'>U&lt;/text>
&lt;text text-anchor='middle' x='32' y='868' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='32' y='900' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='32' y='1012' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='32' y='1108' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='32' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='32' y='1172' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='32' y='1236' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='32' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='32' y='1300' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='32' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='32' y='1444' fill='currentColor' style='font-size:1em'>G&lt;/text>
&lt;text text-anchor='middle' x='32' y='1508' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='32' y='1540' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='1556' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='1572' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='1588' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='1604' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='32' y='1636' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='32' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='32' y='1700' fill='currentColor' style='font-size:1em'>8&lt;/text>
&lt;text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='40' y='260' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='40' y='292' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='40' y='308' fill='currentColor' style='font-size:1em'>F&lt;/text>
&lt;text text-anchor='middle' x='40' y='324' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='40' y='340' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='40' y='356' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='40' y='388' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='40' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='40' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='564' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='40' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='40' y='660' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='40' y='692' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='40' y='756' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='40' y='788' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='40' y='820' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='40' y='836' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='40' y='852' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='40' y='868' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='40' y='900' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='40' y='1012' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='1076' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='40' y='1108' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='40' y='1140' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='40' y='1172' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='40' y='1204' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='40' y='1236' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='40' y='1268' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='40' y='1444' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='40' y='1508' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='40' y='1540' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='40' y='1556' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='40' y='1572' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='40' y='1588' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='40' y='1604' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='40' y='1636' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='40' y='1668' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='40' y='1700' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='48' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='48' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='48' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='48' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='48' y='340' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='48' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='420' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='48' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='48' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='48' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='48' y='692' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='48' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='48' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='820' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='48' y='836' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='48' y='852' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='868' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='48' y='1012' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='48' y='1108' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='48' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='48' y='1300' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='48' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='48' y='1444' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='48' y='1508' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='48' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='48' y='1556' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='48' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='48' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='48' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='48' y='1636' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='48' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='48' y='1700' fill='currentColor' style='font-size:1em'>%&lt;/text>
&lt;text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='56' y='260' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='56' y='308' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='56' y='324' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='56' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='56' y='388' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='56' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='56' y='452' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='56' y='628' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='56' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='56' y='692' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='756' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='56' y='788' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='56' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='836' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='56' y='852' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='56' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='900' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='56' y='1012' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='56' y='1076' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='56' y='1172' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='56' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='56' y='1236' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='56' y='1268' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='56' y='1300' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='56' y='1444' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='1508' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='56' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='1556' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='56' y='1572' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='56' y='1588' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='56' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='56' y='1636' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='64' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='260' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='64' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='64' y='324' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='64' y='340' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='64' y='356' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='64' y='564' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='64' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='64' y='756' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='64' y='788' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='64' y='820' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='64' y='836' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='64' y='1012' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='64' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='64' y='1108' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='1172' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='64' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='1236' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='64' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='64' y='1300' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='64' y='1332' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='64' y='1444' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='64' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='1540' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='64' y='1572' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='64' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='1604' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='64' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='64' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='64' y='1700' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='72' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='292' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='72' y='308' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='324' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='72' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='356' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='72' y='388' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='72' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='72' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='72' y='660' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='72' y='692' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='72' y='756' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='72' y='836' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='72' y='852' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='72' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='900' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='72' y='1012' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='1076' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='72' y='1108' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='72' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='72' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='72' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='1300' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='72' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='72' y='1444' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='72' y='1556' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='72' y='1572' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='72' y='1604' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='72' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='72' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='308' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='80' y='324' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='80' y='340' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='80' y='356' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='80' y='388' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='80' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='628' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='80' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='80' y='692' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='80' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='80' y='756' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='80' y='788' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='80' y='820' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='80' y='836' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='852' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='80' y='868' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='80' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='1076' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='80' y='1108' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='80' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='1172' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='80' y='1236' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='80' y='1332' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='80' y='1444' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='80' y='1508' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='80' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='80' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='80' y='1572' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='80' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='80' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='80' y='1636' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='80' y='1668' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='80' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='88' y='196' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='88' y='260' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='88' y='292' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='308' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='88' y='340' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='88' y='356' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='88' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='88' y='420' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='88' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='88' y='564' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='88' y='628' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='88' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='88' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='88' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='788' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='88' y='820' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='88' y='836' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='88' y='852' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='88' y='868' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='88' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='1012' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='88' y='1108' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='88' y='1172' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='1236' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='88' y='1268' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='88' y='1300' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='88' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='1556' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='88' y='1572' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='88' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='88' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='88' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='88' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='96' y='260' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='96' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='308' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='96' y='324' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='96' y='340' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='96' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='96' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='96' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='96' y='628' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='96' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='96' y='692' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='96' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='96' y='756' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='96' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='820' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='96' y='852' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='96' y='868' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='900' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='96' y='1012' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='96' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='96' y='1108' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='96' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='96' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='96' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='96' y='1236' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='96' y='1268' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='96' y='1300' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='96' y='1332' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='96' y='1444' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='96' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='96' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='96' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='96' y='1588' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='96' y='1604' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='96' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='96' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>Y&lt;/text>
&lt;text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='260' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='104' y='292' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='104' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='340' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='104' y='356' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='104' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='104' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='564' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='104' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='104' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='692' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='104' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='756' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='104' y='820' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='836' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='104' y='852' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='104' y='868' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='104' y='1012' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='104' y='1076' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='104' y='1172' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='104' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='1236' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='1300' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='104' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='1444' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='104' y='1508' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='104' y='1540' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='104' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='1588' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='104' y='1604' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='104' y='1636' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='104' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='104' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='112' y='260' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='112' y='308' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='112' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='112' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='112' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='692' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='112' y='756' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='112' y='788' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='112' y='820' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='836' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='112' y='852' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='112' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='900' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='112' y='1012' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='1076' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='112' y='1108' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='1172' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='112' y='1204' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='112' y='1236' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='112' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='112' y='1300' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='112' y='1332' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='112' y='1444' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='112' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='112' y='1588' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='112' y='1604' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='112' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='112' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='120' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='120' y='260' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='120' y='292' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='120' y='308' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='120' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='340' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='120' y='356' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='120' y='420' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='120' y='564' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='120' y='628' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='120' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='120' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='120' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='120' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='788' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='120' y='836' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='120' y='852' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='868' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='120' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='120' y='1012' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='120' y='1076' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='120' y='1108' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='120' y='1172' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='1236' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='120' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='1300' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='1444' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='120' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='120' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='120' y='1572' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='120' y='1636' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='120' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='120' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='128' y='260' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='128' y='308' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='128' y='324' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='128' y='340' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='128' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='564' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='128' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='128' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='692' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='756' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='128' y='788' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='128' y='820' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='128' y='836' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='128' y='852' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='128' y='868' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='128' y='900' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='128' y='1012' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='1076' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='1108' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='128' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='128' y='1204' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='128' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='128' y='1332' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='128' y='1444' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='1508' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='1540' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='128' y='1556' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='128' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='128' y='1588' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='128' y='1604' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='128' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='128' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='128' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='136' y='260' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='136' y='292' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='136' y='308' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='136' y='324' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='136' y='340' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='136' y='356' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='136' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='136' y='420' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='136' y='452' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='136' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='136' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='136' y='756' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='136' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='820' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='136' y='852' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='136' y='868' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='136' y='900' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='136' y='1012' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='136' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='1172' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='136' y='1236' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='136' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='1300' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='136' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='1444' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='136' y='1572' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='136' y='1588' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='136' y='1604' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='136' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='136' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='144' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='260' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='144' y='292' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='144' y='308' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='144' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='144' y='356' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='144' y='388' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='144' y='452' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='144' y='564' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='144' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='144' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='144' y='692' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='144' y='724' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='144' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='788' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='144' y='820' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='144' y='836' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='144' y='852' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='868' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='144' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='1012' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='144' y='1076' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='144' y='1108' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='144' y='1204' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='144' y='1236' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='144' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='144' y='1300' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='144' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='144' y='1444' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='144' y='1508' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='144' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='144' y='1556' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='144' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='144' y='1588' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='144' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='144' y='1636' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='144' y='1668' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='144' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='152' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='152' y='308' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='152' y='340' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='152' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='152' y='388' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='152' y='420' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='152' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='152' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='152' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='152' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='152' y='692' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='152' y='756' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='152' y='788' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='152' y='820' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='152' y='836' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='152' y='852' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='152' y='900' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='152' y='1012' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='152' y='1076' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='152' y='1108' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='152' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='152' y='1172' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='152' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='152' y='1236' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='152' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='1300' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='152' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='152' y='1508' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='152' y='1540' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='152' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='152' y='1604' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='152' y='1636' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='152' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='160' y='228' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='160' y='260' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='160' y='308' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='160' y='324' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='160' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='420' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='160' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='564' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='160' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='160' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='160' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='160' y='836' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='160' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='160' y='1012' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='160' y='1076' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='160' y='1108' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='160' y='1172' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='160' y='1204' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='160' y='1236' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='160' y='1300' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='160' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='160' y='1444' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='160' y='1508' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='160' y='1540' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='160' y='1556' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='160' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='160' y='1604' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='160' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='168' y='292' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='168' y='308' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='168' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='356' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='168' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='168' y='756' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='168' y='788' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='168' y='820' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='168' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='168' y='852' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='168' y='868' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='168' y='900' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='168' y='1076' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='1108' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='168' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='168' y='1172' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='1236' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='168' y='1268' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='168' y='1300' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='1444' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='168' y='1508' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='168' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='168' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='168' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='168' y='1588' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='168' y='1636' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='168' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='168' y='1700' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='176' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='176' y='260' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='176' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='176' y='324' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='176' y='356' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='176' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='176' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='176' y='564' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='176' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='176' y='692' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='176' y='724' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='176' y='756' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='820' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='176' y='836' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='176' y='852' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='900' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='176' y='1012' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='176' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='1204' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='176' y='1236' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='176' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='176' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='176' y='1444' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='176' y='1508' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='176' y='1540' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='176' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='176' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='176' y='1636' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='176' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='176' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='184' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='184' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='260' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='184' y='292' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='184' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='340' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='184' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='184' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='184' y='564' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='184' y='660' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='184' y='692' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='184' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='756' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='184' y='788' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='184' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='184' y='852' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='184' y='868' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='184' y='1012' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='184' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='184' y='1108' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='184' y='1172' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='184' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='184' y='1236' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='184' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='184' y='1300' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='184' y='1444' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='184' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='184' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='184' y='1572' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='184' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='184' y='1636' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='184' y='1668' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='184' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='228' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='192' y='260' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='292' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='192' y='324' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='192' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='192' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='192' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='192' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='192' y='564' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='192' y='628' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='192' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='192' y='692' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='192' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='820' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='192' y='836' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='852' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='868' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='192' y='900' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='192' y='1012' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='192' y='1076' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='192' y='1108' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='192' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='192' y='1172' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='192' y='1300' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='192' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='1444' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='192' y='1508' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='192' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='1556' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='192' y='1572' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='192' y='1588' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='192' y='1604' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='192' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='192' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='200' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='260' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='200' y='292' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='200' y='324' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='200' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='200' y='564' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='200' y='628' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='200' y='660' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='200' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='200' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='200' y='788' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='200' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='200' y='836' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='200' y='852' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='200' y='868' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='200' y='900' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='200' y='1012' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='200' y='1076' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='200' y='1108' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='200' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='200' y='1236' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='200' y='1300' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='200' y='1332' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='200' y='1540' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='200' y='1556' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='200' y='1572' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='200' y='1588' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='200' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='200' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='208' y='20' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='208' y='132' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='208' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='208' y='228' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='208' y='324' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='208' y='340' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='208' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='208' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='208' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='208' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='208' y='564' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='208' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='208' y='660' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='208' y='692' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='208' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='756' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='208' y='820' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='208' y='852' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='208' y='868' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='208' y='900' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='208' y='1076' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='208' y='1108' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='208' y='1140' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='208' y='1172' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='208' y='1204' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='208' y='1236' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='208' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='1300' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='208' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='208' y='1508' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='208' y='1540' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='208' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='208' y='1572' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='208' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='208' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='216' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='132' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='216' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='216' y='292' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='216' y='324' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='216' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='216' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='216' y='420' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='216' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='564' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='216' y='692' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='216' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='216' y='788' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='216' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='836' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='216' y='852' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='216' y='868' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='216' y='900' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='216' y='1012' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='216' y='1076' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='216' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='1172' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='216' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='216' y='1236' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='216' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='216' y='1300' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='216' y='1508' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='216' y='1540' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='216' y='1556' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='216' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='216' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='216' y='1636' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='216' y='1668' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='216' y='1700' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='224' y='132' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='224' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='224' y='228' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='224' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='224' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='224' y='356' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='224' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='224' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='224' y='564' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='692' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='224' y='724' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='224' y='756' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='224' y='788' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='224' y='820' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='224' y='836' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='224' y='868' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='224' y='900' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='224' y='1012' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='224' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='224' y='1108' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='224' y='1172' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='224' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='224' y='1268' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='224' y='1300' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='224' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='224' y='1508' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='224' y='1556' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='224' y='1572' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='224' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='224' y='1604' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='224' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='224' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='232' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='228' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='232' y='324' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='232' y='340' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='232' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='232' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='232' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='232' y='628' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='232' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='232' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='232' y='788' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='820' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='836' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='232' y='852' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='232' y='868' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='232' y='900' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='232' y='1012' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='232' y='1076' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='232' y='1108' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='232' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='1172' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='1236' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='232' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='1300' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='232' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='1508' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='232' y='1540' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='232' y='1556' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='232' y='1572' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='232' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='232' y='1636' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='232' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='232' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='240' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='240' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='240' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='240' y='292' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='240' y='324' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='240' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='240' y='356' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='240' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='420' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='240' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='240' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='240' y='692' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='240' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='240' y='820' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='240' y='836' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='852' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='240' y='868' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='240' y='1076' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='240' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='240' y='1172' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='1236' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='240' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='240' y='1300' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='240' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='240' y='1508' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='240' y='1540' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='240' y='1556' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='240' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='240' y='1588' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='240' y='1636' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='240' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='240' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='248' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='248' y='292' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='248' y='324' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='248' y='340' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='248' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='248' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='248' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='248' y='452' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='248' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='248' y='692' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='248' y='724' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='248' y='756' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='248' y='788' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='248' y='820' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='248' y='836' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='248' y='868' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='248' y='900' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='248' y='1012' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='248' y='1076' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='248' y='1108' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='248' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='248' y='1172' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='248' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='248' y='1236' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='248' y='1332' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='248' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='248' y='1540' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='248' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='248' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='248' y='1604' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='248' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='256' y='132' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='256' y='196' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='256' y='228' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='256' y='292' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='256' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='256' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='256' y='356' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='256' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='256' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='256' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='256' y='660' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='256' y='692' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='256' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='256' y='820' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='852' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='256' y='868' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='256' y='900' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='256' y='1012' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='256' y='1108' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='256' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='256' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='256' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='256' y='1332' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='256' y='1508' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='256' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='256' y='1556' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='256' y='1572' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='256' y='1588' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='256' y='1604' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='256' y='1636' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='256' y='1668' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='256' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='196' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='264' y='228' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='264' y='292' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='264' y='324' fill='currentColor' style='font-size:1em'>?&lt;/text>
&lt;text text-anchor='middle' x='264' y='340' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='264' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='264' y='628' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='264' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='264' y='692' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='264' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='264' y='788' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='264' y='820' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='264' y='836' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='264' y='852' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='264' y='868' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='264' y='900' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='264' y='1012' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='1076' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='264' y='1108' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='264' y='1140' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='264' y='1236' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='264' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='264' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='264' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='1556' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='264' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='264' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='264' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='272' y='196' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='272' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='272' y='340' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='272' y='356' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='272' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='272' y='452' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='272' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='272' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='272' y='692' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='272' y='724' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='272' y='756' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='788' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='272' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='272' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='272' y='852' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='272' y='868' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='272' y='900' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='272' y='1012' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='272' y='1108' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='272' y='1172' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='272' y='1204' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='272' y='1236' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='272' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='272' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='272' y='1508' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='272' y='1540' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='272' y='1556' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='272' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='1588' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='1604' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='272' y='1636' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='272' y='1668' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='272' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='280' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='280' y='292' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='280' y='324' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='280' y='340' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='280' y='356' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='280' y='388' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='280' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='280' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='280' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='280' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='280' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='280' y='820' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='280' y='836' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='280' y='852' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='280' y='868' fill='currentColor' style='font-size:1em'>Z&lt;/text>
&lt;text text-anchor='middle' x='280' y='1012' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='280' y='1076' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='280' y='1108' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='280' y='1140' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='280' y='1172' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='280' y='1204' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='280' y='1236' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='280' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='280' y='1332' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='280' y='1508' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='280' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='280' y='1588' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='280' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='280' y='1636' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='280' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='280' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='288' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='288' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='288' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='388' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='288' y='420' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='288' y='452' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='288' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='692' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='288' y='724' fill='currentColor' style='font-size:1em'>?&lt;/text>
&lt;text text-anchor='middle' x='288' y='756' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='288' y='788' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='288' y='836' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='288' y='852' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='900' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='288' y='1012' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='288' y='1076' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='1108' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='288' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='1172' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='288' y='1204' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='288' y='1236' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='288' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='288' y='1508' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='1540' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='288' y='1572' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='288' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='288' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='288' y='1636' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='288' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='288' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='296' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='296' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='296' y='292' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='296' y='324' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='296' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='296' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='296' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='296' y='660' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='296' y='692' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='296' y='756' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='296' y='788' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='296' y='820' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='296' y='836' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='852' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='296' y='868' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='296' y='900' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='296' y='1012' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='1108' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='296' y='1140' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='296' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='296' y='1204' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='296' y='1236' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='296' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='1332' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='296' y='1508' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='296' y='1540' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='296' y='1556' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='296' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='296' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='296' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='296' y='1668' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='292' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='304' y='324' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='304' y='340' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='304' y='356' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='304' y='388' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='304' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='452' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='304' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='304' y='692' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='304' y='724' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='304' y='756' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='304' y='788' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='304' y='820' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='304' y='852' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='868' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='900' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='1076' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='304' y='1108' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='304' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='304' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='304' y='1332' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='304' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='1540' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='304' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='304' y='1572' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='304' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='304' y='1636' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='304' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='312' y='132' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='312' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='312' y='292' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='312' y='324' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='312' y='340' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='312' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='312' y='420' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='312' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='312' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='312' y='692' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='312' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='756' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='788' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='820' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='836' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='312' y='852' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='312' y='868' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='312' y='900' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='312' y='1012' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='312' y='1108' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='312' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='312' y='1172' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='312' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='312' y='1236' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='312' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='1332' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='312' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='312' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='312' y='1572' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='312' y='1588' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='312' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='312' y='1636' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='312' y='1668' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='312' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='320' y='132' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='196' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='228' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='320' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='340' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='356' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='320' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='320' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='660' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='320' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='756' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='788' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='320' y='820' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='320' y='836' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='320' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='1012' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='320' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='320' y='1108' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='320' y='1140' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='320' y='1172' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='320' y='1236' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='320' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='320' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='320' y='1508' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='320' y='1540' fill='currentColor' style='font-size:1em'>/&lt;/text>
&lt;text text-anchor='middle' x='320' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='320' y='1668' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='320' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='328' y='132' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='328' y='196' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='328' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='328' y='324' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='328' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='356' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='328' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='328' y='420' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='328' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='328' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='328' y='692' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='328' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='788' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='328' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='328' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='328' y='852' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='328' y='868' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='328' y='900' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='328' y='1012' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='328' y='1108' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='328' y='1172' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='328' y='1204' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='328' y='1236' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='328' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='328' y='1508' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='328' y='1540' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='328' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='1572' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='328' y='1588' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='328' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='328' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='328' y='1700' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='336' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='336' y='132' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='228' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='336' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='356' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='336' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='336' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='336' y='452' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='336' y='628' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='336' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='336' y='692' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='336' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='788' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='336' y='836' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='852' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='868' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='336' y='900' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='1012' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='336' y='1076' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='336' y='1108' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='336' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='336' y='1172' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='336' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='336' y='1236' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='336' y='1332' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='336' y='1508' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='336' y='1556' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='336' y='1572' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='336' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='336' y='1604' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='336' y='1636' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='336' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='336' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='344' y='196' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='344' y='228' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='344' y='292' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='344' y='324' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='344' y='340' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='344' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='344' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='344' y='452' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='344' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='344' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='344' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='344' y='756' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='344' y='788' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='344' y='820' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='852' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='344' y='868' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='344' y='1012' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='344' y='1076' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='344' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='344' y='1172' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='344' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='344' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='344' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='344' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='1540' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='344' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='344' y='1572' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='344' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='344' y='1604' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='344' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='344' y='1668' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='352' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='352' y='196' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='352' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='292' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='352' y='324' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='352' y='340' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='352' y='356' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='352' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='352' y='420' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='352' y='628' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='352' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='352' y='692' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='352' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='352' y='756' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='352' y='788' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='352' y='820' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='352' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='352' y='868' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='352' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='1012' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='352' y='1076' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='352' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='1172' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='352' y='1236' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='352' y='1268' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='352' y='1332' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='352' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='352' y='1572' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='352' y='1588' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='352' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='352' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='352' y='1700' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='360' y='20' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='360' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='360' y='292' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='360' y='324' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='360' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='360' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='360' y='692' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='360' y='756' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='360' y='820' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='360' y='836' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='360' y='852' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='900' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='360' y='1172' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='360' y='1236' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='360' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='360' y='1508' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='360' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='360' y='1556' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='360' y='1572' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='360' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='360' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='360' y='1636' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='360' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='360' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='368' y='20' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='368' y='132' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='368' y='228' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='368' y='292' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='340' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='368' y='356' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='368' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='368' y='420' fill='currentColor' style='font-size:1em'>~&lt;/text>
&lt;text text-anchor='middle' x='368' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='368' y='628' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='368' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='368' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='368' y='756' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='368' y='788' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='368' y='820' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='368' y='852' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='368' y='868' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='368' y='900' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='368' y='1076' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='368' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='368' y='1172' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='1236' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='368' y='1268' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='368' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='368' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='1588' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='368' y='1604' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='368' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='368' y='1700' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='376' y='20' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='376' y='196' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='376' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='340' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='376' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='376' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='376' y='420' fill='currentColor' style='font-size:1em'>5&lt;/text>
&lt;text text-anchor='middle' x='376' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='376' y='628' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='376' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='692' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='376' y='724' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='376' y='756' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='376' y='788' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='376' y='836' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='852' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='376' y='868' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='900' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='376' y='1076' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='376' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='376' y='1172' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='376' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='376' y='1236' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='376' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='376' y='1332' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='376' y='1540' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='376' y='1556' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='376' y='1588' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='376' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='376' y='1636' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='376' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='376' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='384' y='20' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='384' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='384' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='384' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='340' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='384' y='356' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='384' y='420' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='384' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='384' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='384' y='660' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='384' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='788' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='384' y='820' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='852' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='384' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='900' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='1076' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='384' y='1172' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='384' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='384' y='1236' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='384' y='1332' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='384' y='1508' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='384' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='384' y='1572' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='384' y='1604' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='384' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='384' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='384' y='1700' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='392' y='196' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='392' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='392' y='324' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='392' y='340' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='392' y='356' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='392' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='392' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='392' y='628' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='392' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='392' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='756' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='392' y='788' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='392' y='820' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='836' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='392' y='852' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='392' y='868' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='392' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='392' y='1076' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='392' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='392' y='1172' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='392' y='1204' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='392' y='1268' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='392' y='1508' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='392' y='1540' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='392' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='392' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='392' y='1604' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='392' y='1636' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='392' y='1668' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='392' y='1700' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='400' y='20' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='400' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='400' y='388' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='400' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='400' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='400' y='692' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='400' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='400' y='756' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='400' y='788' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='400' y='820' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='400' y='836' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='400' y='852' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='400' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='400' y='1076' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='400' y='1172' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='400' y='1204' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='400' y='1236' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='400' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='400' y='1508' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='400' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='400' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='400' y='1572' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='400' y='1588' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='400' y='1604' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='400' y='1636' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='196' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='408' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='408' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='452' fill='currentColor' style='font-size:1em'>8&lt;/text>
&lt;text text-anchor='middle' x='408' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='408' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='408' y='692' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='408' y='724' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='408' y='756' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='408' y='788' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='820' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='408' y='836' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='408' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='408' y='900' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='408' y='1076' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='408' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='408' y='1236' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='408' y='1268' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='408' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='408' y='1508' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='408' y='1540' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='408' y='1556' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='408' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='408' y='1588' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='408' y='1668' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='408' y='1700' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='416' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='416' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='324' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='416' y='356' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='416' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='416' y='452' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='416' y='660' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='416' y='692' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='416' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='756' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='416' y='788' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='416' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='836' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='416' y='852' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='416' y='868' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='416' y='900' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='1076' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='416' y='1204' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='416' y='1236' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='416' y='1332' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='416' y='1508' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='416' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='416' y='1572' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='416' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='1604' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='416' y='1636' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='416' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='416' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='424' y='196' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='324' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='424' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='424' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='424' y='452' fill='currentColor' style='font-size:1em'>%&lt;/text>
&lt;text text-anchor='middle' x='424' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='424' y='660' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='424' y='692' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='424' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='756' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='424' y='788' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='852' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='424' y='868' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='424' y='900' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='424' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='424' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='424' y='1236' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='424' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='424' y='1332' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='424' y='1508' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='424' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='424' y='1556' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='424' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='424' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='424' y='1668' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='424' y='1700' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='432' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='432' y='196' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='432' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='432' y='356' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='432' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='432' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='432' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='432' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='432' y='724' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='432' y='756' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='432' y='788' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='432' y='820' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='432' y='836' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='432' y='852' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='432' y='868' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='900' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='432' y='1076' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='432' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='432' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='1236' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='432' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='432' y='1508' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='432' y='1540' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='432' y='1556' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='432' y='1572' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='432' y='1588' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='432' y='1604' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='432' y='1636' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='432' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='432' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='440' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='440' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='440' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='440' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='440' y='356' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='440' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='440' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='440' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='440' y='692' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='440' y='756' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='440' y='788' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='440' y='820' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='440' y='852' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='440' y='1076' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='440' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='440' y='1236' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='440' y='1332' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='440' y='1556' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='440' y='1572' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='440' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='440' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='448' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='448' y='228' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='448' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='448' y='356' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='448' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='448' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='448' y='452' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='448' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='448' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='448' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='724' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='448' y='756' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='448' y='788' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='448' y='820' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='448' y='836' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='448' y='852' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='868' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='448' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='1076' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='448' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='448' y='1204' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='448' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='448' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='448' y='1540' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='448' y='1572' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='448' y='1588' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='448' y='1604' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='448' y='1636' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='448' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='448' y='1700' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='456' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='196' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='456' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='456' y='356' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='456' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='456' y='692' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='456' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='456' y='820' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='456' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='456' y='852' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='456' y='868' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='456' y='900' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='456' y='1140' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='456' y='1204' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='456' y='1236' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='456' y='1268' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='456' y='1332' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='456' y='1508' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='1540' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='1556' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='456' y='1572' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='456' y='1588' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='456' y='1604' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='456' y='1636' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='456' y='1668' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='456' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='464' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='464' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='464' y='324' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='464' y='356' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='464' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='464' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='464' y='628' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='464' y='692' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='464' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='464' y='836' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='464' y='852' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='464' y='868' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='464' y='900' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='464' y='1076' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='464' y='1236' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='464' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='464' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='464' y='1540' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='464' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='464' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='464' y='1588' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='464' y='1604' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='464' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='464' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='472' y='20' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='472' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='472' y='228' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='472' y='324' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='472' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='472' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='472' y='420' fill='currentColor' style='font-size:1em'>~&lt;/text>
&lt;text text-anchor='middle' x='472' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='472' y='628' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='472' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='472' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='472' y='724' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='472' y='820' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='472' y='836' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='472' y='852' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='472' y='868' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='472' y='900' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='472' y='1076' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='472' y='1140' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='472' y='1204' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='472' y='1236' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='472' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='472' y='1332' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='472' y='1508' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='472' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='472' y='1556' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='472' y='1572' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='472' y='1604' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='472' y='1636' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='472' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='472' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='480' y='196' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='480' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='480' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='480' y='356' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='480' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='480' y='420' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='480' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='480' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='480' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='480' y='692' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='480' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='480' y='820' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='480' y='836' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='480' y='852' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='480' y='868' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='480' y='900' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='480' y='1076' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='480' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='480' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='480' y='1236' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='480' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='480' y='1332' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='480' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='480' y='1588' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='480' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='480' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='480' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='488' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='488' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='488' y='324' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='488' y='388' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='488' y='420' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='488' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='488' y='692' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='820' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='488' y='836' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='488' y='852' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='488' y='868' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='488' y='900' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='1076' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='488' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='488' y='1236' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='488' y='1268' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='488' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='1540' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='488' y='1556' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='488' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='488' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='488' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='1636' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='488' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='496' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='496' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='496' y='228' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='496' y='356' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='496' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='496' y='420' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='496' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='496' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='496' y='692' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='496' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='496' y='836' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='496' y='852' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='496' y='868' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='496' y='900' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='496' y='1076' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='496' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='496' y='1236' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='496' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='496' y='1332' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='496' y='1540' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='496' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='496' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='496' y='1588' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='496' y='1604' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='496' y='1636' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='496' y='1668' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='496' y='1700' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='504' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='504' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='504' y='324' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='504' y='356' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='504' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='504' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='504' y='628' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='504' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='504' y='692' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='504' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='504' y='820' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='504' y='836' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='504' y='868' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='504' y='900' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='504' y='1076' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='504' y='1140' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='504' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='504' y='1236' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='504' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='504' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='504' y='1508' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='504' y='1540' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='504' y='1556' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='504' y='1588' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='504' y='1604' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='504' y='1636' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='504' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='504' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='512' y='20' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='512' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='512' y='324' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='512' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='512' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='512' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='512' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='512' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='512' y='660' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='512' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='512' y='820' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='512' y='836' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='512' y='868' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='512' y='1076' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='512' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='512' y='1204' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='512' y='1236' fill='currentColor' style='font-size:1em'>*&lt;/text>
&lt;text text-anchor='middle' x='512' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='512' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='512' y='1540' fill='currentColor' style='font-size:1em'>j&lt;/text>
&lt;text text-anchor='middle' x='512' y='1556' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='512' y='1572' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='512' y='1588' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='512' y='1636' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='512' y='1668' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='520' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='520' y='196' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='520' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='520' y='324' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='520' y='388' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='520' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='520' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='520' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='520' y='868' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='520' y='1076' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='520' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='520' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='520' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='520' y='1508' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='520' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='520' y='1572' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='520' y='1588' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='520' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='520' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='520' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='528' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='528' y='196' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='528' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='528' y='324' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='528' y='356' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='528' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='528' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='528' y='660' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='528' y='724' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='528' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='528' y='1076' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='528' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='528' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='528' y='1332' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='528' y='1508' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='528' y='1540' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='528' y='1556' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='528' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='528' y='1588' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='528' y='1604' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='528' y='1636' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='528' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='528' y='1700' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='536' y='20' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='536' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='536' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='536' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='536' y='388' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='536' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='536' y='452' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='536' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='536' y='724' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='536' y='820' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='536' y='1076' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='536' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='536' y='1204' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='536' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='536' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='536' y='1508' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='536' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='536' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='536' y='1572' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='536' y='1588' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='536' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='536' y='1668' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='536' y='1700' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='544' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='544' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='544' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='544' y='324' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='544' y='356' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='544' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='544' y='452' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='544' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='544' y='660' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='544' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='544' y='820' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='544' y='1140' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='544' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='544' y='1268' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='544' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='544' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='544' y='1556' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='544' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='544' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='544' y='1636' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='544' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='552' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='552' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='552' y='324' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='552' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='552' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='552' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='552' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='552' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='552' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='552' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='552' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='552' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='552' y='1332' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='552' y='1508' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='552' y='1540' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='552' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='552' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='552' y='1588' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='552' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='552' y='1636' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='552' y='1668' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='552' y='1700' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='560' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='560' y='324' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='560' y='356' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='560' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='560' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='560' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='560' y='628' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='560' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='560' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='560' y='820' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='560' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='560' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='560' y='1508' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='560' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='560' y='1556' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='560' y='1572' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='560' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='560' y='1604' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='560' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='560' y='1668' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='560' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='568' y='20' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='568' y='196' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='568' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='568' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='568' y='356' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='568' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='568' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='568' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='568' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='568' y='660' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='568' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='568' y='820' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='568' y='1140' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='568' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='568' y='1332' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='568' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='568' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='568' y='1572' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='568' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='568' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='568' y='1636' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='568' y='1700' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='576' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='576' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='576' y='228' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='576' y='324' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='576' y='356' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='576' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='576' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='576' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='576' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='576' y='1204' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='576' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='576' y='1332' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='576' y='1508' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='576' y='1540' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='576' y='1556' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='576' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='576' y='1588' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='576' y='1668' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='576' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='584' y='20' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='584' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='584' y='324' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='584' y='356' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='584' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='584' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='584' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='584' y='660' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='584' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='584' y='820' fill='currentColor' style='font-size:1em'>1&lt;/text>
&lt;text text-anchor='middle' x='584' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='584' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='584' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='584' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='584' y='1556' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='584' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='584' y='1588' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='584' y='1604' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='584' y='1636' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='584' y='1668' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='584' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='592' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='592' y='196' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='592' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='592' y='324' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='592' y='356' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='592' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='592' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='592' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='592' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='592' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='592' y='820' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='592' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='592' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='592' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='592' y='1332' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='592' y='1508' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='592' y='1540' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='592' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='592' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='592' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='592' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='592' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='600' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='600' y='324' fill='currentColor' style='font-size:1em'>?&lt;/text>
&lt;text text-anchor='middle' x='600' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='600' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='600' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='600' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='600' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='600' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='600' y='724' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='600' y='820' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='600' y='1204' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='600' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='600' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='600' y='1508' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='600' y='1556' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='600' y='1572' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='600' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='600' y='1604' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='600' y='1636' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='600' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='600' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='608' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='608' y='196' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='608' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='608' y='356' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='608' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='608' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='608' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='608' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='608' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='608' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='608' y='1268' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='608' y='1332' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='608' y='1508' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='608' y='1540' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='608' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='608' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='608' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='608' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='616' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='616' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='616' y='356' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='616' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='616' y='420' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='616' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='616' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='616' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='616' y='820' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='616' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='616' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='616' y='1332' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='616' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='616' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='616' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='616' y='1604' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='616' y='1636' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='616' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='616' y='1700' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='624' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='624' y='196' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='624' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='624' y='356' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='624' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='624' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='624' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='624' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='624' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='624' y='820' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='624' y='1268' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='624' y='1332' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='624' y='1508' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='624' y='1540' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='624' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='624' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='624' y='1588' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='624' y='1604' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='624' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='624' y='1668' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='624' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='632' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='632' y='196' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='632' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='632' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='632' y='420' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='632' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='632' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='632' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='632' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='632' y='820' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='632' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='632' y='1204' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='632' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='632' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='632' y='1540' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='632' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='632' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='632' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='632' y='1604' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='632' y='1636' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='632' y='1668' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='632' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='640' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='640' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='640' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='640' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='640' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='640' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='640' y='660' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='640' y='724' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='640' y='820' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='640' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='640' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='640' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='640' y='1332' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='640' y='1508' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='640' y='1540' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='640' y='1556' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='640' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='640' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='640' y='1700' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='648' y='196' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='648' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='648' y='388' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='648' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='648' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='648' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='648' y='724' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='648' y='1140' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='648' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='648' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='648' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='648' y='1508' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='648' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='648' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='648' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='648' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='648' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='648' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='648' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='648' y='1700' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='656' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='656' y='196' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='656' y='420' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='656' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='656' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='656' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='656' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='656' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='656' y='1508' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='656' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='656' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='656' y='1588' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='656' y='1604' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='656' y='1636' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='656' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='664' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='664' y='196' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='664' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='664' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='664' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='664' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='664' y='660' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='664' y='724' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='664' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='664' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='664' y='1268' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='664' y='1332' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='664' y='1540' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='664' y='1556' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='664' y='1572' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='664' y='1604' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='664' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='664' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='672' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='672' y='196' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='672' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='672' y='388' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='672' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='672' y='452' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='672' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='672' y='724' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='672' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='672' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='672' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='672' y='1332' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='672' y='1540' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='672' y='1556' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='672' y='1572' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='672' y='1588' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='672' y='1604' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='672' y='1636' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='672' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='672' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='680' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='680' y='196' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='680' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='680' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='680' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='680' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='680' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='680' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='680' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='680' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='680' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='680' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='680' y='1540' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='680' y='1588' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='680' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='680' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='680' y='1700' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='688' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='688' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='688' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='688' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='688' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='688' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='688' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='688' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='688' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='688' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='688' y='1540' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='688' y='1556' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='688' y='1572' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='688' y='1588' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='688' y='1604' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='688' y='1636' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='688' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='696' y='20' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='696' y='420' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='696' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='696' y='660' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='696' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='696' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='696' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='696' y='1332' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='696' y='1540' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='696' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='696' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='696' y='1588' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='696' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='696' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='696' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='704' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='704' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='704' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='704' y='628' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='704' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='704' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='704' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='704' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='704' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='704' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='704' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='704' y='1556' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='704' y='1572' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='704' y='1604' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='704' y='1636' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='704' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='704' y='1700' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='712' y='20' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='712' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='712' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='712' y='420' fill='currentColor' style='font-size:1em'>B&lt;/text>
&lt;text text-anchor='middle' x='712' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='712' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='712' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='712' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='712' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='712' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='712' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='712' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='712' y='1540' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='712' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='712' y='1572' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='712' y='1588' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='712' y='1604' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='712' y='1636' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='712' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='712' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='720' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='720' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='720' y='420' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='720' y='628' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='720' y='660' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='720' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='720' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='720' y='1204' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='720' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='720' y='1332' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='720' y='1540' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='720' y='1588' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='720' y='1636' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='720' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='720' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='728' y='228' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='728' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='728' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='728' y='452' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='728' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='728' y='724' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='728' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='728' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='728' y='1332' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='728' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='728' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='728' y='1572' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='728' y='1588' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='728' y='1604' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='728' y='1636' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='728' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='728' y='1700' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='736' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='736' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='736' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='736' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='736' y='628' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='736' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='736' y='1140' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='736' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='736' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='736' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='736' y='1540' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='736' y='1556' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='736' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='736' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='736' y='1604' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='744' y='20' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='744' y='228' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='744' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='744' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='744' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='744' y='660' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='744' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='744' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='744' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='744' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='744' y='1332' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='744' y='1540' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='744' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='744' y='1572' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='744' y='1588' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='744' y='1604' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='744' y='1636' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='744' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='744' y='1700' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='752' y='20' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='752' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='752' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='752' y='420' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='752' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='752' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='752' y='724' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='752' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='752' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='752' y='1268' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='752' y='1540' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='752' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='752' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='752' y='1588' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='752' y='1604' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='752' y='1636' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='752' y='1668' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='752' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='760' y='20' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='760' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='760' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='760' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='760' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='760' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='760' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='760' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='760' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='760' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='760' y='1332' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='760' y='1540' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='760' y='1556' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='760' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='760' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='760' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='760' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='768' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='768' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='768' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='768' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='768' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='768' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='768' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='768' y='1332' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='768' y='1540' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='768' y='1572' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='768' y='1588' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='768' y='1604' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='768' y='1636' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='768' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='768' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='776' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='776' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='776' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='776' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='776' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='776' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='776' y='1268' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='776' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='776' y='1540' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='776' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='776' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='776' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='776' y='1604' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='776' y='1636' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='776' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='784' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='784' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='784' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='784' y='420' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='784' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='784' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='784' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='784' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='784' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='784' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='784' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='784' y='1540' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='784' y='1556' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='784' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='784' y='1604' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='784' y='1636' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='784' y='1668' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='784' y='1700' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='792' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='792' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='792' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='792' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='792' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='792' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='792' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='792' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='792' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='792' y='1268' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='792' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='792' y='1572' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='792' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='792' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='792' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='800' y='228' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='800' y='388' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='800' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='800' y='452' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='800' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='800' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='800' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='800' y='1204' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='800' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='800' y='1540' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='800' y='1556' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='800' y='1572' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='800' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='800' y='1604' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='800' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='808' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='808' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='808' y='420' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='808' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='808' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='808' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='808' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='808' y='1140' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='808' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='808' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='808' y='1332' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='808' y='1540' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='808' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='808' y='1572' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='808' y='1588' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='808' y='1604' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='808' y='1636' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='808' y='1668' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='808' y='1700' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='816' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='816' y='228' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='816' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='816' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='816' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='816' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='816' y='660' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='816' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='816' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='816' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='816' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='816' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='816' y='1540' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='816' y='1556' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='816' y='1572' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='816' y='1604' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='816' y='1636' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='816' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='816' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='824' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='824' y='228' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='824' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='824' y='420' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='824' y='452' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='824' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='824' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='824' y='724' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='824' y='1332' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='824' y='1540' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='824' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='824' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='824' y='1636' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='824' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='832' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='832' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='832' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='832' y='724' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='832' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='832' y='1204' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='832' y='1268' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='832' y='1332' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='832' y='1572' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='832' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='832' y='1668' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='832' y='1700' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='840' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='840' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='840' y='420' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='840' y='452' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='840' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='840' y='660' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='840' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='840' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='840' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='840' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='840' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='840' y='1572' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='840' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='840' y='1700' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='848' y='20' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='848' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='848' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='848' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='848' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='848' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='848' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='848' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='848' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='848' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='848' y='1332' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='848' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='848' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='848' y='1588' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='848' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='848' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='856' y='20' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='856' y='228' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='856' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='856' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='856' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='856' y='628' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='856' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='856' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='856' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='856' y='1204' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='856' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='856' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='856' y='1556' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='856' y='1572' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='856' y='1588' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='856' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='864' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='864' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='864' y='388' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='864' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='864' y='628' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='864' y='660' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='864' y='724' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='864' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='864' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='864' y='1332' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='864' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='864' y='1572' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='864' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='864' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='872' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='872' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='872' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='872' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='872' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='872' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='872' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='872' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='872' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='872' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='872' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='872' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='872' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='872' y='1572' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='872' y='1588' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='872' y='1668' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='872' y='1700' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='880' y='20' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='880' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='880' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='880' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='880' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='880' y='628' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='880' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='880' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='880' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='880' y='1204' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='880' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='880' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='880' y='1556' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='880' y='1572' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='880' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='880' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='888' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='888' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='888' y='388' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='888' y='420' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='888' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='888' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='888' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='888' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='888' y='1140' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='888' y='1204' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='888' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='888' y='1332' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='888' y='1556' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='888' y='1572' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='888' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='888' y='1668' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='888' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='896' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='896' y='228' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='896' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='896' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='896' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='896' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='896' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='896' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='896' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='896' y='1556' fill='currentColor' style='font-size:1em'>?&lt;/text>
&lt;text text-anchor='middle' x='896' y='1572' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='896' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='896' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='896' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='904' y='20' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='904' y='388' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='904' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='904' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='904' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='904' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='904' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='904' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='904' y='1572' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='904' y='1668' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='904' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='912' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='912' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='912' y='388' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='912' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='912' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='912' y='628' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='912' y='660' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='912' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='912' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='912' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='912' y='1332' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='912' y='1556' fill='currentColor' style='font-size:1em'>W&lt;/text>
&lt;text text-anchor='middle' x='912' y='1572' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='912' y='1588' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='912' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='912' y='1700' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='920' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='920' y='228' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='920' y='388' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='920' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='920' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='920' y='628' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='920' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='920' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='920' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='920' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='920' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='920' y='1332' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='920' y='1556' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='920' y='1572' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='920' y='1588' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='920' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='928' y='20' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='928' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='928' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='928' y='420' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='928' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='928' y='628' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='928' y='660' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='928' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='928' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='928' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='928' y='1332' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='928' y='1556' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='928' y='1572' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='928' y='1588' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='928' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='928' y='1700' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='936' y='20' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='936' y='388' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='936' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='936' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='936' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='936' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='936' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='936' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='936' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='936' y='1588' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='936' y='1668' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='936' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='944' y='228' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='944' y='420' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='944' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='944' y='628' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='944' y='660' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='944' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='944' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='944' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='944' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='944' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='944' y='1588' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='944' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='952' y='20' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='952' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='952' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='952' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='952' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='952' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='952' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='952' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='952' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='952' y='1204' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='952' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='952' y='1588' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='952' y='1668' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='952' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='960' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='960' y='228' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='960' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='960' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='960' y='452' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='960' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='960' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='960' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='960' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='960' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='960' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='960' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='960' y='1556' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='960' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='960' y='1700' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='968' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='968' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='968' y='388' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='968' y='420' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='968' y='628' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='968' y='660' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='968' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='968' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='968' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='968' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='968' y='1556' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='968' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='976' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='976' y='388' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='976' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='976' y='452' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='976' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='976' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='976' y='1140' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='976' y='1204' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='976' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='976' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='976' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='976' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='984' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='984' y='228' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='984' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='984' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='984' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='984' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='984' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='984' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='984' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='984' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='984' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='984' y='1556' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='984' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='992' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='992' y='388' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='992' y='452' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='992' y='628' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='992' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='992' y='724' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='992' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='992' y='1204' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='992' y='1268' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='992' y='1556' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='992' y='1668' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='992' y='1700' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1000' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1000' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1000' y='420' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1000' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1000' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1000' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1000' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1000' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1000' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1008' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1008' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1008' y='388' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1008' y='420' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1008' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1008' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1008' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1008' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1008' y='1204' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1008' y='1268' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='1008' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1008' y='1556' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1008' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1008' y='1700' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1016' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1016' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1016' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1016' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1016' y='628' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1016' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1016' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1016' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1016' y='1332' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1016' y='1556' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1016' y='1668' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1016' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1024' y='20' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1024' y='228' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1024' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1024' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1024' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1024' y='660' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1332' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1556' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1024' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1032' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1032' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1032' y='388' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1032' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1032' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1032' y='628' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1032' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1032' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1332' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1556' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1032' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1040' y='228' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1040' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1040' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1040' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1040' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1040' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1040' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1040' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1040' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1040' y='1556' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1040' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1040' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1048' y='20' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='1048' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1048' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='1048' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1048' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1048' y='628' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1048' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1048' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1048' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1048' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1048' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1048' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1056' y='388' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='1056' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1056' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1056' y='724' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1056' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1056' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1056' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1056' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1056' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1064' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1064' y='228' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1064' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1064' y='420' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1064' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1064' y='628' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1064' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1064' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1064' y='1204' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1064' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1064' y='1332' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1064' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1064' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1072' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1072' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1072' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1072' y='420' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1072' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1072' y='628' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1072' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1072' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1072' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1072' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1072' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1072' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1080' y='20' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1080' y='228' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1080' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='1080' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1080' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1080' y='628' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1080' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1080' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1080' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1080' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1080' y='1332' fill='currentColor' style='font-size:1em'>M&lt;/text>
&lt;text text-anchor='middle' x='1080' y='1668' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='1080' y='1700' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1088' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1088' y='228' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1088' y='420' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1088' y='452' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='1088' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1088' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1088' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1088' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1088' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1088' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1088' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1096' y='228' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1096' y='388' fill='currentColor' style='font-size:1em'>(&lt;/text>
&lt;text text-anchor='middle' x='1096' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1096' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1096' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1096' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1096' y='1204' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1096' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1096' y='1332' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1096' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1096' y='1700' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='1104' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1104' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1104' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1104' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1104' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1104' y='628' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1104' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1104' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1104' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1104' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1104' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1104' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1104' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1112' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1112' y='228' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1112' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1112' y='420' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1112' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1112' y='628' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1112' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1112' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1112' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1112' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1112' y='1668' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1112' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1120' y='20' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1120' y='388' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1120' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1120' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1120' y='628' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1120' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1120' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1120' y='1204' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1120' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1120' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1120' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1128' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1128' y='228' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1128' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1128' y='420' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='1128' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1128' y='628' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1128' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1128' y='724' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1128' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1128' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1128' y='1332' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1128' y='1668' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1128' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1136' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1136' y='628' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1136' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1136' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1136' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1136' y='1268' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1136' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1136' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1144' y='20' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1144' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1144' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1144' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1144' y='660' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1144' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1144' y='1140' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='1144' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1144' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1144' y='1332' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1144' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1144' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1152' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1152' y='228' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1152' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='1152' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1152' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1152' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1152' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1152' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1152' y='1332' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1152' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1152' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1160' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1160' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1160' y='388' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='1160' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1160' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1160' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1160' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1160' y='1204' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1160' y='1268' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1160' y='1332' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1160' y='1700' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1168' y='20' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1168' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1168' y='388' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1168' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1168' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1168' y='724' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='1168' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1168' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1168' y='1332' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1168' y='1668' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1168' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1176' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1176' y='228' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='1176' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1176' y='420' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1176' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1176' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1176' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1176' y='1204' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1176' y='1268' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1176' y='1332' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1176' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1176' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1184' y='388' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1184' y='420' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1184' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1184' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1184' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1184' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1184' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1184' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1184' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1192' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1192' y='228' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1192' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='1192' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1192' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1192' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1192' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1192' y='1204' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1192' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1192' y='1332' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1192' y='1668' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1192' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1200' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1200' y='228' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1200' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1200' y='452' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1200' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1200' y='724' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='1200' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1200' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1200' y='1332' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1200' y='1668' fill='currentColor' style='font-size:1em'>-&lt;/text>
&lt;text text-anchor='middle' x='1200' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1208' y='20' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1208' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1208' y='388' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1208' y='420' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1208' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1208' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1208' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1208' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1208' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1208' y='1332' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1208' y='1668' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1208' y='1700' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1216' y='20' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1216' y='228' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1216' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1216' y='420' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1216' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1216' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1216' y='1140' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1216' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1216' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1216' y='1332' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1216' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1216' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1224' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1224' y='228' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1224' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1224' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1224' y='660' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='1224' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1224' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1224' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1224' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1224' y='1332' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1224' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1224' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1232' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1232' y='228' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1232' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1232' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1232' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1232' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1232' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1232' y='1204' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1232' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1232' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1240' y='228' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1240' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1240' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1240' y='452' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1240' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1240' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1240' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1240' y='1668' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1240' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1248' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1248' y='228' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1248' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1248' y='420' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1248' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1248' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1248' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1248' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1248' y='1204' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1248' y='1268' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1248' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1248' y='1700' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1256' y='20' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1256' y='228' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1256' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1256' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1256' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1256' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1256' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1256' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1256' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1256' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1256' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1256' y='1700' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1264' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1264' y='228' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1264' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1264' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1264' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1264' y='724' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1264' y='1140' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1264' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1264' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1272' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1272' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1272' y='420' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1272' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1272' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1272' y='724' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1272' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1272' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1272' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1272' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1272' y='1700' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='1280' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1280' y='388' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1280' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1280' y='1140' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1280' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1280' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1280' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1280' y='1700' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1288' y='20' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='1288' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1288' y='420' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1288' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1288' y='660' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1288' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1288' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1288' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1288' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1288' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1296' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1296' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1296' y='420' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1296' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1296' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1296' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1296' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1296' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1296' y='1668' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1296' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1304' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1304' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1304' y='420' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1304' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1304' y='724' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1304' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1304' y='1204' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1304' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1304' y='1700' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1312' y='20' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1312' y='420' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1312' y='452' fill='currentColor' style='font-size:1em'>4&lt;/text>
&lt;text text-anchor='middle' x='1312' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1312' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1312' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1312' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1312' y='1668' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='1312' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1320' y='388' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1320' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1320' y='452' fill='currentColor' style='font-size:1em'>0&lt;/text>
&lt;text text-anchor='middle' x='1320' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1320' y='1204' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1320' y='1268' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1320' y='1668' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1328' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1328' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1328' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1328' y='452' fill='currentColor' style='font-size:1em'>%&lt;/text>
&lt;text text-anchor='middle' x='1328' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1328' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1328' y='1140' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1328' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1328' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1328' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1336' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1336' y='388' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1336' y='420' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1336' y='452' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1336' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1336' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1336' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1336' y='1204' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='1336' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1336' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1336' y='1700' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1344' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1344' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1344' y='420' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1344' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1344' y='724' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1344' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1344' y='1204' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1344' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1344' y='1668' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1344' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1352' y='388' fill='currentColor' style='font-size:1em'>)&lt;/text>
&lt;text text-anchor='middle' x='1352' y='420' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1352' y='452' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='1352' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1352' y='1204' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1352' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1352' y='1668' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1360' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1360' y='388' fill='currentColor' style='font-size:1em'>"&lt;/text>
&lt;text text-anchor='middle' x='1360' y='420' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1360' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1360' y='660' fill='currentColor' style='font-size:1em'>S&lt;/text>
&lt;text text-anchor='middle' x='1360' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1360' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1360' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1360' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1368' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1368' y='388' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1368' y='420' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1368' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1368' y='660' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='1368' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1368' y='1140' fill='currentColor' style='font-size:1em'>q&lt;/text>
&lt;text text-anchor='middle' x='1368' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1368' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1368' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1376' y='420' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1376' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1376' y='660' fill='currentColor' style='font-size:1em'>D&lt;/text>
&lt;text text-anchor='middle' x='1376' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1376' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1376' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1376' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1376' y='1668' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1376' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1384' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1384' y='388' fill='currentColor' style='font-size:1em'>N&lt;/text>
&lt;text text-anchor='middle' x='1384' y='452' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1384' y='660' fill='currentColor' style='font-size:1em'>2&lt;/text>
&lt;text text-anchor='middle' x='1384' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1384' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1384' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1384' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1384' y='1700' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1392' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1392' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1392' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1392' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1392' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1392' y='1268' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1400' y='20' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1400' y='388' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1400' y='724' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1400' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1400' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1400' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1400' y='1668' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1400' y='1700' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1408' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1408' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1408' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1408' y='660' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1408' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1408' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1408' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1408' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1408' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1416' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1416' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1416' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1416' y='724' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1416' y='1140' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1416' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1416' y='1668' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1416' y='1700' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1424' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1424' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1424' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1424' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1424' y='724' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1424' y='1140' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1424' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1424' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1424' y='1668' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1424' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1432' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1432' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1432' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1432' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1432' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1432' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1432' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1440' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1440' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1440' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1440' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1440' y='1140' fill='currentColor' style='font-size:1em'>Y&lt;/text>
&lt;text text-anchor='middle' x='1440' y='1204' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1440' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1440' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1448' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1448' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1448' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1448' y='660' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1448' y='724' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1448' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1448' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1448' y='1268' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1448' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1448' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1456' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1456' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1456' y='724' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1456' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1456' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1456' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1456' y='1700' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1464' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1464' y='388' fill='currentColor' style='font-size:1em'>I&lt;/text>
&lt;text text-anchor='middle' x='1464' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1464' y='660' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1464' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1464' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1464' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1464' y='1668' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1472' y='20' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1472' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1472' y='724' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1472' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1472' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1472' y='1268' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1472' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1472' y='1700' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1480' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1480' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1480' y='660' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='1480' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1480' y='1140' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1480' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1480' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1488' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1488' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1488' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1488' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1488' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1488' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1488' y='1268' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='1488' y='1668' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1488' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1496' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1496' y='388' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1496' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1496' y='724' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1496' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1496' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1496' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1496' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1496' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1504' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1504' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1504' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1504' y='660' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1504' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1504' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1504' y='1268' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1504' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1504' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1512' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1512' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1512' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1512' y='660' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1512' y='724' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1512' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1512' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1512' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1512' y='1668' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1512' y='1700' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1520' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1520' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1520' y='724' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1520' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1520' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1520' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1528' y='20' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1528' y='388' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1528' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1528' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1528' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1528' y='1204' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1528' y='1268' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1528' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1536' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1536' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1536' y='452' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1536' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1536' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1536' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1536' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1536' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1544' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1544' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1544' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1544' y='660' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1544' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1544' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1544' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1552' y='452' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1552' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1552' y='724' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1552' y='1140' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1552' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1552' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1552' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1552' y='1700' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1560' y='20' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1560' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1560' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1560' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1560' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1560' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1560' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1560' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1560' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1568' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1568' y='452' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1568' y='660' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1568' y='724' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1568' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1568' y='1204' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1568' y='1668' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1568' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1576' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1576' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1576' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1576' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1576' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1576' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1576' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1576' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1576' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1584' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1584' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1584' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1584' y='724' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1584' y='1268' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1584' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1584' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1592' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1592' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1592' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1592' y='724' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1592' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1592' y='1204' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1592' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1592' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1592' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1600' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1600' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1600' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1600' y='660' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1600' y='724' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1600' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1600' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1600' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1600' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1608' y='20' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1608' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1608' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1608' y='724' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1608' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1608' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1608' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1608' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1608' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1616' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1616' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1616' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1616' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1616' y='724' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1616' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1616' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1616' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1616' y='1668' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1616' y='1700' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1624' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1624' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1624' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1624' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1624' y='1700' fill='currentColor' style='font-size:1em'>:&lt;/text>
&lt;text text-anchor='middle' x='1632' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1632' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1632' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1632' y='660' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1632' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1632' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1632' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1632' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1640' y='20' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1640' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1640' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1640' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1640' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1640' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1640' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1640' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1648' y='20' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1648' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1648' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1648' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1648' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1648' y='1268' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1648' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1648' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1656' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1656' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1656' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1656' y='1140' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1656' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1656' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1656' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1656' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1664' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1664' y='452' fill='currentColor' style='font-size:1em'>—&lt;/text>
&lt;text text-anchor='middle' x='1664' y='660' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1664' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1664' y='1668' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1664' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1672' y='20' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1672' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1672' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1672' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1672' y='1140' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1672' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1672' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1672' y='1668' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1672' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1680' y='20' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1680' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1680' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1680' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1680' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1680' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1680' y='1268' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1680' y='1668' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1680' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1688' y='20' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1688' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1688' y='452' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1688' y='660' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1688' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1688' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1688' y='1268' fill='currentColor' style='font-size:1em'>;&lt;/text>
&lt;text text-anchor='middle' x='1688' y='1668' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1688' y='1700' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1696' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1696' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1696' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1696' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1696' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1696' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1696' y='1668' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1704' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1704' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1704' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1704' y='1140' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='1704' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1704' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1704' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1704' y='1700' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1712' y='20' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1712' y='388' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1712' y='452' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1712' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1712' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1712' y='1268' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1712' y='1668' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1712' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1720' y='20' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1720' y='388' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='1720' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1720' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1720' y='1140' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1720' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1720' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1720' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1720' y='1700' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1728' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1728' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1728' y='1204' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1728' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1728' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1736' y='20' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1736' y='388' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='1736' y='452' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1736' y='1140' fill='currentColor' style='font-size:1em'>Y&lt;/text>
&lt;text text-anchor='middle' x='1736' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1736' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1744' y='20' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1744' y='388' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1744' y='452' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1744' y='660' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1744' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1744' y='1204' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1744' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1744' y='1700' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1752' y='20' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1752' y='388' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1752' y='452' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1752' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1752' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1752' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1752' y='1268' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1752' y='1668' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1752' y='1700' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1760' y='20' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1760' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1760' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1760' y='660' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1760' y='1140' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='1760' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1760' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1760' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1768' y='20' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1768' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1768' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1768' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1768' y='1204' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1768' y='1268' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1768' y='1668' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1768' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1776' y='20' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1776' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1776' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1776' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1776' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1776' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1776' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1784' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1784' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1784' y='1268' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1784' y='1668' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1784' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1792' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1792' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1792' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1792' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1792' y='1204' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1792' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1792' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1792' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1800' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1800' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1800' y='1204' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1800' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1800' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1800' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1808' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1808' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1808' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1808' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1808' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1808' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1808' y='1668' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1808' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1816' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1816' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1816' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1816' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1816' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1824' y='452' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1824' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1824' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1824' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1824' y='1268' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1824' y='1668' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1824' y='1700' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1832' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='1832' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1832' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1832' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1832' y='1204' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1832' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1832' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1840' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1840' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1840' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1840' y='1140' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1840' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1840' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1840' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1848' y='388' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1848' y='452' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='1848' y='660' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1848' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1848' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1848' y='1668' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1848' y='1700' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1856' y='388' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1856' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1856' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1856' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1856' y='1204' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1856' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1856' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1864' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1864' y='1140' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1864' y='1268' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1864' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1864' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1872' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1872' y='452' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1872' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1872' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1872' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1872' y='1268' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1872' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1880' y='388' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1880' y='452' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1880' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1880' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1880' y='1204' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1880' y='1268' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1880' y='1700' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1888' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1888' y='452' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1888' y='660' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1888' y='1140' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1888' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1888' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1888' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1888' y='1700' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1896' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1896' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1896' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1896' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1896' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1896' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1904' y='388' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='1904' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1904' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1904' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1904' y='1204' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1904' y='1268' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1904' y='1668' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1904' y='1700' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='1912' y='388' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1912' y='452' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1912' y='1140' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1912' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1912' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1912' y='1668' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1912' y='1700' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1920' y='388' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='1920' y='452' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1920' y='660' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='1920' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1920' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1920' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1928' y='388' fill='currentColor' style='font-size:1em'>`&lt;/text>
&lt;text text-anchor='middle' x='1928' y='452' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1928' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1928' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1928' y='1204' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1928' y='1268' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='1928' y='1668' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='1928' y='1700' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1936' y='452' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1936' y='660' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='1936' y='1140' fill='currentColor' style='font-size:1em'>h&lt;/text>
&lt;text text-anchor='middle' x='1936' y='1204' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1936' y='1668' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1936' y='1700' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1944' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1944' y='452' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1944' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1944' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='1944' y='1204' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='1944' y='1268' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1944' y='1668' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1944' y='1700' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1952' y='388' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='1952' y='452' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1952' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1952' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1952' y='1204' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1952' y='1268' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1952' y='1668' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1952' y='1700' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='1960' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1960' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1960' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1960' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='1960' y='1700' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='1968' y='388' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='1968' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1968' y='1268' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1976' y='388' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1976' y='660' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1976' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1976' y='1204' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='1976' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1984' y='388' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='1984' y='660' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1984' y='1140' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='1984' y='1204' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='1984' y='1268' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='1992' y='388' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='1992' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='1992' y='1204' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='1992' y='1268' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='2000' y='388' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='2000' y='660' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='2000' y='1204' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='2000' y='1268' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='2008' y='388' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='2008' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2008' y='1140' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='2008' y='1204' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2008' y='1268' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2016' y='388' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='2016' y='660' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='2016' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='2016' y='1204' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='2016' y='1268' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='2024' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='2024' y='1140' fill='currentColor' style='font-size:1em'>x&lt;/text>
&lt;text text-anchor='middle' x='2024' y='1268' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='2032' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='2032' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='2040' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2040' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='2048' y='660' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='2048' y='1140' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='2064' y='660' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='2064' y='1140' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='2072' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='2080' y='660' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='2080' y='1140' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='2088' y='660' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='2088' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='2096' y='660' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='2096' y='1140' fill='currentColor' style='font-size:1em'>;&lt;/text>
&lt;text text-anchor='middle' x='2104' y='660' fill='currentColor' style='font-size:1em'>b&lt;/text>
&lt;text text-anchor='middle' x='2112' y='660' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='2112' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='2120' y='660' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2120' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='2128' y='660' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='2128' y='1140' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='2136' y='660' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;text text-anchor='middle' x='2144' y='1140' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='2152' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='2160' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='2168' y='1140' fill='currentColor' style='font-size:1em'>'&lt;/text>
&lt;text text-anchor='middle' x='2176' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='2184' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2200' y='1140' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='2208' y='1140' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='2216' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='2224' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='2240' y='1140' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='2248' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='2256' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2264' y='1140' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='2272' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2280' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='2288' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='2296' y='1140' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='2304' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='2312' y='1140' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='2328' y='1140' fill='currentColor' style='font-size:1em'>f&lt;/text>
&lt;text text-anchor='middle' x='2336' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='2344' y='1140' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='2352' y='1140' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='2360' y='1140' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='2368' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2384' y='1140' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='2392' y='1140' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='2400' y='1140' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='2408' y='1140' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='2416' y='1140' fill='currentColor' style='font-size:1em'>.&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div></content:encoded><category>Data Engineering</category><category>Career Development</category><category>AI</category><category>dbt</category><category>Data Quality</category><category>SQL</category><category>Productivity</category><category>VSCode</category><category>Claude</category></item><item><title>The Duct Tape Data Engineer</title><link>https://ghostinthedata.info/posts/2026/2026-01-24-duct-tape-data-engineer/</link><pubDate>Sat, 24 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-24-duct-tape-data-engineer/</guid><author>Chris Hillman</author><description>The engineers who ship 'ugly' but working solutions consistently outperform those waiting for perfect architectures. Here's why pragmatism beats elegance in data engineering—and how to embrace it without becoming reckless.</description><content:encoded>&lt;h3 id="the-engineer-who-ships">The Engineer Who Ships&lt;/h3>
&lt;br>
&lt;p>I want to tell you about a data engineer I worked with. Let&amp;rsquo;s call her Sarah.&lt;/p>
&lt;p>Sarah had a reputation. When business stakeholders had an urgent question—the kind that arrives at 4 PM on a Friday with the CEO&amp;rsquo;s name in the subject line—they went to Sarah. Not to the senior architect with the impeccable data model. Not to the platform team with their carefully orchestrated Airflow DAGs. They went to Sarah.&lt;/p>
&lt;p>Her code wasn&amp;rsquo;t pretty. She&amp;rsquo;d pull data from production databases with SQL queries that made the architecture team wince. She&amp;rsquo;d transform things in Python scripts that violated every design pattern in the book. She&amp;rsquo;d dump results into Excel because that&amp;rsquo;s where the CFO could actually use them.&lt;/p>
&lt;p>And here&amp;rsquo;s the thing: Sarah shipped. Every time. On time. With answers that moved the business forward.&lt;/p>
&lt;p>At the time, I thought Sarah was the best data engineer I&amp;rsquo;d ever worked with. I&amp;rsquo;m less certain now.&lt;/p>
&lt;p>Sarah is what I would call a &amp;ldquo;duct tape programmer.&amp;rdquo; Someone who ships working solutions while others are still debating architectures. In the moment, there&amp;rsquo;s no one you&amp;rsquo;d rather have on your team.&lt;/p>
&lt;p>But moments pass. And the code remains.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="three-types-of-data-engineer">Three Types of Data Engineer&lt;/h3>
&lt;hr>
&lt;p>I&amp;rsquo;ve come to believe there are three distinct approaches to data engineering, and understanding the differences matters more than most of us realise.&lt;/p>
&lt;p>&lt;strong>The Architecture Astronaut&lt;/strong> builds for a future that never arrives. They spend months perfecting data contracts and tenant-level data mesh architecture. They have governance frameworks, schema registries, and architectural purity that would make Zhamak Dehghani proud. What they don&amp;rsquo;t have is a single stakeholder whose problem they&amp;rsquo;ve solved.&lt;/p>
&lt;p>&lt;strong>The Duct Tape Engineer&lt;/strong> ships at all costs. They&amp;rsquo;re Sarah at 4 PM on Friday—pulling data from production, hacking together Python scripts, dumping results into Excel. They solve problems. They move fast. They leave a trail of undocumented queries and mystery scripts in their wake.&lt;/p>
&lt;p>&lt;strong>The Foundational Pragmatist&lt;/strong> does something different. They invest in the foundations that make future speed possible—proper dimensional models, conformed dimensions, clean grain definitions—and then they duct tape on top of that prepared surface. They ship fast &lt;em>because&lt;/em> they built well, not &lt;em>instead of&lt;/em> building well.&lt;/p>
&lt;p>The industry talks endlessly about the first two. The conferences are full of astronauts evangelising the latest architecture. The war stories celebrate the duct tape heroes who saved the day. But the engineers who actually deliver sustained value? They&amp;rsquo;re usually the third type, and they&amp;rsquo;re rarely on stage.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-architecture-astronaut-visits-your-desk">The Architecture Astronaut Visits Your Desk&lt;/h3>
&lt;hr>
&lt;p>You know this person. They show up at your desk, coffee in hand, ready to explain why your simple ETL pipeline is fundamentally wrong.&lt;/p>
&lt;p>&amp;ldquo;What you really need,&amp;rdquo; they say, eyes lighting up, &amp;ldquo;is a proper lakehouse architecture. Bronze, silver, gold layers. Delta Lake for ACID compliance. Apache Kafka for real-time ingestion. And have you considered data mesh? Each domain team should own their own data products with standardized contracts and federated governance.&amp;rdquo;&lt;/p>
&lt;p>You nod politely, but inside you&amp;rsquo;re doing the math. Your company has three data sources. Your biggest table has 50 million rows. Your most demanding stakeholder needs a weekly report. And this person wants you to implement an architecture designed for companies processing petabytes per day.&lt;/p>
&lt;p>They&amp;rsquo;re the over-engineering colleague who &lt;em>&amp;ldquo;comes up to your desk, coffee mug in hand, and starts rattling on about how if you implement event-driven architecture with a proper schema registry, your pipeline will be 34% more scalable.&amp;rdquo;&lt;/em> Scalable for what? You&amp;rsquo;re processing 100MB of data. On a good day.&lt;/p>
&lt;p>The architecture astronaut isn&amp;rsquo;t stupid. They&amp;rsquo;re often brilliant. They&amp;rsquo;ve read every blog post, attended every conference, mastered every tool. They can diagram a system that would make Netflix jealous.&lt;/p>
&lt;p>But they have a disease: they&amp;rsquo;re allergic to shipping.&lt;/p>
&lt;p>They&amp;rsquo;d rather spend six months perfecting a medallion architecture than six hours writing the SQL query that answers the CEO&amp;rsquo;s question. They optimise for theoretical elegance at the expense of actual impact. And they leave a trail of half-finished platforms in their wake.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="a-tale-of-two-teams">A Tale of Two Teams&lt;/h3>
&lt;hr>
&lt;p>Let me tell you about two data teams I&amp;rsquo;ve observed over the years.&lt;/p>
&lt;p>The first was building the future. Data contracts. Federated governance. Tenant-level data mesh with domain ownership and self-serve capabilities. They had architecture review boards, schema evolution policies, and compatibility matrices. Every decision went through rigorous evaluation. Every interface was documented exhaustively.&lt;/p>
&lt;p>Three years in, they were still perfecting their approach. The platform team had become a bureaucracy that produced governance documents, not insights. Architecture astronauts, every one of them.&lt;/p>
&lt;p>The second team took a different path. They weren&amp;rsquo;t duct tape engineers—they didn&amp;rsquo;t hack things together and hope for the best. Instead, they invested in a proper dimensional model. Conformed dimensions. Clean fact tables. Well-defined grain. Nothing fancy. No distributed systems. No streaming. Just solid data modeling fundamentals executed well.&lt;/p>
&lt;p>When the general manager asked a question, they answered it in minutes. Not because they had magical technology, but because the foundation was sound. The conformed customer dimension meant they didn&amp;rsquo;t have to reconcile six different definitions of &amp;ldquo;active customer.&amp;rdquo; The properly-grained fact tables meant they could slice and dice without fear of double-counting. The slowly changing dimensions meant they could answer &amp;ldquo;what did this look like last quarter?&amp;rdquo; without archaeological expeditions through backup tapes.&lt;/p>
&lt;p>One team had years of architectural innovation with nothing to show for it. The other had basic dimensional modeling and could answer almost anything.&lt;/p>
&lt;p>The difference wasn&amp;rsquo;t intelligence or effort. It was understanding which investments actually compound. The second team wasn&amp;rsquo;t avoiding architecture—they were choosing the &lt;em>right&lt;/em> architecture. The kind that creates a foundation for speed rather than a monument to complexity.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-85-failure-rate">The 85% Failure Rate&lt;/h3>
&lt;hr>
&lt;p>But wait, you might say. Surely all these complex architectures exist for a reason. Surely the industry&amp;rsquo;s best minds have converged on data mesh and lakehouses because they work.&lt;/p>
&lt;p>In 2015, Gartner estimated that most big data projects would fail. By 2017, analyst Nick Heudecker admitted they&amp;rsquo;d been &amp;ldquo;too conservative.&amp;rdquo; The actual failure rate was higher—in the 80% range. Most data migrations fail or exceed their budget and scope.&lt;/p>
&lt;p>Eighty percent. In what other engineering discipline would we accept failure rates like that? In what other field would we keep advocating for the same approaches that fail four out of five times?&lt;/p>
&lt;p>The UK&amp;rsquo;s National Health Service attempted to centralise patient records in one of the largest data projects ever undertaken. They flushed $15 billion down the drain. Fifteen billion dollars. And they got nothing.&lt;/p>
&lt;p>Lyft spent years migrating to Hive. By the time they finished in 2020, the entire industry had moved on. They ended up stuck on an outdated system, having invested countless engineering hours chasing an architecture that was already obsolete.&lt;/p>
&lt;p>These aren&amp;rsquo;t exceptions. They&amp;rsquo;re the pattern. Complex architectures fail. Simple foundations, built well, endure.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-philosophy-of-worse">The Philosophy of Worse&lt;/h3>
&lt;hr>
&lt;p>This tension isn&amp;rsquo;t new. Richard Gabriel identified it in 1989 with his famous essay &amp;ldquo;Worse is Better.&amp;rdquo;&lt;/p>
&lt;p>The core idea is counterintuitive: &lt;em>&amp;ldquo;Implementation simplicity has highest priority.&amp;rdquo;&lt;/em> Not correctness. Not completeness. Not elegance. Simplicity.&lt;/p>
&lt;p>Gabriel argued that &lt;em>&amp;ldquo;it is better to get half of the right thing available so that it spreads like a virus. Once people are hooked on it, take the time to improve it to 90% of the right thing.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>The four principles he outlined still apply:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Simplicity&lt;/strong>: The system should be simple above all else.&lt;/li>
&lt;li>&lt;strong>Correctness&lt;/strong>: It&amp;rsquo;s slightly better to be simple than correct.&lt;/li>
&lt;li>&lt;strong>Consistency&lt;/strong>: Can be sacrificed for simplicity.&lt;/li>
&lt;li>&lt;strong>Completeness&lt;/strong>: Can be sacrificed in favour of any other quality.&lt;/li>
&lt;/ul>
&lt;p>This feels wrong to engineers. We&amp;rsquo;re trained to build things correctly. We&amp;rsquo;re judged on technical sophistication. Our peers admire elegant architectures and comprehensive solutions.&lt;/p>
&lt;p>But Gabriel was describing how Unix beat more sophisticated operating systems. How C beat more powerful languages. How TCP/IP beat more theoretically correct protocols. The simpler, &amp;ldquo;worse&amp;rdquo; solutions won because they shipped, spread, and evolved.&lt;/p>
&lt;p>John Gall formalised the pattern in 1975: &lt;em>&amp;ldquo;A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>Don&amp;rsquo;t architect a lakehouse from scratch. Start with a simple pipeline that works. Evolve it as you understand your actual requirements. That&amp;rsquo;s not compromise—that&amp;rsquo;s how successful systems are built.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-foundation-that-enables-speed">The Foundation That Enables Speed&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s where the duct tape philosophy goes wrong: it assumes all architecture is overhead.&lt;/p>
&lt;p>I believe strongly in dimensional modeling. Not because it&amp;rsquo;s elegant—though it is. Not because it&amp;rsquo;s theoretically pure—though it can be. But because when a proper dimensional model exists, I can answer the general manager&amp;rsquo;s Friday afternoon question in fifteen minutes instead of four hours.&lt;/p>
&lt;p>The conformed dimension that took a week to build properly? It&amp;rsquo;s saved me hundreds of hours of query writing since. The fact table with clean grain definitions? It means I&amp;rsquo;m not debugging data quality issues at 11 PM.&lt;/p>
&lt;p>This is what separates the foundational pragmatist from the pure duct tape engineer. Sarah could answer Friday&amp;rsquo;s question. But she&amp;rsquo;d have to re-derive the customer definition from scratch every time. She&amp;rsquo;d spend hours reconciling data that a conformed dimension would have reconciled once. She could produce 3 reports but they might differ in definitions of what a customer is.&lt;/p>
&lt;p>The architecture astronaut spends six months building infrastructure nobody uses. The duct tape engineer hacks together solutions that become unmaintainable. The foundational pragmatist invests two weeks in dimensional modeling and creates the conditions for rapid, reliable delivery forever after.&lt;/p>
&lt;p>The question isn&amp;rsquo;t whether to invest in architecture. It&amp;rsquo;s &lt;em>which&lt;/em> architecture and &lt;em>when&lt;/em>. A solid dimensional model? Almost always worth it. A lakehouse for your 50GB dataset? Probably not. Data contracts and federated governance for a team of three? You&amp;rsquo;re solving problems you don&amp;rsquo;t have.&lt;/p>
&lt;p>That team I mentioned—the one answering questions in minutes—they weren&amp;rsquo;t magicians. They&amp;rsquo;d done the foundational work. They had clean dimensions. They had documented grain. They had conformed definitions.&lt;/p>
&lt;p>Their duct tape stuck better because they&amp;rsquo;d prepared the surface.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-maturity-gradient">The Maturity Gradient&lt;/h3>
&lt;hr>
&lt;p>There&amp;rsquo;s a progression that the conference talks never mention:&lt;/p>
&lt;p>&lt;strong>Monthly reports → Weekly reports → Daily pipelines → Micro-batches → Streaming&lt;/strong>&lt;/p>
&lt;p>Each step leftward costs exponentially more in complexity. Each step rightward gives you exponentially more time to get the quality right.&lt;/p>
&lt;p>When I build something new, I start as far right as the business can tolerate. Monthly reporting sounds unsexy, but it gives you thirty days to catch quality issues before anyone sees bad data. You have time to validate, to cross-check, to build confidence.&lt;/p>
&lt;p>Once monthly is bulletproof, moving to weekly is straightforward. You&amp;rsquo;ve already solved the hard problems—the business logic, the edge cases, the data quality rules. Now you&amp;rsquo;re just increasing frequency. Daily follows. Micro-batch follows.&lt;/p>
&lt;p>Can you skip straight to real-time? Sure. I&amp;rsquo;ve seen it done. I&amp;rsquo;ve also seen the pain involved—debugging race conditions, explaining to stakeholders why the &amp;ldquo;real-time&amp;rdquo; dashboard showed impossible numbers, rebuilding pipelines from scratch because the streaming-first design couldn&amp;rsquo;t handle a schema change.&lt;/p>
&lt;p>The streaming-first approach means you&amp;rsquo;re solving data quality, business logic, edge cases, AND distributed systems problems simultaneously. That&amp;rsquo;s a recipe for the 85% failure rate we talked about.&lt;/p>
&lt;p>Ship the monthly report. Prove the logic. Harden the quality. Then earn your way to daily.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="duct-tape-with-a-sunset-date">Duct Tape With a Sunset Date&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s what separates strategic pragmatism from technical negligence: the hardening plan.&lt;/p>
&lt;p>Every quick solution I ship comes with an implicit contract. Yes, I&amp;rsquo;ll wrangle this data together to plug the compliance gap by Friday. Yes, I&amp;rsquo;ll build that dashboard for the legal team using queries that would make a data modeler weep. But written somewhere—in Jira, in a Confluence page, in my own notes—is the plan to do it properly.&lt;/p>
&lt;p>The CFO&amp;rsquo;s urgent analysis gets a spreadsheet today. It gets a proper pipeline next quarter.&lt;/p>
&lt;p>This isn&amp;rsquo;t compromise—it&amp;rsquo;s sequencing. The business gets value immediately. The team gets breathing room. And the quick solution becomes a specification for the permanent one. You&amp;rsquo;ve proven the logic works. You&amp;rsquo;ve validated the business need. Now you can invest in building it right, with far less risk than starting from scratch.&lt;/p>
&lt;p>The failure mode isn&amp;rsquo;t duct tape itself. It&amp;rsquo;s duct tape without a sunset date. It&amp;rsquo;s the &amp;ldquo;temporary&amp;rdquo; query that&amp;rsquo;s still running five years later because nobody remembered to harden it. It&amp;rsquo;s technical debt accumulated without a repayment plan.&lt;/p>
&lt;p>I&amp;rsquo;ve used this approach to protect my teams from massive pieces of work that would have consumed them for months. The compliance hole that needed plugging immediately? Duct tape with a ticket. The legal team&amp;rsquo;s urgent risk analysis? Duct tape with a documented plan. The high-value project that couldn&amp;rsquo;t wait for proper architecture? Duct tape with a sunset date.&lt;/p>
&lt;p>The duct tape isn&amp;rsquo;t the problem. Forgetting to remove it is.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="sarahs-legacy">Sarah&amp;rsquo;s Legacy&lt;/h3>
&lt;hr>
&lt;p>Every organisation has Bob&amp;rsquo;s query.&lt;/p>
&lt;p>You know the one. Bob wrote it three years ago. Bob left eighteen months ago. The query is still running every Tuesday morning, feeding a report that twelve people apparently depend on, documented nowhere, understood by no one.&lt;/p>
&lt;p>Here&amp;rsquo;s the uncomfortable truth: Bob&amp;rsquo;s query is what Sarah&amp;rsquo;s Friday afternoon heroics look like three years later.&lt;/p>
&lt;p>Sarah shipped. She solved the problem. The CEO got their answer. But she didn&amp;rsquo;t write the ticket. She didn&amp;rsquo;t document the logic. She didn&amp;rsquo;t create a sunset date. She moved on to the next fire, and the quick fix quietly became load-bearing infrastructure.&lt;/p>
&lt;p>The person who inherited Sarah&amp;rsquo;s work doesn&amp;rsquo;t think she&amp;rsquo;s a hero. They think she&amp;rsquo;s the reason they&amp;rsquo;re debugging mystery scripts at 11 PM instead of building something new.&lt;/p>
&lt;p>This is what duct tape looks like when the sunset date gets forgotten. It&amp;rsquo;s technical debt, pure and simple. A liability waiting to break at the worst possible moment.&lt;/p>
&lt;p>But here&amp;rsquo;s the reframe: Sarah&amp;rsquo;s legacy is also an opportunity.&lt;/p>
&lt;p>That query is a perfect project for a junior engineer. The risk is low—it&amp;rsquo;s already working, so they have a baseline to compare against. The scope is contained—one query, one output, one business purpose. They can pull it apart, understand the logic, document what they find, and rebuild it as a proper pipeline.&lt;/p>
&lt;p>They learn data modeling. They learn testing. They learn documentation. They learn how to interview stakeholders about requirements that were never written down. They harden their skills in a safe environment while simultaneously paying down technical debt. This is what builds up Junior to Senior levels.&lt;/p>
&lt;p>Every piece of Sarah&amp;rsquo;s legacy is both a problem to solve and a training ground to offer. The mature data engineer sees both. They create the ticket, assign the junior, and turn a liability into a development opportunity.&lt;/p>
&lt;p>The debt gets paid. The junior grows. The organisation gets a documented, tested, maintainable pipeline instead of a mystery script.&lt;/p>
&lt;p>That&amp;rsquo;s pragmatism with a long-term view.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-pragmatists-manifesto">The Pragmatist&amp;rsquo;s Manifesto&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s what the foundational pragmatist understands:&lt;/p>
&lt;p>&lt;strong>Shipping is a feature.&lt;/strong> The most important feature. Your data pipeline doesn&amp;rsquo;t exist until it&amp;rsquo;s serving production queries. Your data model doesn&amp;rsquo;t matter until it&amp;rsquo;s powering real decisions. &lt;em>&amp;ldquo;Your product must have it.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>&lt;strong>The pipeline that ships insights beats the pipeline that ships nothing.&lt;/strong> A &amp;ldquo;50%-good solution that people actually have solves more problems and survives longer than a 99% solution that nobody has because it&amp;rsquo;s in your lab where you&amp;rsquo;re endlessly polishing the damn thing.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>But foundations compound.&lt;/strong> A solid dimensional model isn&amp;rsquo;t architecture astronaut behaviour—it&amp;rsquo;s the investment that makes all future delivery faster and more reliable. Build the foundation once, benefit forever.&lt;/p>
&lt;p>&lt;strong>Directionally correct beats precisely wrong.&lt;/strong> The dashboard that&amp;rsquo;s 80% accurate and delivered today creates more value than the perfectly governed data product delivered never.&lt;/p>
&lt;p>&lt;strong>Start with CSV, graduate to Parquet, earn your way to streaming.&lt;/strong> You don&amp;rsquo;t need real-time until you do. You don&amp;rsquo;t need a lakehouse until you do. You don&amp;rsquo;t need Kafka until you do. And you probably don&amp;rsquo;t.&lt;/p>
&lt;p>&lt;strong>Every shortcut needs a sunset.&lt;/strong> Ship the hack. Document the plan. Pay down the debt. Quick fixes without a hardening plan aren&amp;rsquo;t pragmatism—they&amp;rsquo;re negligence with extra steps.&lt;/p>
&lt;p>Jamie Zawinski said it best: &lt;em>&amp;ldquo;At the end of the day, ship the fucking thing! It&amp;rsquo;s great to rewrite your code and make it cleaner and by the third time it&amp;rsquo;ll actually be pretty. But that&amp;rsquo;s not the point—you&amp;rsquo;re not here to write code; you&amp;rsquo;re here to ship products.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>For data engineers, the adaptation is obvious: You&amp;rsquo;re not here to build architectures. You&amp;rsquo;re here to ship insights. But the insights you ship today shouldn&amp;rsquo;t sabotage the insights you need to ship tomorrow.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-caveat">The Caveat&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s where I need to be careful.&lt;/p>
&lt;p>Pragmatic shortcuts aren&amp;rsquo;t an excuse for incompetence. &lt;em>&amp;ldquo;Duct tape programmers must be talented enough to pull it off.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>Sarah wasn&amp;rsquo;t shipping ugly code because she didn&amp;rsquo;t know better. She was making conscious trade-offs. She understood the implications of pulling from production databases. She knew how to write cleaner code—she just knew when not to bother.&lt;/p>
&lt;p>Her failure wasn&amp;rsquo;t the duct tape. It was stopping there.&lt;/p>
&lt;p>There&amp;rsquo;s a difference between strategic simplicity and reckless shortcuts. The foundational pragmatist understands the trade-offs. They know what they&amp;rsquo;re sacrificing and why it&amp;rsquo;s worth it. They can explain their decisions and defend them. And they write the ticket for the hardening work.&lt;/p>
&lt;p>Netflix actually needs Kafka. Meta actually needs petabyte-scale systems. Google actually needs distributed query engines.&lt;/p>
&lt;p>The question is whether &lt;em>your&lt;/em> company does.&lt;/p>
&lt;p>Most don&amp;rsquo;t. The data supports this overwhelmingly. But knowing when you&amp;rsquo;re the exception requires understanding the fundamentals. You can&amp;rsquo;t break the rules effectively until you know what the rules are.&lt;/p>
&lt;p>So yes, learn the proper patterns. Understand data modeling. Know why slowly changing dimensions exist. Study the medallion architecture. Read about data mesh.&lt;/p>
&lt;p>Then make conscious decisions about when to use them. Not because a blog post said so. Not because a vendor promised ROI. Not because that&amp;rsquo;s what the architecture astronaut wants.&lt;/p>
&lt;p>Because they solve a problem you actually have.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-engineer-who-holds-all-three">The Engineer Who Holds All Three&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s what I&amp;rsquo;ve learned after years of watching these patterns play out: the best data engineers understand all three modes and know when each applies.&lt;/p>
&lt;p>I read every blog post about data mesh. I attend the conferences. I evaluate new tools. I think constantly about how we might evolve our architecture. That&amp;rsquo;s not being an astronaut—that&amp;rsquo;s staying informed.&lt;/p>
&lt;p>When my team faces a Friday afternoon crisis, I can be Sarah. I can hack together the solution, ship the answer, buy us breathing room. That&amp;rsquo;s not recklessness—that&amp;rsquo;s responsiveness.&lt;/p>
&lt;p>But unlike Sarah, I write the ticket. I document the sunset date. And when we have the space, I advocate for the dimensional modeling that will make the next crisis easier to handle.&lt;/p>
&lt;p>The architecture astronaut&amp;rsquo;s failure is pursuing elegance without urgency. The duct tape engineer&amp;rsquo;s failure is urgency without investment in foundations. The pure pragmatist ships fast on solid ground and pays down their debts.&lt;/p>
&lt;p>I&amp;rsquo;ve watched teams spend years on data mesh with nothing to show for it. I&amp;rsquo;ve inherited Sarah&amp;rsquo;s legacy code and cursed her name at midnight. I&amp;rsquo;ve also seen teams with solid data models answer any question in minutes, year after year.&lt;/p>
&lt;p>The difference wasn&amp;rsquo;t the sophistication of their approach—it was whether their investments actually compounded into capability, and whether their shortcuts came with expiration dates.&lt;/p>
&lt;p>So yes, ship the dashboard. Answer the executive&amp;rsquo;s question. Plug the compliance hole today.&lt;/p>
&lt;p>But write down the hardening plan. Build the dimensional model when you can. Start with monthly, earn your way to daily. Invest in the foundations that make future speed possible.&lt;/p>
&lt;p>And when the junior engineer asks why Sarah&amp;rsquo;s query is still running after five years, hand them the ticket to fix it.&lt;/p>
&lt;p>That&amp;rsquo;s the job.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Career Development</category><category>Leadership</category><category>Data Engineering</category><category>DuckDB</category><category>Architecture</category><category>Pragmatism</category><category>Career Development</category><category>Technical Strategy</category><category>Data Platforms</category><category>Kimball</category><category>Data Modeling</category></item><item><title>That Tuesday Morning When I Finally Fixed Our Ten-Minute Queries</title><link>https://ghostinthedata.info/posts/2026/2026-01-16-aws-dimensional-modelling/</link><pubDate>Fri, 16 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-16-aws-dimensional-modelling/</guid><author>Chris Hillman</author><description>How Kimball dimensional modeling, proper job orchestration, and Write-Audit-Publish patterns transformed our data pipeline from a nightmare into something that actually works</description><content:encoded>&lt;h3 id="the-ten-minute-query">The Ten-Minute Query&lt;/h3>
&lt;hr>
&lt;p>I&amp;rsquo;m sitting at my laptop on a Tuesday morning, waiting. The progress bar on my screen says &amp;lsquo;Query running&amp;hellip; 4 minutes, 37 seconds.&amp;rsquo; I lean back in my chair and let out this long sigh that probably says more than I intended.&lt;/p>
&lt;p>My manager walks past my desk. She glances at my screen, and I can see that look—the one that says she already knows what I&amp;rsquo;m about to tell her. I didn&amp;rsquo;t need to explain.&lt;/p>
&lt;p>&amp;ldquo;Still pulling that customer report?&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Yeah.&amp;rdquo;&lt;/p>
&lt;p>She shakes her head. &amp;ldquo;We need to fix this.&amp;rdquo;&lt;/p>
&lt;p>It was an ad-hoc piece of work that we&amp;rsquo;d been running for months. But I just nod.&lt;/p>
&lt;p>Here&amp;rsquo;s what was happening: every time someone needed to know &amp;lsquo;how many orders did customer X place?&amp;rsquo; or &amp;lsquo;which products sold best last month?&amp;rsquo;, we&amp;rsquo;d query this massive table. Three million rows. Every single customer&amp;rsquo;s name, address, email—repeated on every order row. The system was scanning through all that redundant data every single time.&lt;/p>
&lt;p>And then, two weeks ago, it got worse. Someone ran a data import script that had a bug. It replaced half our customer names with NULL. By the time we caught it, twelve reports had already gone out with blank customer names. I had to send apology emails. My team and I had to stay until midnight fixing it.&lt;/p>
&lt;p>I thought, &lt;em>there has to be a better way to do this&lt;/em>. People have been building data systems for decades—someone must have figured this out.&lt;/p>
&lt;p>And they have. His name is Ralph Kimball, and his dimensional modeling methodology has been the gold standard for data warehouses since the 1990s.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-problem-i-couldnt-unsee">The Problem I Couldn&amp;rsquo;t Unsee&lt;/h3>
&lt;hr>
&lt;p>Let me show you what our data looked like.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>order_id | date | customer_name | address | email | product | amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------|------------|---------------|--------------|--------------|---------|-------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 | 2024-11-15 | John Smith | 123 Main St | john@... | Pizza | 25
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 | 2024-11-15 | John Smith | 123 Main St | john@... | Soda | 3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3 | 2024-11-15 | Jane Doe | 456 Oak Ave | jane@... | Pizza | 25
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>4 | 2024-11-16 | John Smith | 123 Main St | john@... | Burger | 12
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>See what&amp;rsquo;s happening? John Smith&amp;rsquo;s address and email are stored three times. Multiply that by thousands of customers and millions of orders, and you&amp;rsquo;ve got:&lt;/p>
&lt;ul>
&lt;li>Every query scanning way more data than necessary&lt;/li>
&lt;li>Storage costs that make your CFO unhappy&lt;/li>
&lt;li>One bug corrupting your entire history&lt;/li>
&lt;li>Query times that make you question your career choices&lt;/li>
&lt;/ul>
&lt;p>Every single query was like asking someone to find your keys by searching every room in your house, including the ones you never use. Sure, they&amp;rsquo;ll find the keys eventually. But there&amp;rsquo;s a better way.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-kitchen-that-changed-everything">The Kitchen That Changed Everything&lt;/h3>
&lt;hr>
&lt;p>Let me paint you a hypothetical scenario. Imagine it&amp;rsquo;s a Saturday, and you&amp;rsquo;re standing in your kitchen after just moving house. You can&amp;rsquo;t find anything. Everything&amp;rsquo;s still in boxes.&lt;/p>
&lt;p>You&amp;rsquo;re rummaging through the third box, looking for a spatula. You&amp;rsquo;re pulling out random stuff—a whisk, some Tupperware lids, a pizza cutter, yesterday&amp;rsquo;s takeout containers, a bag of flour. Everything mixed together. This is chaos.&lt;/p>
&lt;p>Now imagine a friend comes over. They take one look at your kitchen and say, &amp;ldquo;What is this?&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;It&amp;rsquo;s&amp;hellip; I haven&amp;rsquo;t unpacked yet.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;You&amp;rsquo;ve been here a week.&amp;rdquo; They walk over to the mess. &amp;ldquo;Here&amp;rsquo;s what you should do. The stuff that rarely changes—your plates, your pots, your cutting boards—those go in the cabinets. Organised, labelled, easy to find. The stuff that comes and goes constantly—the groceries, the leftovers, what you&amp;rsquo;re cooking today—that goes in the fridge and on the counter. Don&amp;rsquo;t mix the permanent stuff with the temporary stuff.&amp;rdquo;&lt;/p>
&lt;p>Two hours later, the kitchen makes sense. You can find anything in seconds. And here&amp;rsquo;s the wild part—it actually takes up less space because you&amp;rsquo;re not duplicating storage for the same items.&lt;/p>
&lt;p>That&amp;rsquo;s dimensional modeling.&lt;/p>
&lt;p>When your data is like those boxes, everything&amp;rsquo;s mixed together. Customer information sitting right next to order information, repeated over and over. Every time you need something, you&amp;rsquo;re digging through all the boxes.&lt;/p>
&lt;p>Dimensional modeling says: let&amp;rsquo;s organise this. Put stable descriptive data—things that rarely change—in one place. We call these &lt;strong>dimension tables&lt;/strong>. Put the events that happen constantly—the transactions, the updates, the measurements—in another place. We call these &lt;strong>fact tables&lt;/strong>. Link them with keys.&lt;/p>
&lt;p>Here&amp;rsquo;s what that transformation looks like:&lt;/p>
&lt;p>BEFORE (Messy Boxes - everything mixed together):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>order_id | customer_name | address | product | amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 | John Smith | 123 Main St | Pizza | 25
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 | John Smith | 123 Main St | Soda | 3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>↑ John&amp;rsquo;s details repeated every single order!&lt;/p>
&lt;p>AFTER (Organised Kitchen):
DIM_CUSTOMER (Cabinets - stable stuff, stored ONCE):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>customer_key | customer_id | name | address | email
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1001 | C-123 | John Smith | 123 Main St | john@...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1002 | C-456 | Jane Doe | 456 Oak Ave | jane@...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>DIM_DATE (Calendar on the wall - one row per day):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>date_key | full_date | day_name | month | quarter | year | is_weekend
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>20241115 | 2024-11-15 | Friday | Nov | Q4 | 2024 | N
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>20241116 | 2024-11-16 | Saturday | Nov | Q4 | 2024 | Y
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>FACT_ORDERS (Fridge/counter - events that happen constantly):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>order_key | date_key | customer_key | product_key | amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 | 20241115 | 1001 | 5 | 25
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 | 20241115 | 1001 | 8 | 3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Customer details stored one time. Not a thousand times. When you need to answer &amp;lsquo;how much did we sell?&amp;rsquo;, you scan the compact fact table. When you need &amp;lsquo;which customers bought?&amp;rsquo;, you JOIN to get the descriptive details.&lt;/p>
&lt;p>Notice something important in that structure? The dimension tables have two types of keys:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>customer_key&lt;/strong> (1001, 1002) — This is a &lt;em>surrogate key&lt;/em>. A meaningless integer we generate ourselves.&lt;/li>
&lt;li>&lt;strong>customer_id&lt;/strong> (C-123, C-456) — This is the &lt;em>natural key&lt;/em>. The ID from the source system.&lt;/li>
&lt;/ul>
&lt;p>Why both? Because source systems are chaotic. They recycle IDs. They change formats. They get merged in acquisitions. The surrogate key is &lt;em>our&lt;/em> key—stable, predictable, and under our control. Every dimension table gets a surrogate key as its primary key.&lt;/p>
&lt;p>And that date dimension? It&amp;rsquo;s &amp;ldquo;the most important dimension.&amp;rdquo; Every fact table should have one. It lets you slice data by day of week, month, quarter, fiscal period, holidays—without calculating these on every query.&lt;/p>
&lt;p>The core principle: things that rarely change go in dedicated storage, organised and easy to find. Things that happen constantly go in their own space, linked back by keys. Same rule applies to data:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Dimensions&lt;/strong> = Stable descriptive stuff (customers, products, locations, dates)&lt;/li>
&lt;li>&lt;strong>Facts&lt;/strong> = Events that happen constantly (orders, clicks, updates, measurements)&lt;/li>
&lt;/ul>
&lt;/br>
&lt;/br>
&lt;h3 id="the-conference-room-ill-never-forget">The Conference Room I&amp;rsquo;ll Never Forget&lt;/h3>
&lt;hr>
&lt;p>I&amp;rsquo;m sitting in a conference room. Fluorescent lights buzzing. That weird hum they make when it&amp;rsquo;s too quiet—I&amp;rsquo;m wondering why we haven&amp;rsquo;t changed to LEDs. My manager is standing at the whiteboard, and she&amp;rsquo;s pissed.&lt;/p>
&lt;p>&amp;ldquo;Someone,&amp;rdquo; she says, not looking at anyone in particular, &amp;ldquo;pushed bad data to production last night.&amp;rdquo;&lt;/p>
&lt;p>I feel my stomach drop. The data pipeline my team had been working on.&lt;/p>
&lt;p>&amp;ldquo;The revenue report that went to the board this morning showed every transaction as zero dollars. Zero. The board emailed me asking if the company made any money last quarter.&amp;rdquo;&lt;/p>
&lt;p>Oh god. Oh no. I sank down in my chair a little.&lt;/p>
&lt;p>Turns out it wasn&amp;rsquo;t actually our release—it was someone else&amp;rsquo;s. But in that moment, sitting there, I realised something crucial: we had no safety net. Data went straight from transformation to production. No checks. No validation. If you made a mistake, everyone saw it immediately.&lt;/p>
&lt;p>That conference room moment stuck with me. I started researching how other teams prevented this kind of disaster. That&amp;rsquo;s when I learned about Write-Audit-Publish. And honestly? It&amp;rsquo;s probably saved me from that same sick feeling countless times since.&lt;/p>
&lt;p>Here&amp;rsquo;s the idea:&lt;/p>
&lt;p>&lt;strong>WRITE&lt;/strong> — You finish your transformation. Instead of putting the data straight into production, you write it to a staging branch. Think of it like a draft folder for emails. It&amp;rsquo;s there, but it&amp;rsquo;s not official yet.&lt;/p>
&lt;p>&lt;strong>AUDIT&lt;/strong> — This is the crucial part. You run every check you can think of:&lt;/p>
&lt;ul>
&lt;li>Did I lose any rows?&lt;/li>
&lt;li>Are there any NULL values where there shouldn&amp;rsquo;t be?&lt;/li>
&lt;li>Do all the foreign keys match up?&lt;/li>
&lt;li>Does the math make sense?&lt;/li>
&lt;li>Do surrogate keys have duplicates?&lt;/li>
&lt;/ul>
&lt;p>It&amp;rsquo;s like re-reading an important email before you hit send. You check for typos, you verify the recipients, you make sure the attachment is right.&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> In the future when you find incidents, bugs, or problems—add them to this AUDIT layer. Ensure you don&amp;rsquo;t make the same mistakes twice.&lt;/p>
&lt;p>&lt;strong>PUBLISH&lt;/strong> — Only if everything passes, you merge the branch to production. If anything fails, you stop. You fix it. You try again. The bad data never reaches production. Never reaches that board meeting.&lt;/p>
&lt;p>The traditional way to implement this involves copying data between folders or tables. But there&amp;rsquo;s a better approach using Apache Iceberg&amp;rsquo;s branching feature. Instead of physically copying data, you create a branch—like in Git. Your audit runs against the branch. If it passes, you merge the branch to main. If it fails, you drop the branch. No data was ever at risk.&lt;/p>
&lt;p>&lt;strong>Critical design decision:&lt;/strong> WAP should happen at &lt;em>each table level&lt;/em>, not just at the end of the pipeline. Each dimension job does its own Write-Audit-Publish cycle. Each fact job does its own. This way:&lt;/p>
&lt;ul>
&lt;li>Failures are isolated to the specific table that failed&lt;/li>
&lt;li>You catch issues early, not at the very end&lt;/li>
&lt;li>Each job is self-contained and independently testable&lt;/li>
&lt;li>Downstream jobs read from published (main) tables, not staging branches&lt;/li>
&lt;/ul>
&lt;p>Let me show you what this looks like when an audit catches something:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>AUDIT PHASE (dim_system):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Row count check.......................... PASS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Source: 1,198 rows
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Target: 1,198 rows
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Primary key uniqueness................... FAIL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Found 13 duplicate system_keys in dim_system!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>AUDIT FAILED — BRANCH NOT MERGED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Data remains in staging branch.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Production table unchanged.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Fix the duplicates and re-run the job.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Look at that. The audit caught duplicate surrogate keys before they reached production. Nobody sees this except you. You fix the problem. You run it again. Then it passes, and the branch merges to main.&lt;/p>
&lt;p>This is Write-Audit-Publish. It&amp;rsquo;s the difference between sending an email with a typo versus catching it first. Between serving undercooked food versus checking with a thermometer. Between publishing bad data versus validating before it matters.&lt;/p>
&lt;p>That conference room moment? Doesn&amp;rsquo;t happen with this pattern.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="building-this-thing-for-real">Building This Thing for Real&lt;/h3>
&lt;hr>
&lt;p>Alright, enough theory. Let me show you how to actually build this in AWS.&lt;/p>
&lt;p>We&amp;rsquo;re going to use real (game) space exploration data—systems, planets, updates. It&amp;rsquo;s way more interesting than order data, and the patterns work exactly the same.&lt;/p>
&lt;p>&lt;strong>In this section:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="#architecture-overview">Architecture Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-1-setting-up-s3-buckets">Step 1: Setting Up S3 Buckets&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-2-creating-the-iceberg-tables">Step 2: Creating the Iceberg Tables&lt;/a>&lt;/li>
&lt;li>&lt;a href="#the-heart-of-the-system-orchestrated-glue-jobs-with-per-table-wap">The Glue Jobs (with Per-Table WAP)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-functions-workflow">Step Functions Workflow&lt;/a>&lt;/li>
&lt;li>&lt;a href="#dependency-triggers-with-s3-events">Dependency Triggers with S3 Events&lt;/a>&lt;/li>
&lt;/ul>
&lt;h4 id="architecture-overview">Architecture Overview&lt;/h4>
&lt;p>Here&amp;rsquo;s our architecture:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span> +------------------------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | AWS Step Functions Workflow |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +------------------------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +----------------------------+----------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | | |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> v v v
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +------------+ +------------+ +------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | Glue Job: | | Glue Job: | | Glue Job: |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | dim_date | | dim_system | | dim_planet |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | (WAP) | | (WAP) | | (WAP) |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +------------+ +------------+ +------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | | |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +----------------------------+----------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Wait for all dimensions)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> v
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | Glue Job: |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | fact_system_updates |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | (WAP + Incremental) |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +------------------------+
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Data Flow:&lt;/strong>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Landing Layer&lt;/th>
 &lt;th>Staging Layer (PSA)&lt;/th>
 &lt;th>Semantic Layer&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Raw 1:1 from source&lt;/td>
 &lt;td>Iceberg + data_date partitioning&lt;/td>
 &lt;td>Production dimensional model&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>systems/&lt;/code>&lt;/td>
 &lt;td>&lt;code>stg_systems/&lt;/code>&lt;/td>
 &lt;td>&lt;code>dim_date&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>planets/&lt;/code>&lt;/td>
 &lt;td>&lt;code>stg_planets/&lt;/code>&lt;/td>
 &lt;td>&lt;code>dim_system&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>updates/&lt;/code>&lt;/td>
 &lt;td>&lt;code>stg_updates/&lt;/code>&lt;/td>
 &lt;td>&lt;code>dim_planet&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;/td>
 &lt;td>&lt;/td>
 &lt;td>&lt;code>fact_system_updates&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Let me explain the three layers:&lt;/p>
&lt;p>&lt;strong>Landing Layer&lt;/strong> — Raw data, 1:1 from source systems. No transformations except what&amp;rsquo;s needed to land it. Contains only the &lt;em>latest&lt;/em> fetch. If sourcing fails, this layer fails. Get the data close to you as quickly as possible (into your ecosystem). Typically all the files will be string or varchar to mitigate data type issues from a source system.&lt;/p>
&lt;p>&lt;strong>Staging Layer (Persistent Staging Area)&lt;/strong> — This is where we convert data types and prepare for dimensional modeling. Critically, we partition by &lt;code>data_date&lt;/code>—the date the source data was fetched. This gives us:&lt;/p>
&lt;ul>
&lt;li>Historical reprocessing capability (reload any past date)&lt;/li>
&lt;li>Debugging (what did the data look like on January 15th?)&lt;/li>
&lt;li>Dependency triggers (when a partition is written, trigger downstream jobs)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Semantic Layer&lt;/strong> — Production-ready dimensional model. This is where business users query. Tables are partitioned appropriately for query performance.&lt;/p>
&lt;p>Also notice the job structure: &lt;strong>each job does its own WAP cycle&lt;/strong>. Dimensions build in parallel, each publishing to main when their audits pass. Fact tables wait for all dimensions to publish, then do their own WAP cycle. This ensures:&lt;/p>
&lt;ul>
&lt;li>Fact tables read from &lt;em>published&lt;/em> dimensions (main branch), not staging branches&lt;/li>
&lt;li>Foreign key lookups use production-ready surrogate keys&lt;/li>
&lt;li>Each job is independently testable&lt;/li>
&lt;/ul>
&lt;h4 id="step-1-setting-up-s3-buckets">Step 1: Setting Up S3 Buckets&lt;/h4>
&lt;p>I&amp;rsquo;m in the S3 console. Click &amp;ldquo;Create bucket&amp;rdquo;. I&amp;rsquo;m calling mine &lt;code>space-data-pipeline-demo&lt;/code>.&lt;/p>
&lt;p>Important settings:&lt;/p>
&lt;ul>
&lt;li>Region: ap-southeast-2 (Sydney—pick one close to you)&lt;/li>
&lt;li>Block all public access: Yes&lt;/li>
&lt;li>Versioning: Enabled&lt;/li>
&lt;li>Default encryption: Enabled&lt;/li>
&lt;/ul>
&lt;p>Inside this bucket:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>space-data-pipeline-demo/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── landing/ ← Raw 1:1 from source (latest only)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── systems/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── planets/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ └── updates/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── staging/ ← PSA with data_date partitioning
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── stg_systems/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── stg_planets/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ └── stg_updates/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── semantic/ ← Production dimensional model
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── dim_date/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── dim_system/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── dim_planet/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── fact_system_updates/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="step-2-creating-the-iceberg-tables">Step 2: Creating the Iceberg Tables&lt;/h4>
&lt;p>Here&amp;rsquo;s the DDL for our dimensional model. Notice the partitioning strategies and SCD Type 2 columns.&lt;/p>
&lt;details>
&lt;summary>&lt;strong>Click to expand: dim_date DDL&lt;/strong>&lt;/summary>
&lt;p>This is a reference dimension—typically loaded once with all dates, then rarely updated. Partitioned by year for query performance.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.dim_date (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_key INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> full_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> day_of_week STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> day_of_month INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> day_of_year INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> week_of_year INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> month_number INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> month_name STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> quarter INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">year&lt;/span> INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> is_weekend STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> is_holiday STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fiscal_quarter INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fiscal_year INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Audit columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_system STRING
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (&lt;span style="color:#66d9ef">year&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/semantic/dim_date/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;details>
&lt;summary>&lt;strong>Click to expand: dim_system DDL (SCD Type 2)&lt;/strong>&lt;/summary>
&lt;p>Tracks history with effective_date/expiration_date. Partitioned by effective_year for efficient history queries.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.dim_system (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> system_key INT, &lt;span style="color:#75715e">-- Surrogate key (generated)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> system_id STRING, &lt;span style="color:#75715e">-- Natural key (from source)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> system_name STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sector STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main_star_type STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> coordinates_x DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> coordinates_y DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> coordinates_z DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> discovery_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exploration_status STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- SCD Type 2 columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> effective_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expiration_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_flag STRING, &lt;span style="color:#75715e">-- &amp;#39;Y&amp;#39; or &amp;#39;N&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">-- Audit columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_system STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Partition column
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> effective_year INT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (effective_year)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/semantic/dim_system/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;details>
&lt;summary>&lt;strong>Click to expand: dim_planet DDL (SCD Type 2)&lt;/strong>&lt;/summary>
&lt;p>Foreign key to dim_system uses the surrogate key. Partitioned by effective_year.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.dim_planet (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> planet_key INT, &lt;span style="color:#75715e">-- Surrogate key
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> planet_id STRING, &lt;span style="color:#75715e">-- Natural key
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> system_key INT, &lt;span style="color:#75715e">-- FK to dim_system (surrogate!)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> planet_name STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> planet_type STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> orbital_period DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> has_atmosphere STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> habitability_score DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- SCD Type 2 columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> effective_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expiration_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_flag STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Audit columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_system STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- Partition column
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> effective_year INT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (effective_year)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/semantic/dim_planet/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;details>
&lt;summary>&lt;strong>Click to expand: fact_system_updates DDL&lt;/strong>&lt;/summary>
&lt;p>Incremental fact table partitioned by date_year. Loaded with delete-then-insert pattern.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.fact_system_updates (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> update_key BIGINT, &lt;span style="color:#75715e">-- Surrogate key
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> date_key INT, &lt;span style="color:#75715e">-- FK to dim_date
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> system_key INT, &lt;span style="color:#75715e">-- FK to dim_system (current surrogate!)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> update_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>, &lt;span style="color:#75715e">-- Original precision preserved
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> status STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ships_present INT, &lt;span style="color:#75715e">-- Additive fact
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> resource_level DOUBLE, &lt;span style="color:#75715e">-- Additive fact
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">-- Audit columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_system STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data_date DATE, &lt;span style="color:#75715e">-- Source data date (for reprocessing)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">-- Partition column
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> date_year INT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (date_year)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/semantic/fact_system_updates/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;details>
&lt;summary>&lt;strong>Click to expand: Staging Tables DDL (Persistent Staging Area)&lt;/strong>&lt;/summary>
&lt;p>Partitioned by data_date for historical reprocessing.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.stg_systems (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> system_id STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> system_name STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sector STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main_star_type STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> coordinates_x DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> coordinates_y DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> coordinates_z DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> discovery_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exploration_status STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- PSA columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> data_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/staging/stg_systems/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (&lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.stg_planets (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> planet_id STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> system_id STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> planet_name STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> planet_type STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> orbital_period DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> has_atmosphere STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> habitability_score DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- PSA columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> data_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/staging/stg_planets/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (&lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> space_exploration.stg_updates (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> system_id STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> update_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> status STRING,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ships_present INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> resource_level DOUBLE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- PSA columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> data_date DATE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> load_timestamp &lt;span style="color:#66d9ef">TIMESTAMP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PARTITIONED &lt;span style="color:#66d9ef">BY&lt;/span> (data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LOCATION&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s3://space-data-pipeline-demo/staging/stg_updates/&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TBLPROPERTIES (&lt;span style="color:#e6db74">&amp;#39;table_type&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ICEBERG&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;format&amp;#39;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;parquet&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;p>&lt;strong>Key design decisions:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>dim_date&lt;/strong>: Partitioned by &lt;code>year&lt;/code>. This is a &lt;em>reference dimension&lt;/em>—typically loaded once with all dates from 1900-01-01 to 9999-12-31, then rarely updated.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>dim_system / dim_planet&lt;/strong>: SCD Type 2 with &lt;code>effective_date&lt;/code>, &lt;code>expiration_date&lt;/code>, &lt;code>current_flag&lt;/code>. Partitioned by &lt;code>effective_year&lt;/code> for efficient history queries. Updated daily.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>fact_system_updates&lt;/strong>: Partitioned by &lt;code>date_year&lt;/code>. Loaded incrementally using delete-then-insert pattern.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Staging tables (PSA)&lt;/strong>: Partitioned by &lt;code>data_date&lt;/code>. This lets us reprocess any historical date and provides dependency triggers.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/br>
&lt;/br>
&lt;h3 id="the-heart-of-the-system-orchestrated-glue-jobs-with-per-table-wap">The Heart of the System: Orchestrated Glue Jobs with Per-Table WAP&lt;/h3>
&lt;hr>
&lt;p>Each job does its own Write-Audit-Publish cycle. Let me walk you through each one.&lt;/p>
&lt;hr>
&lt;h4 id="job-1-build-date-dimension-reference-dimension">Job 1: Build Date Dimension (Reference Dimension)&lt;/h4>
&lt;p>The date dimension is special—it&amp;rsquo;s a &lt;em>reference dimension&lt;/em>. We generate it ourselves with all calendar attributes, typically as a one-time load. You&amp;rsquo;d only rerun this if you need to extend the date range or add new attributes (like new holidays).&lt;/p>
&lt;details>
&lt;summary>&lt;strong>Click to expand: build_dim_date Glue Job&lt;/strong>&lt;/summary>
&lt;p>This job generates a complete date dimension from 2000-01-01 to 2050-12-31 (for demo purposes—production would use a wider range). It creates date attributes like day_of_week, quarter, fiscal_year, and is_weekend, then runs WAP to validate before publishing.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Glue Job: build_dim_date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># This is typically a ONE-TIME RUN to populate all dates&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Rerun only when extending date range or adding attributes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.transforms &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.utils &lt;span style="color:#f92672">import&lt;/span> getResolvedOptions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.context &lt;span style="color:#f92672">import&lt;/span> SparkContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.context &lt;span style="color:#f92672">import&lt;/span> GlueContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.job &lt;span style="color:#f92672">import&lt;/span> Job
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.functions &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.types &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> datetime &lt;span style="color:#f92672">import&lt;/span> datetime, timedelta
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Initialize&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>args &lt;span style="color:#f92672">=&lt;/span> getResolvedOptions(sys&lt;span style="color:#f92672">.&lt;/span>argv, [&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sc &lt;span style="color:#f92672">=&lt;/span> SparkContext()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glueContext &lt;span style="color:#f92672">=&lt;/span> GlueContext(sc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark &lt;span style="color:#f92672">=&lt;/span> glueContext&lt;span style="color:#f92672">.&lt;/span>spark_session
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job &lt;span style="color:#f92672">=&lt;/span> Job(glueContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>init(args[&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>], args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;Building Date Dimension (Reference Dimension)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;This is typically a ONE-TIME load&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Generate comprehensive date range&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Standard practice: 1900-01-01 to 9999-12-31&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>start_date &lt;span style="color:#f92672">=&lt;/span> datetime(&lt;span style="color:#ae81ff">1900&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>end_date &lt;span style="color:#f92672">=&lt;/span> datetime(&lt;span style="color:#ae81ff">9999&lt;/span>, &lt;span style="color:#ae81ff">12&lt;/span>, &lt;span style="color:#ae81ff">31&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># For demo purposes, let&amp;#39;s use a more reasonable range&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># In production, you&amp;#39;d use the full range&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>start_date &lt;span style="color:#f92672">=&lt;/span> datetime(&lt;span style="color:#ae81ff">2000&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>end_date &lt;span style="color:#f92672">=&lt;/span> datetime(&lt;span style="color:#ae81ff">2050&lt;/span>, &lt;span style="color:#ae81ff">12&lt;/span>, &lt;span style="color:#ae81ff">31&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Generating dates from &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>start_date&lt;span style="color:#f92672">.&lt;/span>date()&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> to &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>end_date&lt;span style="color:#f92672">.&lt;/span>date()&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>date_list &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>current &lt;span style="color:#f92672">=&lt;/span> start_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> current &lt;span style="color:#f92672">&amp;lt;=&lt;/span> end_date:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_list&lt;span style="color:#f92672">.&lt;/span>append((current,))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current &lt;span style="color:#f92672">+=&lt;/span> timedelta(days&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>date_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>createDataFrame(date_list, [&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Build dimension with rich attributes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dim_date &lt;span style="color:#f92672">=&lt;/span> date_df&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Surrogate key: YYYYMMDD format&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (year(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">10000&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> month(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dayofmonth(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>))&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;int&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_format(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;EEEE&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;day_of_week&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dayofmonth(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;day_of_month&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dayofyear(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;day_of_year&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> weekofyear(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;week_of_year&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> month(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;month_number&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> date_format(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;MMMM&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;month_name&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> quarter(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;quarter&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> year(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;year&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Business-friendly flags&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> when(dayofweek(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isin([&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">7&lt;/span>]), &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>otherwise(&lt;span style="color:#e6db74">&amp;#34;N&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;is_weekend&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;N&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;is_holiday&amp;#34;&lt;/span>), &lt;span style="color:#75715e"># Populate from holiday calendar&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Fiscal calendar (assuming fiscal year = calendar year)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> quarter(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;fiscal_quarter&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> year(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;fiscal_year&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Audit columns&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_timestamp()&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;load_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;GENERATED&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;source_system&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>row_count &lt;span style="color:#f92672">=&lt;/span> dim_date&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Generated &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> date rows&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># WRITE PHASE: Write to staging branch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- WRITE PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;Writing to staging branch...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_date CREATE BRANCH IF NOT EXISTS staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dim_date&lt;span style="color:#f92672">.&lt;/span>writeTo(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_date&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>overwritePartitions()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;Written to staging branch&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># AUDIT PHASE: Validate before publishing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- AUDIT PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staging_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_date&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>audit_failures &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 1: Row count&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staged_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 1 - Row count: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> staged_count &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">&amp;#34;FAIL: dim_date is empty&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 2: Primary key uniqueness&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unique_keys &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> staged_count &lt;span style="color:#f92672">!=&lt;/span> unique_keys:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> duplicates &lt;span style="color:#f92672">=&lt;/span> staged_count &lt;span style="color:#f92672">-&lt;/span> unique_keys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>duplicates&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> duplicate date_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - PK uniqueness: FAIL (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>duplicates&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> duplicates)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - PK uniqueness: PASS (all &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>unique_keys&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> unique)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 3: No NULLs in critical fields&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>null_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull() &lt;span style="color:#f92672">|&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> null_count &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> NULL values in critical fields&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 3 - NULL check: FAIL (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> NULLs)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 3 - NULL check: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 4: Date range coverage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>date_range &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>agg(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;min_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max(&lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;max_date&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>collect()[&lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 4 - Date range: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>date_range[&lt;span style="color:#e6db74">&amp;#39;min_date&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> to &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>date_range[&lt;span style="color:#e6db74">&amp;#39;max_date&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># PUBLISH PHASE: Merge to main if audits pass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- PUBLISH PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> len(audit_failures) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">AUDIT FAILED:&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> failure &lt;span style="color:#f92672">in&lt;/span> audit_failures:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>failure&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">Branch NOT merged. Production unchanged.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_date DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span>(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit failed with &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(audit_failures)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> errors&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">ALL AUDITS PASSED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Merging staging branch to main...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Fast-forward main to staging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> CALL system.fast_forward(
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> table =&amp;gt; &amp;#39;space_exploration.dim_date&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> branch =&amp;gt; &amp;#39;main&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> to =&amp;gt; &amp;#39;staging&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> )
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Clean up staging branch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_date DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;PUBLISHED: dim_date (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;dim_date job complete&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;hr>
&lt;h4 id="job-2-build-system-dimension-scd-type-2">Job 2: Build System Dimension (SCD Type 2)&lt;/h4>
&lt;p>This dimension tracks history. When a system&amp;rsquo;s attributes change (like &lt;code>exploration_status&lt;/code>), we expire the old row and create a new one.&lt;/p>
&lt;details>
&lt;summary>&lt;strong>Click to expand: build_dim_system Glue Job&lt;/strong>&lt;/summary>
&lt;p>This job implements SCD Type 2 change detection. It compares incoming data against the current dimension, identifies NEW/CHANGED/UNCHANGED records, expires old versions, and creates new versions with updated surrogate keys.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Glue Job: build_dim_system&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># SCD Type 2 implementation with effective/expiration dates&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.transforms &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.utils &lt;span style="color:#f92672">import&lt;/span> getResolvedOptions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.context &lt;span style="color:#f92672">import&lt;/span> SparkContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.context &lt;span style="color:#f92672">import&lt;/span> GlueContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.job &lt;span style="color:#f92672">import&lt;/span> Job
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.functions &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.window &lt;span style="color:#f92672">import&lt;/span> Window
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>args &lt;span style="color:#f92672">=&lt;/span> getResolvedOptions(sys&lt;span style="color:#f92672">.&lt;/span>argv, [&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;DATA_DATE&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sc &lt;span style="color:#f92672">=&lt;/span> SparkContext()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glueContext &lt;span style="color:#f92672">=&lt;/span> GlueContext(sc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark &lt;span style="color:#f92672">=&lt;/span> glueContext&lt;span style="color:#f92672">.&lt;/span>spark_session
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job &lt;span style="color:#f92672">=&lt;/span> Job(glueContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>init(args[&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>], args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>data_date &lt;span style="color:#f92672">=&lt;/span> args[&lt;span style="color:#e6db74">&amp;#39;DATA_DATE&amp;#39;&lt;/span>] &lt;span style="color:#75715e"># e.g., &amp;#39;2026-01-17&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Building System Dimension (SCD Type 2)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Data Date: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># LOAD: Read from Persistent Staging Area&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- LOAD PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Read today&amp;#39;s staged data (already landed in PSA)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.stg_systems&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_count &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Source rows from PSA (data_date=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">): &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>source_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> source_count &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;No data for this date. Exiting.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sys&lt;span style="color:#f92672">.&lt;/span>exit(&lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Read current production dimension (for SCD2 comparison)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_dim &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_count &lt;span style="color:#f92672">=&lt;/span> current_dim&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Current dimension rows (current_flag=&amp;#39;Y&amp;#39;): &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>current_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> has_existing_data &lt;span style="color:#f92672">=&lt;/span> current_count &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">except&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;No existing dimension data (first load)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> has_existing_data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_dim &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># TRANSFORM: SCD Type 2 Logic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- TRANSFORM PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">not&lt;/span> has_existing_data:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># First load: all rows are new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;First load - all rows are INSERT&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window_spec &lt;span style="color:#f92672">=&lt;/span> Window&lt;span style="color:#f92672">.&lt;/span>orderBy(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_dim &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> row_number()&lt;span style="color:#f92672">.&lt;/span>over(window_spec)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_name&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;sector&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;main_star_type&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;coordinates_x&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;coordinates_y&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;coordinates_z&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;discovery_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;exploration_status&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(data_date)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;effective_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;9999-12-31&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;expiration_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_timestamp()&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;load_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;ELITE_DANGEROUS&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;source_system&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> year(lit(data_date))&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;effective_year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )&lt;span style="color:#f92672">.&lt;/span>distinct()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Incremental load: detect changes for SCD2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Incremental load - detecting changes for SCD2&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Define which columns to track for changes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracked_columns &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;system_name&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;sector&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;main_star_type&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;coordinates_x&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;coordinates_y&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;coordinates_z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;exploration_status&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Join source with current to detect changes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> comparison &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;src&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>join(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_dim&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;curr&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.system_id&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.system_id&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;full_outer&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Identify: NEW (no match in current), CHANGED (values differ), UNCHANGED&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> change_detection &lt;span style="color:#f92672">=&lt;/span> comparison&lt;span style="color:#f92672">.&lt;/span>withColumn(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;change_type&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> when(col(&lt;span style="color:#e6db74">&amp;#34;curr.system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull(), &lt;span style="color:#e6db74">&amp;#34;NEW&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>when(col(&lt;span style="color:#e6db74">&amp;#34;src.system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull(), &lt;span style="color:#e6db74">&amp;#34;DELETED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>when(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (col(&lt;span style="color:#e6db74">&amp;#34;src.system_name&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.system_name&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (col(&lt;span style="color:#e6db74">&amp;#34;src.sector&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.sector&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (col(&lt;span style="color:#e6db74">&amp;#34;src.main_star_type&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.main_star_type&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (col(&lt;span style="color:#e6db74">&amp;#34;src.exploration_status&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.exploration_status&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (abs(col(&lt;span style="color:#e6db74">&amp;#34;src.coordinates_x&amp;#34;&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.coordinates_x&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0.001&lt;/span>) &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (abs(col(&lt;span style="color:#e6db74">&amp;#34;src.coordinates_y&amp;#34;&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.coordinates_y&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0.001&lt;/span>) &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (abs(col(&lt;span style="color:#e6db74">&amp;#34;src.coordinates_z&amp;#34;&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;curr.coordinates_z&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0.001&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;CHANGED&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>otherwise(&lt;span style="color:#e6db74">&amp;#34;UNCHANGED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Count changes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> change_counts &lt;span style="color:#f92672">=&lt;/span> change_detection&lt;span style="color:#f92672">.&lt;/span>groupBy(&lt;span style="color:#e6db74">&amp;#34;change_type&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>count()&lt;span style="color:#f92672">.&lt;/span>collect()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> row &lt;span style="color:#f92672">in&lt;/span> change_counts:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row[&lt;span style="color:#e6db74">&amp;#39;change_type&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>row[&lt;span style="color:#e6db74">&amp;#39;count&amp;#39;&lt;/span>]&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Get max surrogate key for new key generation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_key &lt;span style="color:#f92672">=&lt;/span> current_dim&lt;span style="color:#f92672">.&lt;/span>agg(max(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>))&lt;span style="color:#f92672">.&lt;/span>collect()[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">or&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Build new dimension rows for NEW and CHANGED records&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_records &lt;span style="color:#f92672">=&lt;/span> change_detection&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;change_type&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isin([&lt;span style="color:#e6db74">&amp;#34;NEW&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;CHANGED&amp;#34;&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window_spec &lt;span style="color:#f92672">=&lt;/span> Window&lt;span style="color:#f92672">.&lt;/span>orderBy(&lt;span style="color:#e6db74">&amp;#34;src.system_id&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_rows &lt;span style="color:#f92672">=&lt;/span> new_records&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (row_number()&lt;span style="color:#f92672">.&lt;/span>over(window_spec) &lt;span style="color:#f92672">+&lt;/span> max_key)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.system_name&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;system_name&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.sector&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;sector&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.main_star_type&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;main_star_type&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.coordinates_x&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;coordinates_x&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.coordinates_y&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;coordinates_y&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.coordinates_z&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;coordinates_z&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.discovery_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;discovery_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;src.exploration_status&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;exploration_status&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(data_date)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;effective_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;9999-12-31&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;expiration_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_timestamp()&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;load_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;ELITE_DANGEROUS&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;source_system&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> year(lit(data_date))&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;effective_year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Build expired rows (for CHANGED records, expire the old version)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> changed_ids &lt;span style="color:#f92672">=&lt;/span> change_detection&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;change_type&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;CHANGED&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(col(&lt;span style="color:#e6db74">&amp;#34;curr.system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expired_rows &lt;span style="color:#f92672">=&lt;/span> current_dim&lt;span style="color:#f92672">.&lt;/span>join(changed_ids, &lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>withColumn(&lt;span style="color:#e6db74">&amp;#34;expiration_date&amp;#34;&lt;/span>, lit(data_date)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>withColumn(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>, lit(&lt;span style="color:#e6db74">&amp;#34;N&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Unchanged rows stay as-is&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unchanged_rows &lt;span style="color:#f92672">=&lt;/span> current_dim&lt;span style="color:#f92672">.&lt;/span>join(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> change_detection&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;change_type&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;UNCHANGED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(col(&lt;span style="color:#e6db74">&amp;#34;curr.system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>)),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Historical rows (already expired) - read from full table&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> historical_rows &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;N&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Combine all&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_dim &lt;span style="color:#f92672">=&lt;/span> new_rows \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>unionByName(expired_rows, allowMissingColumns&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>unionByName(unchanged_rows, allowMissingColumns&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>unionByName(historical_rows, allowMissingColumns&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transform_count &lt;span style="color:#f92672">=&lt;/span> new_dim&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Total dimension rows after SCD2: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>transform_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># WRITE PHASE: Write to staging branch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- WRITE PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_system CREATE BRANCH IF NOT EXISTS staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>new_dim&lt;span style="color:#f92672">.&lt;/span>writeTo(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>overwritePartitions()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;Written to staging branch&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># AUDIT PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- AUDIT PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staging_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>audit_failures &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 1: Row count&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staged_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 1 - Row count: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> staged_count &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">&amp;#34;FAIL: dim_system is empty&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 2: Surrogate key uniqueness&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unique_keys &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> staged_count &lt;span style="color:#f92672">!=&lt;/span> unique_keys:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> duplicates &lt;span style="color:#f92672">=&lt;/span> staged_count &lt;span style="color:#f92672">-&lt;/span> unique_keys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>duplicates&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> duplicate system_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - PK uniqueness: FAIL (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>duplicates&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> duplicates)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - PK uniqueness: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 3: Only one current row per natural key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>current_rows &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>current_count &lt;span style="color:#f92672">=&lt;/span> current_rows&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unique_natural_keys &lt;span style="color:#f92672">=&lt;/span> current_rows&lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> current_count &lt;span style="color:#f92672">!=&lt;/span> unique_natural_keys:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: Multiple current rows for same system_id&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 3 - Single current: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 3 - Single current: PASS (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>current_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> current rows)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 4: No NULLs in critical fields&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>null_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull() &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull() &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;effective_date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull() &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> null_count &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> NULL values in critical fields&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 4 - NULL check: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 4 - NULL check: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 5: Effective/expiration date logic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>invalid_dates &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;effective_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;expiration_date&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> invalid_dates &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>invalid_dates&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows with effective &amp;gt; expiration&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 5 - Date logic: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 5 - Date logic: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># PUBLISH PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- PUBLISH PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> len(audit_failures) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">AUDIT FAILED:&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> failure &lt;span style="color:#f92672">in&lt;/span> audit_failures:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>failure&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">Branch NOT merged. Production unchanged.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_system DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span>(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit failed with &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(audit_failures)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> errors&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">ALL AUDITS PASSED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Merging staging branch to main...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> CALL system.fast_forward(
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> table =&amp;gt; &amp;#39;space_exploration.dim_system&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> branch =&amp;gt; &amp;#39;main&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> to =&amp;gt; &amp;#39;staging&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> )
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_system DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;PUBLISHED: dim_system (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;hr>
&lt;h4 id="job-3-build-planet-dimension-scd-type-2">Job 3: Build Planet Dimension (SCD Type 2)&lt;/h4>
&lt;p>Similar pattern to dim&lt;em>system, but with a foreign key lookup to the _published&lt;/em> dim_system.&lt;/p>
&lt;details>
&lt;summary>&lt;strong>Click to expand: build_dim_planet Glue Job&lt;/strong>&lt;/summary>
&lt;p>This job demonstrates FK lookup to a published dimension. It reads from the main branch of dim_system (not staging), ensuring it uses production-ready surrogate keys. The FK integrity audit validates this relationship.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Glue Job: build_dim_planet&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># SCD Type 2 with FK lookup to published dim_system&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.transforms &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.utils &lt;span style="color:#f92672">import&lt;/span> getResolvedOptions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.context &lt;span style="color:#f92672">import&lt;/span> SparkContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.context &lt;span style="color:#f92672">import&lt;/span> GlueContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.job &lt;span style="color:#f92672">import&lt;/span> Job
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.functions &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.window &lt;span style="color:#f92672">import&lt;/span> Window
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>args &lt;span style="color:#f92672">=&lt;/span> getResolvedOptions(sys&lt;span style="color:#f92672">.&lt;/span>argv, [&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;DATA_DATE&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sc &lt;span style="color:#f92672">=&lt;/span> SparkContext()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glueContext &lt;span style="color:#f92672">=&lt;/span> GlueContext(sc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark &lt;span style="color:#f92672">=&lt;/span> glueContext&lt;span style="color:#f92672">.&lt;/span>spark_session
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job &lt;span style="color:#f92672">=&lt;/span> Job(glueContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>init(args[&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>], args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>data_date &lt;span style="color:#f92672">=&lt;/span> args[&lt;span style="color:#e6db74">&amp;#39;DATA_DATE&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Building Planet Dimension (SCD Type 2)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Data Date: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># LOAD PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- LOAD PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.stg_planets&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_count &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Source rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>source_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> source_count &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;No data. Exiting.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sys&lt;span style="color:#f92672">.&lt;/span>exit(&lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># CRITICAL: Read from PUBLISHED dim_system (main branch, not staging)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># This is why dim_planet must run AFTER dim_system publishes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dim_system &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;dim_system lookup rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>dim_system&lt;span style="color:#f92672">.&lt;/span>count()&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># TRANSFORM PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- TRANSFORM PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Check for existing data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_dim &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_planet&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> has_existing &lt;span style="color:#f92672">=&lt;/span> current_dim&lt;span style="color:#f92672">.&lt;/span>count() &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_key &lt;span style="color:#f92672">=&lt;/span> current_dim&lt;span style="color:#f92672">.&lt;/span>agg(max(&lt;span style="color:#e6db74">&amp;#34;planet_key&amp;#34;&lt;/span>))&lt;span style="color:#f92672">.&lt;/span>collect()[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">or&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">except&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> has_existing &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_key &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Join source with dim_system to get surrogate key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_with_sk &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>join(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_system,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_df&lt;span style="color:#f92672">.&lt;/span>system_id &lt;span style="color:#f92672">==&lt;/span> dim_system&lt;span style="color:#f92672">.&lt;/span>system_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;left&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_df[&lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_system&lt;span style="color:#f92672">.&lt;/span>system_key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Track orphaned planets (no matching system)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>orphaned &lt;span style="color:#f92672">=&lt;/span> source_with_sk&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull())&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> orphaned &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;WARNING: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>orphaned&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> planets have no matching system!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># SCD2 logic (simplified for first load demonstration)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>window_spec &lt;span style="color:#f92672">=&lt;/span> Window&lt;span style="color:#f92672">.&lt;/span>orderBy(&lt;span style="color:#e6db74">&amp;#34;planet_id&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>new_dim &lt;span style="color:#f92672">=&lt;/span> source_with_sk&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (row_number()&lt;span style="color:#f92672">.&lt;/span>over(window_spec) &lt;span style="color:#f92672">+&lt;/span> max_key)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;planet_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;planet_id&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;planet_name&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;planet_type&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;orbital_period&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;has_atmosphere&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;habitability_score&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(data_date)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;effective_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;9999-12-31&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;expiration_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_timestamp()&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;load_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;ELITE_DANGEROUS&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;source_system&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> year(lit(data_date))&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;effective_year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>distinct()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># (Full SCD2 change detection would follow same pattern as dim_system)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transform_count &lt;span style="color:#f92672">=&lt;/span> new_dim&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Dimension rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>transform_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># WRITE PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- WRITE PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_planet CREATE BRANCH IF NOT EXISTS staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>new_dim&lt;span style="color:#f92672">.&lt;/span>writeTo(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_planet&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>overwritePartitions()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;Written to staging branch&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># AUDIT PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- AUDIT PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staging_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_planet&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>audit_failures &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 1: PK uniqueness&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staged_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unique_keys &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;planet_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> staged_count &lt;span style="color:#f92672">!=&lt;/span> unique_keys:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: Duplicate planet_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 1 - PK uniqueness: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 2: FK integrity to dim_system (published!)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>published_system_keys &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>orphaned_fk &lt;span style="color:#f92672">=&lt;/span> staging_df \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNotNull()) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct() \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>join(published_system_keys, &lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;left_anti&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> orphaned_fk &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>orphaned_fk&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> orphaned system_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - FK integrity: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 2 - FK integrity: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 3: NULL check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>null_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;planet_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull() &lt;span style="color:#f92672">|&lt;/span> col(&lt;span style="color:#e6db74">&amp;#34;planet_id&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> null_count &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_count&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> NULLs in critical fields&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 3 - NULL check: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># PUBLISH PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- PUBLISH PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> len(audit_failures) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">AUDIT FAILED:&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> f &lt;span style="color:#f92672">in&lt;/span> audit_failures:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>f&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_planet DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span>(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit failed&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">ALL AUDITS PASSED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> CALL system.fast_forward(
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> table =&amp;gt; &amp;#39;space_exploration.dim_planet&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> branch =&amp;gt; &amp;#39;main&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> to =&amp;gt; &amp;#39;staging&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> )
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.dim_planet DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;PUBLISHED: dim_planet (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;hr>
&lt;h4 id="job-4-build-fact-table-incremental-with-delete-insert">Job 4: Build Fact Table (Incremental with Delete-Insert)&lt;/h4>
&lt;p>This is the critical job. It reads from &lt;em>published&lt;/em> dimensions (main branch), uses delete-then-insert pattern for the date partition, and includes left anti-join as a defensive check for missing records.&lt;/p>
&lt;details>
&lt;summary>&lt;strong>Click to expand: build_fact_system_updates Glue Job&lt;/strong>&lt;/summary>
&lt;p>This job loads facts incrementally by date. For a given data_date, it deletes existing records and inserts new ones. It validates FK integrity against all published dimensions before merging to main.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Glue Job: build_fact_system_updates&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Incremental load with delete-then-insert + left anti-join defensive coding&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.transforms &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.utils &lt;span style="color:#f92672">import&lt;/span> getResolvedOptions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.context &lt;span style="color:#f92672">import&lt;/span> SparkContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.context &lt;span style="color:#f92672">import&lt;/span> GlueContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> awsglue.job &lt;span style="color:#f92672">import&lt;/span> Job
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.functions &lt;span style="color:#f92672">import&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pyspark.sql.window &lt;span style="color:#f92672">import&lt;/span> Window
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>args &lt;span style="color:#f92672">=&lt;/span> getResolvedOptions(sys&lt;span style="color:#f92672">.&lt;/span>argv, [&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;DATA_DATE&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sc &lt;span style="color:#f92672">=&lt;/span> SparkContext()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glueContext &lt;span style="color:#f92672">=&lt;/span> GlueContext(sc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark &lt;span style="color:#f92672">=&lt;/span> glueContext&lt;span style="color:#f92672">.&lt;/span>spark_session
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job &lt;span style="color:#f92672">=&lt;/span> Job(glueContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>init(args[&lt;span style="color:#e6db74">&amp;#39;JOB_NAME&amp;#39;&lt;/span>], args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>data_date &lt;span style="color:#f92672">=&lt;/span> args[&lt;span style="color:#e6db74">&amp;#39;DATA_DATE&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Building Fact Table: System Updates (Incremental)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Data Date: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># LOAD PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- LOAD PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Read from PSA for this data_date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.stg_updates&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_count &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Source rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>source_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> source_count &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;No data. Exiting.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sys&lt;span style="color:#f92672">.&lt;/span>exit(&lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Read PUBLISHED dimensions (main branch)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Fact tables MUST read from published dimensions, not staging branches&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dim_system &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;current_flag&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;system_id&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dim_date &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_date&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;full_date&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;dim_system rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>dim_system&lt;span style="color:#f92672">.&lt;/span>count()&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;dim_date rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>dim_date&lt;span style="color:#f92672">.&lt;/span>count()&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Read existing fact table for incremental logic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> existing_fact &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.fact_system_updates&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> existing_count &lt;span style="color:#f92672">=&lt;/span> existing_fact&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Existing fact rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>existing_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_key &lt;span style="color:#f92672">=&lt;/span> existing_fact&lt;span style="color:#f92672">.&lt;/span>agg(max(&lt;span style="color:#e6db74">&amp;#34;update_key&amp;#34;&lt;/span>))&lt;span style="color:#f92672">.&lt;/span>collect()[&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">or&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">except&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;No existing fact data (first load)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> existing_fact &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_key &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># TRANSFORM PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- TRANSFORM PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Add date column for joining&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source_with_date &lt;span style="color:#f92672">=&lt;/span> source_df&lt;span style="color:#f92672">.&lt;/span>withColumn(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;update_date&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> to_date(&lt;span style="color:#e6db74">&amp;#34;update_timestamp&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Lookup surrogate keys from published dimensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fact_transformed &lt;span style="color:#f92672">=&lt;/span> source_with_date \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>join(dim_system, source_with_date&lt;span style="color:#f92672">.&lt;/span>system_id &lt;span style="color:#f92672">==&lt;/span> dim_system&lt;span style="color:#f92672">.&lt;/span>system_id, &lt;span style="color:#e6db74">&amp;#34;left&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>join(dim_date, source_with_date&lt;span style="color:#f92672">.&lt;/span>update_date &lt;span style="color:#f92672">==&lt;/span> dim_date&lt;span style="color:#f92672">.&lt;/span>full_date, &lt;span style="color:#e6db74">&amp;#34;left&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_system&lt;span style="color:#f92672">.&lt;/span>system_key,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source_with_date&lt;span style="color:#f92672">.&lt;/span>update_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;status&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;ships_present&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;resource_level&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(data_date)&lt;span style="color:#f92672">.&lt;/span>cast(&lt;span style="color:#e6db74">&amp;#34;date&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dim_date&lt;span style="color:#f92672">.&lt;/span>year&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;date_year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Track orphaned facts&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>orphaned_systems &lt;span style="color:#f92672">=&lt;/span> fact_transformed&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull())&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>orphaned_dates &lt;span style="color:#f92672">=&lt;/span> fact_transformed&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull())&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> orphaned_systems &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;WARNING: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>orphaned_systems&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> facts have no matching system&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> orphaned_dates &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;WARNING: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>orphaned_dates&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> facts have no matching date&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># INCREMENTAL LOGIC: Delete-then-Insert + Left Anti-Join&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- INCREMENTAL LOGIC ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Extract the date partition we&amp;#39;re loading&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>date_partition_year &lt;span style="color:#f92672">=&lt;/span> int(data_date[:&lt;span style="color:#ae81ff">4&lt;/span>]) &lt;span style="color:#75715e"># e.g., 2026&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> existing_fact &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Step 1: Get existing records for this data_date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> existing_for_date &lt;span style="color:#f92672">=&lt;/span> existing_fact&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> existing_for_date_count &lt;span style="color:#f92672">=&lt;/span> existing_for_date&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Existing records for data_date=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>existing_for_date_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Step 2: Records NOT in this date (keep as-is)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> records_to_keep &lt;span style="color:#f92672">=&lt;/span> existing_fact&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> data_date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> keep_count &lt;span style="color:#f92672">=&lt;/span> records_to_keep&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Records to keep (other dates): &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>keep_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Step 3: New records from source (delete-then-insert for this date)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Generate new surrogate keys starting after max existing key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window_spec &lt;span style="color:#f92672">=&lt;/span> Window&lt;span style="color:#f92672">.&lt;/span>orderBy(&lt;span style="color:#e6db74">&amp;#34;update_timestamp&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_records &lt;span style="color:#f92672">=&lt;/span> fact_transformed&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (row_number()&lt;span style="color:#f92672">.&lt;/span>over(window_spec) &lt;span style="color:#f92672">+&lt;/span> max_key)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;update_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;update_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;status&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;ships_present&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;resource_level&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_timestamp()&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;load_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;ELITE_DANGEROUS&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;source_system&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;date_year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_count &lt;span style="color:#f92672">=&lt;/span> new_records&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;New records for data_date=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>new_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Step 4: Left anti-join defensive check&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Find any records in destination that should exist but don&amp;#39;t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># (This catches edge cases where source has records not in our transform)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># For this example, we&amp;#39;re doing a full replace for the date, so this is informational&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Step 5: Combine: kept records + new records (effectively delete-insert)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> final_fact &lt;span style="color:#f92672">=&lt;/span> records_to_keep&lt;span style="color:#f92672">.&lt;/span>unionByName(new_records)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># First load: all records are new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window_spec &lt;span style="color:#f92672">=&lt;/span> Window&lt;span style="color:#f92672">.&lt;/span>orderBy(&lt;span style="color:#e6db74">&amp;#34;update_timestamp&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> final_fact &lt;span style="color:#f92672">=&lt;/span> fact_transformed&lt;span style="color:#f92672">.&lt;/span>select(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> row_number()&lt;span style="color:#f92672">.&lt;/span>over(window_spec)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;update_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;update_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;status&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;ships_present&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;resource_level&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_timestamp()&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;load_timestamp&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lit(&lt;span style="color:#e6db74">&amp;#34;ELITE_DANGEROUS&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>alias(&lt;span style="color:#e6db74">&amp;#34;source_system&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> col(&lt;span style="color:#e6db74">&amp;#34;date_year&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transform_count &lt;span style="color:#f92672">=&lt;/span> final_fact&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Final fact rows: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>transform_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># WRITE PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- WRITE PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.fact_system_updates CREATE BRANCH IF NOT EXISTS staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>final_fact&lt;span style="color:#f92672">.&lt;/span>writeTo(&lt;span style="color:#e6db74">&amp;#34;space_exploration.fact_system_updates&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>overwritePartitions()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;Written to staging branch&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># AUDIT PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- AUDIT PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staging_df &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>option(&lt;span style="color:#e6db74">&amp;#34;branch&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;staging&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.fact_system_updates&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>audit_failures &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 1: Row count (source to target for this date)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staged_for_date &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;data_date&amp;#34;&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> data_date)&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 1 - Row count for &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">: Source=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>source_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">, Target=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_for_date&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> source_count &lt;span style="color:#f92672">!=&lt;/span> staged_for_date:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># This might be OK if we filtered orphans, but flag for review&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; Note: Difference of &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>source_count &lt;span style="color:#f92672">-&lt;/span> staged_for_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> (likely orphaned records)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 2: PK uniqueness&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>staged_count &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unique_keys &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;update_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> staged_count &lt;span style="color:#f92672">!=&lt;/span> unique_keys:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: Duplicate update_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - PK uniqueness: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 2 - PK uniqueness: PASS (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> unique)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 3: FK integrity to dim_system&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>published_system_keys &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_system&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>orphaned_fk_system &lt;span style="color:#f92672">=&lt;/span> staging_df \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNotNull()) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct() \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>join(published_system_keys, &lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;left_anti&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> orphaned_fk_system &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>orphaned_fk_system&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> orphaned system_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 3 - FK to dim_system: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 3 - FK to dim_system: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 4: FK integrity to dim_date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>published_date_keys &lt;span style="color:#f92672">=&lt;/span> spark&lt;span style="color:#f92672">.&lt;/span>read&lt;span style="color:#f92672">.&lt;/span>table(&lt;span style="color:#e6db74">&amp;#34;space_exploration.dim_date&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>orphaned_fk_date &lt;span style="color:#f92672">=&lt;/span> staging_df \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNotNull()) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>select(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>distinct() \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>join(published_date_keys, &lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;left_anti&amp;#34;&lt;/span>) \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> orphaned_fk_date &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>orphaned_fk_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> orphaned date_keys&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 4 - FK to dim_date: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 4 - FK to dim_date: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 5: No NULLs in mandatory FKs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># (We allow NULLs for orphaned records, but track them)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>null_system &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;system_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull())&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>null_date &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;date_key&amp;#34;&lt;/span>)&lt;span style="color:#f92672">.&lt;/span>isNull())&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 5 - NULL FKs: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_system&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> null system_key, &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>null_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> null date_key&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 6: Value range checks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>invalid_resources &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (col(&lt;span style="color:#e6db74">&amp;#34;resource_level&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">|&lt;/span> (col(&lt;span style="color:#e6db74">&amp;#34;resource_level&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> invalid_resources &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>invalid_resources&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> invalid resource_level values&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 6 - Value ranges: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 6 - Value ranges: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Audit 7: No future timestamps&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>future_ts &lt;span style="color:#f92672">=&lt;/span> staging_df&lt;span style="color:#f92672">.&lt;/span>filter(col(&lt;span style="color:#e6db74">&amp;#34;update_timestamp&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> current_timestamp())&lt;span style="color:#f92672">.&lt;/span>count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> future_ts &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audit_failures&lt;span style="color:#f92672">.&lt;/span>append(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;FAIL: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>future_ts&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> future timestamps&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit 7 - Timestamps: FAIL&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Audit 7 - Timestamps: PASS&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># PUBLISH PHASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ============================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">--- PUBLISH PHASE ---&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> len(audit_failures) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">AUDIT FAILED:&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> f &lt;span style="color:#f92672">in&lt;/span> audit_failures:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>f&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.fact_system_updates DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">Exception&lt;/span>(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Audit failed with &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(audit_failures)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> errors&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">ALL AUDITS PASSED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> CALL system.fast_forward(
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> table =&amp;gt; &amp;#39;space_exploration.fact_system_updates&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> branch =&amp;gt; &amp;#39;main&amp;#39;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> to =&amp;gt; &amp;#39;staging&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> )
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spark&lt;span style="color:#f92672">.&lt;/span>sql(&lt;span style="color:#e6db74">&amp;#34;ALTER TABLE space_exploration.fact_system_updates DROP BRANCH staging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;PUBLISHED: fact_system_updates (&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>staged_count&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">,&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> rows)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>job&lt;span style="color:#f92672">.&lt;/span>commit()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;hr>
&lt;h3 id="step-functions-workflow">Step Functions Workflow&lt;/h3>
&lt;hr>
&lt;p>The Step Functions workflow orchestrates everything. Here&amp;rsquo;s the execution flow:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Stage 1 (Parallel):&lt;/strong> dim_date and dim_system run simultaneously—neither depends on the other&lt;/li>
&lt;li>&lt;strong>Stage 2:&lt;/strong> dim_planet runs after Stage 1 completes (needs dim_system&amp;rsquo;s surrogate keys)&lt;/li>
&lt;li>&lt;strong>Stage 3:&lt;/strong> fact_system_updates runs last (needs all dimension surrogate keys)&lt;/li>
&lt;/ol>
&lt;p>If any job fails, the workflow stops and sends an SNS notification.&lt;/p>
&lt;details>
&lt;summary>&lt;strong>Click to expand: Step Functions JSON Definition&lt;/strong>&lt;/summary>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Comment&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Dimensional Modeling Pipeline - Per-Table WAP Pattern&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;StartAt&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Parallel Dimensions Stage 1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;States&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Parallel Dimensions Stage 1&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Parallel&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Comment&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;dim_date and dim_system have no dependencies&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Branches&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;StartAt&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Build dim_date (WAP)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;States&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Build dim_date (WAP)&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Task&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;arn:aws:states:::glue:startJobRun.sync&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Parameters&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;JobName&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;build_dim_date&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;End&amp;#34;&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;StartAt&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Build dim_system (WAP)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;States&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Build dim_system (WAP)&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Task&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;arn:aws:states:::glue:startJobRun.sync&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Parameters&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;JobName&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;build_dim_system&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Arguments&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;--DATA_DATE.$&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;$.data_date&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;End&amp;#34;&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Build dim_planet (WAP)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Catch&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ErrorEquals&amp;#34;&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;States.ALL&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Notify Failure&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ResultPath&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;$.error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Build dim_planet (WAP)&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Task&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Comment&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Depends on dim_system being published&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;arn:aws:states:::glue:startJobRun.sync&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Parameters&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;JobName&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;build_dim_planet&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Arguments&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;--DATA_DATE.$&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;$.data_date&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Build fact_system_updates (WAP)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Catch&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ErrorEquals&amp;#34;&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;States.ALL&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Notify Failure&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ResultPath&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;$.error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Build fact_system_updates (WAP)&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Task&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Comment&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Depends on ALL dimensions being published&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;arn:aws:states:::glue:startJobRun.sync&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Parameters&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;JobName&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;build_fact_system_updates&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Arguments&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;--DATA_DATE.$&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;$.data_date&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Pipeline Succeeded&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Catch&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ErrorEquals&amp;#34;&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;States.ALL&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Notify Failure&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ResultPath&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;$.error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Notify Failure&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Task&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;arn:aws:states:::sns:publish&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Parameters&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;TopicArn&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;arn:aws:sns:ap-southeast-2:ACCOUNT:pipeline-alerts&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Message.$&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;States.Format(&amp;#39;Pipeline failed: {}&amp;#39;, $.error.Cause)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Subject&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Data Pipeline Failed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Next&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Pipeline Failed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Pipeline Succeeded&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Succeed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Pipeline Failed&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Fail&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Error&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;PipelineError&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Cause&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;One or more jobs failed - check individual job logs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/details>
&lt;p>Start the workflow with input:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;data_date&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;2026-01-17&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This &lt;code>data_date&lt;/code> propagates to each job, enabling:&lt;/p>
&lt;ul>
&lt;li>PSA partition reads (&lt;code>WHERE data_date = '2026-01-17'&lt;/code>)&lt;/li>
&lt;li>Historical reprocessing (re-run with a past date)&lt;/li>
&lt;li>Dependency triggers (S3 event on partition write triggers workflow)&lt;/li>
&lt;/ul>
&lt;/br>
&lt;/br>
&lt;h3 id="dependency-triggers-with-s3-events">Dependency Triggers with S3 Events&lt;/h3>
&lt;hr>
&lt;p>You can improve this further by using partition writes as triggers. Here&amp;rsquo;s how to set that up:&lt;/p>
&lt;p>&lt;strong>Option 1: S3 Event → EventBridge → Step Functions&lt;/strong>&lt;/p>
&lt;p>When a file lands in &lt;code>landing/updates/&lt;/code>, trigger the pipeline:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># EventBridge Rule&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;source&amp;#34;: &lt;/span>[&lt;span style="color:#e6db74">&amp;#34;aws.s3&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;detail-type&amp;#34;: &lt;/span>[&lt;span style="color:#e6db74">&amp;#34;Object Created&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;detail&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;bucket&amp;#34;: { &amp;#34;name&amp;#34;: &lt;/span>[&lt;span style="color:#e6db74">&amp;#34;space-data-pipeline-demo&amp;#34;&lt;/span>] },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;object&amp;#34;: { &amp;#34;key&amp;#34;: [{ &amp;#34;prefix&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;landing/updates/&amp;#34;&lt;/span> }] },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Option 2: Glue Trigger on Partition&lt;/strong>&lt;/p>
&lt;p>Configure Glue to watch for new partitions:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Check if partition exists before running&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>partition_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;s3://space-data-pipeline-demo/staging/stg_updates/data_date=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> boto3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>s3 &lt;span style="color:#f92672">=&lt;/span> boto3&lt;span style="color:#f92672">.&lt;/span>client(&lt;span style="color:#e6db74">&amp;#39;s3&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> s3&lt;span style="color:#f92672">.&lt;/span>head_object(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Bucket&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;space-data-pipeline-demo&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#39;staging/stg_updates/data_date=&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>data_date&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/_SUCCESS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Partition exists - proceeding&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">except&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Partition not ready - exiting&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sys&lt;span style="color:#f92672">.&lt;/span>exit(&lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This lets you build dependency chains based on partition availability.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-moment-of-truth-querying">The Moment of Truth: Querying&lt;/h3>
&lt;hr>
&lt;p>Now let&amp;rsquo;s query the properly partitioned dimensional model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Query leverages date partition pruning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt.&lt;span style="color:#66d9ef">year&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt.quarter,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d.main_star_type,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> updates,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(f.ships_present) &lt;span style="color:#66d9ef">as&lt;/span> total_ships,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(&lt;span style="color:#66d9ef">AVG&lt;/span>(f.resource_level), &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> avg_resources
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> space_exploration.fact_system_updates f
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> space_exploration.dim_system d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> f.system_key &lt;span style="color:#f92672">=&lt;/span> d.system_key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> d.current_flag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;Y&amp;#39;&lt;/span> &lt;span style="color:#75715e">-- Only current dimension records!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> space_exploration.dim_date dt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> f.date_key &lt;span style="color:#f92672">=&lt;/span> dt.date_key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt.&lt;span style="color:#66d9ef">year&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2026&lt;/span> &lt;span style="color:#75715e">-- Partition pruning!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">AND&lt;/span> dt.quarter &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dt.&lt;span style="color:#66d9ef">year&lt;/span>, dt.quarter, d.main_star_type
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_ships &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notice &lt;code>d.current_flag = 'Y'&lt;/code> — this is how you query SCD Type 2 dimensions for current state. For point-in-time analysis:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Point-in-time analysis: What was system status on Jan 15, 2026?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d.system_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d.exploration_status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d.effective_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d.expiration_date
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> space_exploration.dim_system d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> d.system_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SOL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> DATE &lt;span style="color:#e6db74">&amp;#39;2026-01-15&amp;#39;&lt;/span> &lt;span style="color:#66d9ef">BETWEEN&lt;/span> d.effective_date &lt;span style="color:#66d9ef">AND&lt;/span> d.expiration_date;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/br>
&lt;/br>
&lt;h3 id="a-few-things-worth-remembering">A Few Things Worth Remembering&lt;/h3>
&lt;hr>
&lt;p>&lt;strong>WAP at each table, not just at the end.&lt;/strong> Each job should do its own Write-Audit-Publish cycle. Failures are isolated. Downstream jobs read from published (main) tables.&lt;/p>
&lt;p>&lt;strong>Landing is raw, Staging is persistent.&lt;/strong> Landing = 1:1 from source, latest only. Staging (PSA) = partitioned by &lt;code>data_date&lt;/code> for historical reprocessing.&lt;/p>
&lt;p>&lt;strong>dim_date is a reference dimension.&lt;/strong> Load it once with a wide date range. Partition by year. Rarely needs updates.&lt;/p>
&lt;p>&lt;strong>SCD Type 2 for dimensions that change.&lt;/strong> Add &lt;code>effective_date&lt;/code>, &lt;code>expiration_date&lt;/code>, &lt;code>current_flag&lt;/code>. Partition by effective year. Query with &lt;code>current_flag = 'Y'&lt;/code> for current state.&lt;/p>
&lt;p>&lt;strong>Incremental facts with delete-then-insert.&lt;/strong> For a given date partition: delete existing, insert new. Use left anti-join as defensive check for missing records.&lt;/p>
&lt;p>&lt;strong>Partition strategically:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>dim_date: by year&lt;/li>
&lt;li>dim_system/planet: by effective_year (SCD2)&lt;/li>
&lt;li>fact: by date_year&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Use data_date for dependencies.&lt;/strong> When &lt;code>stg_updates/data_date=2026-01-17/&lt;/code> is written, trigger the downstream pipeline. This makes dependencies explicit and reprocessing easy.&lt;/p>
&lt;hr>
&lt;p>That ten-minute query? Down to 1.2 seconds with partition pruning.&lt;/p>
&lt;p>That conference room moment? Each job&amp;rsquo;s WAP cycle catches issues before they propagate.&lt;/p>
&lt;p>These patterns work. They&amp;rsquo;ve saved me from embarrassment, late nights, and angry emails. They&amp;rsquo;ll do the same for you.&lt;/p></content:encoded><category>Data Engineering</category><category>AWS</category><category>Data Modeling</category><category>AWS Glue</category><category>Dimensional Modeling</category><category>Kimball Methodology</category><category>Data Quality</category><category>ETL</category><category>Write-Audit-Publish</category><category>Apache Iceberg</category><category>Step Functions</category><category>SCD Type 2</category></item></channel></rss>