<?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, 25 Apr 2026 09:00:00 +1100</lastBuildDate><atom:link href="https://ghostinthedata.info/posts/index.xml" rel="self" type="application/rss+xml"/><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><item><title>The 2026 Data Engineering Strategy Nobody's Writing (But Everyone Needs)</title><link>https://ghostinthedata.info/posts/2026/2026-01-15-data-engineering-strategy/</link><pubDate>Thu, 15 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-15-data-engineering-strategy/</guid><author>Chris Hillman</author><description>While teams obsess over AI tools and cost optimization, we're ignoring the talent pipeline crisis that will define success through 2030. Here's what your planning memo should say instead.</description><content:encoded>&lt;p>What if I told you the biggest threat to your data platform isn&amp;rsquo;t technology—it&amp;rsquo;s that we&amp;rsquo;ve stopped building the next generation of engineers who&amp;rsquo;ll run it?&lt;/p>
&lt;br>
&lt;p>Not the latest database that promises to solve everything. Not whether you picked the right orchestrator.&lt;/p>
&lt;p>The real crisis is that we&amp;rsquo;ve systematically broken our talent pipeline. And in 2026, that decision is going to start costing us in ways that no amount of tooling can fix.&lt;/p>
&lt;p>But let&amp;rsquo;s back up. Because if you&amp;rsquo;re in planning mode right now—building roadmaps, setting budgets, arguing about which technologies to bet on—you&amp;rsquo;re probably focused on the wrong things. Everyone is.&lt;/p>
&lt;p>Here&amp;rsquo;s what I believe actually matters for 2026—and what will determine whether you&amp;rsquo;re thriving or scrambling by 2030.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="were-creating-the-next-talent-crisison-purpose">We&amp;rsquo;re Creating the Next Talent Crisis—On Purpose&lt;/h2>
&lt;br>
&lt;p>This was a conversation:&lt;/p>
&lt;p>&amp;ldquo;We need more engineers. Senior people who can hit the ground running.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Agreed. Let&amp;rsquo;s post the role at six years minimum experience.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Why aren&amp;rsquo;t we getting qualified candidates?&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Must be a talent shortage. Everyone wants data engineers these days.&amp;rdquo;&lt;/p>
&lt;p>It&amp;rsquo;s not a shortage. It&amp;rsquo;s a manufactured crisis.&lt;/p>
&lt;p>Entry-level data engineering positions represent just 2% of job postings. Two percent. Meanwhile, roles requiring 6+ years of experience make up nearly 20% of openings. We&amp;rsquo;ve created an impossible paradox: the industry demands experienced engineers while systematically refusing to create them.&lt;/p>
&lt;p>And here&amp;rsquo;s the thing—data engineering skills aren&amp;rsquo;t taught in computer science programs. You don&amp;rsquo;t learn dimensional modeling or pipeline orchestration or data quality frameworks in a classroom. These are skills built through experience, through making mistakes, through having a senior engineer look at your code and say &amp;ldquo;okay, but what happens when this table has a million rows instead of a thousand?&amp;rdquo;&lt;/p>
&lt;p>I&amp;rsquo;ve watched this play out across every team I&amp;rsquo;ve worked with. Companies want plug-and-play professionals, but they won&amp;rsquo;t invest in training. Fresh graduates realize quickly that &amp;ldquo;junior data engineer&amp;rdquo; jobs are about as common as unicorn sightings. The roles exist, technically. They&amp;rsquo;re just not being hired for.&lt;/p>
&lt;p>The retention numbers tell the same story from the other end. Surveys show that 95% of current data engineers report experiencing burnout. Seventy percent are likely to seek new jobs within 12 months. Four out of five are considering leaving the career entirely.&lt;/p>
&lt;p>So we&amp;rsquo;re not hiring juniors, and we&amp;rsquo;re burning out the seniors we have.&lt;/p>
&lt;p>The irony is that hiring juniors actually helps senior engineers. Juniors handle the repetitive stuff—the data quality checks you&amp;rsquo;ve written a hundred times, the standard ETL patterns you could code in your sleep, the documentation that needs doing but never gets prioritized. This frees up senior time for architecture decisions, complex problem-solving, and the high-value work that actually requires experience.&lt;/p>
&lt;p>But more importantly, juniors represent the institutional knowledge that walks out the door when that burned-out senior finally quits. And right now, we&amp;rsquo;re choosing short-term convenience over long-term sustainability.&lt;/p>
&lt;p>When I started in this field, there were 10 candidates per data engineering job listing. Now? You&amp;rsquo;re lucky to get 2.5. For context, web developers see 10 candidates per job. Marketing managers? Try 53.&lt;/p>
&lt;p>This isn&amp;rsquo;t a future problem. It&amp;rsquo;s a 2026 problem. And by 2030, projections suggest 10.5 million unfilled data and analytics positions globally.&lt;/p>
&lt;p>Your 2030 self is going to be really mad about this decision.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="ai-changes-everything-except-what-actually-matters">AI Changes Everything (Except What Actually Matters)&lt;/h2>
&lt;br>
&lt;p>And this is where many teams think AI will save them.&lt;/p>
&lt;p>A junior engineer on a team needed to build a data quality framework for their pipeline. In 2020, this would have meant three hours of my time walking through the approach, another two hours of their time implementing it, and probably a day of debugging edge cases.&lt;/p>
&lt;p>Instead, she spent 20 minutes with Claude. Described the problem, got three different approaches, validated the logic, tested the implementations, and asked for feedback on edge cases I hadn&amp;rsquo;t even thought to mention. By the time she pinged me, she had working code and specific questions about production deployment.&lt;/p>
&lt;p>This is the reality of AI in data engineering in 2026. It&amp;rsquo;s not replacing engineers. It&amp;rsquo;s fundamentally changing what &amp;ldquo;junior&amp;rdquo; means.&lt;/p>
&lt;p>GitHub Copilot has over 15 million users now. Ninety percent of Fortune 100 companies have adopted it. Developers report 51% faster coding speed—and that matches what I see in practice. The text-to-SQL market has matured to where straightforward queries just work. Tools like Vanna.AI and the native integrations in Databricks and Snowflake handle the routine stuff competently.&lt;/p>
&lt;p>But here&amp;rsquo;s what the adoption stats don&amp;rsquo;t tell you: AI is a force multiplier for experience, not a replacement for it.&lt;/p>
&lt;p>I&amp;rsquo;ve seen junior engineers use AI to cover gaps in their understanding—generating code they can&amp;rsquo;t debug, creating pipelines they can&amp;rsquo;t explain, building on top of patterns they don&amp;rsquo;t actually grasp. And I&amp;rsquo;ve seen senior engineers use it to remove friction and accelerate decisions they already know how to make.&lt;/p>
&lt;p>The difference matters. A lot.&lt;/p>
&lt;p>Some organizations now require 85% test coverage for AI-assisted code versus 70% for human-written code. They flag high-AI-content pull requests for additional security review. A few even implement &amp;ldquo;AI-free Fridays&amp;rdquo; to prevent skill atrophy. Because the risk isn&amp;rsquo;t that AI writes bad code—it&amp;rsquo;s that engineers stop learning how to recognize when it does.&lt;/p>
&lt;p>The Spider 2.0 benchmark—a standard test for enterprise-level SQL generation—is instructive here. GPT-4 solves only 6% of enterprise-level SQL questions. Six percent. Not because the models are bad, but because enterprise reality is messy. Tables with a thousand columns, sparse data that doesn&amp;rsquo;t match the schema documentation, business logic buried in five years of accumulated stored procedures that nobody fully understands anymore.&lt;/p>
&lt;p>AI is phenomenal at solving clean, well-defined problems. Enterprise data engineering is rarely clean or well-defined.&lt;/p>
&lt;h3 id="the-hive-mind-advantage-you-can-actually-use">The Hive Mind Advantage (You Can Actually Use)&lt;/h3>
&lt;p>Here&amp;rsquo;s something that doesn&amp;rsquo;t get talked about enough: when an AI learns something—like how to write performant SQL, or recognize a common anti-pattern—every instance of that AI learns it simultaneously.&lt;/p>
&lt;p>Compare that to human learning. If I figure out a better approach to incremental loads, I share it with my team. Maybe it spreads to 5-10 people. Maybe someone writes a blog post and it reaches a few hundred. But it takes &lt;em>years&lt;/em> for a best practice to become industry standard.&lt;/p>
&lt;p>AI doesn&amp;rsquo;t have that limitation. When Claude or GPT gets better at something, every developer using it gets better at it on the same day.&lt;/p>
&lt;p>This is where the real opportunity lies for 2026: not fighting against AI or using it as a crutch, but deliberately leveraging that collective learning capability:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Peer review assistance&lt;/strong>: AI can spot patterns across your entire codebase that no single engineer could hold in their head. It&amp;rsquo;s seen millions of pipelines fail in similar ways—use that knowledge.&lt;/li>
&lt;li>&lt;strong>Best practice enforcement&lt;/strong>: Standards that would require constant human vigilance can be automated. Style guides, naming conventions, common performance pitfalls—AI catches these at scale.&lt;/li>
&lt;li>&lt;strong>Accelerated learning&lt;/strong>: Juniors can ask &amp;ldquo;why is this pattern better?&amp;rdquo; and get explanations based on millions of examples (specific to your industry, or tapped into real work examples), not just what their one mentor happens to know. The AI has seen every permutation of every mistake.&lt;/li>
&lt;/ul>
&lt;p>The engineers who thrive won&amp;rsquo;t be the ones competing against AI. They&amp;rsquo;ll be the ones who figured out how to use that hive-mind capability to augment their own judgment.&lt;/p>
&lt;p>So yes, AI should be in your 2026 strategy. But if you&amp;rsquo;re counting on it to solve your junior engineer problem? You&amp;rsquo;re in for an expensive surprise. Because AI might help juniors be productive faster, but it doesn&amp;rsquo;t teach them judgment and critical thinking. And in data engineering, this is one of those soft skills that separates the engineers who ship reliable systems from the ones who generate incidents.&lt;/p>
&lt;p>The question isn&amp;rsquo;t &amp;ldquo;should we use AI&amp;rdquo;—of course you should. The question is &amp;ldquo;how do we use AI without creating a generation of engineers who can&amp;rsquo;t function without it?&amp;rdquo;&lt;/p>
&lt;p>I don&amp;rsquo;t have a perfect answer to that. But I know it starts with still hiring and training juniors, even though it&amp;rsquo;s harder and slower than we&amp;rsquo;d like.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="the-180000-mistake-hiding-in-your-architecture">The $180,000 Mistake Hiding in Your Architecture&lt;/h2>
&lt;br>
&lt;p>Here&amp;rsquo;s a story that&amp;rsquo;s going to sound familiar.&lt;/p>
&lt;p>I got asked to review a friends data platform that was spending a lot of compute. The team was frustrated. &amp;ldquo;We&amp;rsquo;re growing fast, costs keep climbing, and we can&amp;rsquo;t figure out where the money&amp;rsquo;s going.&amp;rdquo;&lt;/p>
&lt;p>So I looked at their queries. Ninety percent of them scanned less than 100MB of data.&lt;/p>
&lt;p>Not gigabytes. Not terabytes. Megabytes. The kind of data that fits comfortably in your laptop&amp;rsquo;s memory.&lt;/p>
&lt;p>They were running a distributed cloud data warehouse—with all the complexity, cost, and operational overhead that entails—to process files that could run on a Raspberry Pi.&lt;/p>
&lt;p>This is the emperor-has-no-clothes moment of 2026: most &amp;ldquo;big data&amp;rdquo; isn&amp;rsquo;t actually big.&lt;/p>
&lt;p>Jordan Tigani figured this out a few years ago. He&amp;rsquo;s the former Google BigQuery founding engineer, so he&amp;rsquo;s not some random person with an axe to grind. He looked at actual BigQuery usage patterns and found that 90% of queries process less than 100MB. Among instances with supposedly &amp;ldquo;big data,&amp;rdquo; 98% of queries scan less than 1TB.&lt;/p>
&lt;p>His conclusion: &amp;ldquo;The data cataclysm that had been predicted hasn&amp;rsquo;t come to pass. Data sizes may have gotten marginally larger, but hardware has gotten bigger at an even faster rate.&amp;rdquo;&lt;/p>
&lt;p>DuckDB is the practical realization of this insight. It&amp;rsquo;s an embedded columnar database that runs inside Python, R, or just the command line. No server required. No cluster management. When it runs out of memory, it automatically spills to disk. It natively handles Parquet, CSV, and JSON.&lt;/p>
&lt;p>For most data engineering work, this means you can develop locally without cloud dependencies, test rapidly in CI/CD without warehouse costs, and handle production workloads up to hundreds of gigabytes on a single machine.&lt;/p>
&lt;p>The cost implications are striking. After moving appropriate workloads to DuckDB, teams regularly see 80-90% cost reduction. One team processed pipeline work that was taking 8 hours and got it down to 8 minutes. On cheaper infrastructure.&lt;/p>
&lt;p>Polars complements this story nicely—a Rust-based DataFrame library that gives you programmatic workflows where DuckDB gives you SQL. Together, they represent a genuine paradigm shift from &amp;ldquo;scale out everything&amp;rdquo; to &amp;ldquo;right-size your architecture.&amp;rdquo;&lt;/p>
&lt;p>You still need distributed systems for genuinely big data, for complex concurrent workloads, for petabyte-scale scanning. But the skill that matters isn&amp;rsquo;t knowing DuckDB or Polars—tools change. The skill is architectural judgment: knowing when a single powerful machine beats a distributed cluster. Knowing when to optimize for cost versus scale versus simplicity.&lt;/p>
&lt;p>This is what separates senior engineers from junior ones. And it&amp;rsquo;s why AI can&amp;rsquo;t replace architectural judgment—the models are trained on a world where &amp;ldquo;big data&amp;rdquo; meant Hadoop clusters and distributed everything. They don&amp;rsquo;t know that world is ending.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-your-2026-planning-should-actually-address">What Your 2026 Planning Should Actually Address&lt;/h2>
&lt;br>
&lt;p>If you&amp;rsquo;re in planning meetings right now, here&amp;rsquo;s what&amp;rsquo;s probably on your list:&lt;/p>
&lt;ul>
&lt;li>Which AI tools to adopt&lt;/li>
&lt;li>Whether to migrate to a new data warehouse&lt;/li>
&lt;li>Cost optimization initiatives&lt;/li>
&lt;li>Team headcount requests&lt;/li>
&lt;/ul>
&lt;p>And here&amp;rsquo;s what should be on your list but probably isn&amp;rsquo;t:&lt;/p>
&lt;p>&lt;strong>First: How are we creating the next generation of engineers?&lt;/strong>&lt;/p>
&lt;p>Not &amp;ldquo;when we have time&amp;rdquo; or &amp;ldquo;once we&amp;rsquo;re fully staffed.&amp;rdquo; Now. Budget for at least one junior engineer per three seniors. Build mentorship into performance reviews. Dedicate senior time to teaching, and treat that as valuable work, not a distraction from &amp;ldquo;real&amp;rdquo; work.&lt;/p>
&lt;p>Your 2027 self will thank you. Your 2030 self will wonder why this wasn&amp;rsquo;t obvious to everyone.&lt;/p>
&lt;p>&lt;strong>Second: Where are we using distributed systems unnecessarily?&lt;/strong>&lt;/p>
&lt;p>Audit your infrastructure. Find the places where you&amp;rsquo;re using Spark to process files that fit in memory, or Snowflake for queries that scan megabytes. Not to eliminate distributed systems—they&amp;rsquo;re essential for genuinely big data—but to right-size your architecture.&lt;/p>
&lt;p>Every workload that moves from a distributed system to a well-tuned single machine is potentially 80-90% cost savings. That adds up fast.&lt;/p>
&lt;p>&lt;strong>Third: How are we integrating AI without creating skill atrophy?&lt;/strong>&lt;/p>
&lt;p>Use AI. Absolutely use it. But have a plan for ensuring your team still knows how to think without it. Maybe that&amp;rsquo;s higher test coverage requirements for AI-assisted code. Maybe it&amp;rsquo;s regular code reviews focused on understanding, not just correctness. Maybe it&amp;rsquo;s explicit training on recognizing when AI is confidently wrong.&lt;/p>
&lt;p>Whatever it is, don&amp;rsquo;t just adopt AI and hope for the best.&lt;/p>
&lt;p>&lt;strong>Fourth: What are we doing about retention?&lt;/strong>&lt;/p>
&lt;p>If 95% of data engineers are burned out and 70% are job-hunting, what makes you think yours are different? This isn&amp;rsquo;t about ping-pong tables or free snacks. It&amp;rsquo;s about sustainable workloads, clear career progression, meaningful work, and not being on-call for systems that could be more reliable with better architecture.&lt;/p>
&lt;p>Every senior engineer who leaves takes years of institutional knowledge with them. In a world where we&amp;rsquo;re not hiring juniors to replace them, that knowledge is gone permanently.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="the-2030-horizon-why-this-matters-now">The 2030 Horizon: Why This Matters Now&lt;/h2>
&lt;br>
&lt;p>Everything I&amp;rsquo;ve described isn&amp;rsquo;t just about surviving 2026. It&amp;rsquo;s about positioning for where this industry is headed.&lt;/p>
&lt;p>By 2030, projections suggest demand for data professionals will grow by 36%. The global AI market will hit approximately $2 trillion. And those 10.5 million unfilled positions I mentioned earlier? That&amp;rsquo;s the scale of the gap we&amp;rsquo;re creating right now, with every junior engineer we don&amp;rsquo;t hire and every senior we burn out.&lt;/p>
&lt;p>The consensus from industry analysts is clear: AI will transform data engineering workflows, but it won&amp;rsquo;t replace the humans who design, implement, and maintain those systems. What will change is the nature of the work.&lt;/p>
&lt;p>Data engineers will transition from technical executors to strategic leaders. The routine tasks—basic ETL, straightforward data cleansing, simple pipeline monitoring—will increasingly be handled by AI assistants. What remains is the work that requires judgment: understanding business context, making architectural trade-offs, ensuring data quality for AI applications, and building systems that are reliable at scale.&lt;/p>
&lt;p>Here&amp;rsquo;s the paradox that most people miss: AI applications are hungry for huge, well-prepared datasets. Every chatbot, every recommendation engine, every automated decision system needs data pipelines feeding it. The same AI revolution that automates some of our work multiplies the demand for the infrastructure we build.&lt;/p>
&lt;p>The question is whether you&amp;rsquo;ll have the team to build it.&lt;/p>
&lt;p>Organizations that invest in talent pipelines today—hiring juniors, retaining seniors, building mentorship cultures—will have the workforce to capture this opportunity. Organizations that don&amp;rsquo;t will be competing for an ever-shrinking pool of experienced engineers, paying premium salaries for people who could have been developed internally.&lt;/p>
&lt;p>The technology decisions you make in 2026 are reversible. Switch databases, change orchestrators, adopt new tools—these are tactical choices you can adjust. But the talent decisions compound. Every junior you don&amp;rsquo;t hire today is a mid-level engineer you won&amp;rsquo;t have in 2028 and a senior you won&amp;rsquo;t have in 2030.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-actually-matters">What Actually Matters&lt;/h2>
&lt;br>
&lt;p>The data engineering landscape in 2026 is simultaneously more accessible and more demanding than ever before. AI tools, managed services, and mature open source have lowered the barriers to building data systems. But the systems we need to build are more complex, the scale challenges are more varied, and the skills required to make good architectural decisions have broadened significantly.&lt;/p>
&lt;p>The teams that thrive in this environment—and position themselves for 2030—won&amp;rsquo;t be the ones with the fanciest tools or the biggest budgets. They&amp;rsquo;ll be the ones who:&lt;/p>
&lt;ul>
&lt;li>Build sustainable talent pipelines instead of fighting for a shrinking pool of senior engineers&lt;/li>
&lt;li>Right-size their architectures instead of defaulting to distributed systems for everything&lt;/li>
&lt;li>Use AI as a force multiplier—including leveraging its collective learning capability—instead of treating it as a crutch&lt;/li>
&lt;li>Create environments where people want to stay instead of burning through talent&lt;/li>
&lt;/ul>
&lt;p>None of this is easy. But it&amp;rsquo;s necessary.&lt;/p>
&lt;p>Because here&amp;rsquo;s the thing: the talent pipeline crisis, the cost optimization opportunities, the AI integration challenges—these aren&amp;rsquo;t going away. They&amp;rsquo;re getting worse. The organizations that address them proactively in 2026 will have a massive advantage. The ones that don&amp;rsquo;t will spend 2027 wondering why they can&amp;rsquo;t hire anyone, why their costs keep climbing, and why their best engineers keep leaving.&lt;/p>
&lt;p>The choice is yours. But choose soon.&lt;/p>
&lt;p>The biggest risk to your data platform in 2026 isn&amp;rsquo;t technology. It&amp;rsquo;s whether you&amp;rsquo;ll have anyone left who knows how to run it.&lt;/p>
&lt;p>And by 2030, everyone will wish they&amp;rsquo;d acted now.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Career Development</category><category>Technical Architecture</category><category>Strategy</category><category>Team Building</category><category>Cost Optimization</category><category>DuckDB</category><category>AI Tools</category><category>Career Planning</category><category>2026 Trends</category><category>Future of Work</category></item><item><title>The Guerrilla Guide to Data Engineering Interviews</title><link>https://ghostinthedata.info/posts/2026/2026-01-11-guerrilla-interviews/</link><pubDate>Sun, 11 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-11-guerrilla-interviews/</guid><author>Chris Hillman</author><description>A battle-tested guide to showcasing what you've actually built, not just what you've memorized. Because implementations speak louder than definitions.</description><content:encoded>&lt;h3 id="the-scenario-that-changes-everything">The Scenario That Changes Everything&lt;/h3>
&lt;br>
&lt;p>Picture this: You&amp;rsquo;re sitting in an interview room—or more likely these days, staring at a Zoom window with your carefully curated bookshelf background—and the interviewer asks you about data quality.&lt;/p>
&lt;p>&amp;ldquo;Tell me about your experience with data quality,&amp;rdquo; they say.&lt;/p>
&lt;p>You have two choices.&lt;/p>
&lt;p>&lt;strong>Choice A&lt;/strong>: &amp;ldquo;Data quality is really important in data engineering. It involves ensuring data is accurate, complete, consistent, and timely. I believe strongly in implementing data quality checks throughout the pipeline.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Choice B&lt;/strong>: &amp;ldquo;Last year, we were bleeding money. Our data wasn&amp;rsquo;t running on time, it wasn&amp;rsquo;t consistent we had a table that had a 12% duplicate rate—we only discovered this when the Head of department noticed and our spend per customer was mysteriously 12% higher than industry benchmarks. I implemented row-level assertions on the incoming CDC stream that caught duplicates before they hit the merge, then backfilled three months of historical data by running a deduplication job that prioritized records based on source system hierarchy and last-updated timestamps. Took two weeks. Cut the duplicate rate to 0.3%.&amp;rdquo;&lt;/p>
&lt;p>One of these answers gets you hired. The other gets you a polite &amp;ldquo;we&amp;rsquo;ll be in touch.&amp;rdquo;&lt;/p>
&lt;p>Here&amp;rsquo;s the thing about data engineering interviews: they&amp;rsquo;re not testing whether you &lt;em>know&lt;/em> things. Any idiot with ChatGPT can tell you what a slowly changing dimension is. What interviewers desperately want to know is whether you&amp;rsquo;ve actually &lt;em>built&lt;/em> things. Have you stood in the wreckage of a failed pipeline at 2 AM and figured out what went wrong? Have you stared at two conflicting requirements from two department heads and architected a solution that made both of them reasonably happy?&lt;/p>
&lt;p>This guide is about demonstrating that you&amp;rsquo;ve been in the trenches. Because that&amp;rsquo;s what separates the candidates who get offers from the candidates who get &amp;ldquo;we decided to move forward with other applicants.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-two-things-that-actually-matter">The Two Things That Actually Matter&lt;/h3>
&lt;br>
&lt;p>After nearly two decades of interviewing data engineers—and being interviewed plenty of times myself—I&amp;rsquo;ve come to believe that good data engineering candidates have exactly two qualities:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Smart&lt;/strong>, and&lt;/li>
&lt;li>&lt;strong>Get things done&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>That&amp;rsquo;s it. That&amp;rsquo;s the whole list.&lt;/p>
&lt;p>Sound overly simplistic? It&amp;rsquo;s not. These two qualities encompass everything an interviewer is actually trying to assess. Technical knowledge? That&amp;rsquo;s a component of smart. Communication skills? Part of getting things done. Problem-solving ability? Both.&lt;/p>
&lt;p>The beauty of this framework is that it helps you understand what interviewers are &lt;em>really&lt;/em> looking for beneath all those questions about window functions and star schemas.&lt;/p>
&lt;p>&lt;strong>People who are Smart but don&amp;rsquo;t Get Things Done&lt;/strong> often have impressive credentials but struggle to ship anything. They&amp;rsquo;re the ones who want to spend three weeks &amp;ldquo;properly&amp;rdquo; designing a data model for a proof-of-concept that needs to be done by Friday. They&amp;rsquo;ll tell you why your approach has theoretical limitations but won&amp;rsquo;t propose a practical alternative. In interviews, they can answer technical questions perfectly but give vague, abstract answers when asked about projects they&amp;rsquo;ve completed.&lt;/p>
&lt;p>&lt;strong>People who Get Things Done but aren&amp;rsquo;t Smart&lt;/strong> will build things that barely work, create technical debt that takes months to unwind, and make decisions that seem reasonable in the moment but reveal fundamental misunderstandings later. In interviews, they can talk enthusiastically about all the pipelines they&amp;rsquo;ve built but can&amp;rsquo;t explain &lt;em>why&lt;/em> they made specific technical choices.&lt;/p>
&lt;p>The magic happens when you have both. Smart people who get things done understand the theory, recognize when it matters and when it doesn&amp;rsquo;t, and ship production-quality solutions that their colleagues can maintain.&lt;/p>
&lt;p>Your job in an interview is to demonstrate that you&amp;rsquo;re one of these people.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="show-me-what-youve-built">Show Me What You&amp;rsquo;ve Built&lt;/h3>
&lt;br>
&lt;p>The single most important piece of advice I can give you is this: &lt;strong>Come to every interview with a mental catalog of specific implementations you&amp;rsquo;ve delivered.&lt;/strong>&lt;/p>
&lt;p>Not concepts you understand. Not technologies you&amp;rsquo;ve used. Not certificates you have attained. Actual problems you&amp;rsquo;ve solved, with specific details about what you did and why.&lt;/p>
&lt;p>I&amp;rsquo;ve seen brilliant engineers fumble interviews because when asked &amp;ldquo;Tell me about a time you implemented a backfill strategy,&amp;rdquo; they gave a generic answer about what backfills are. Meanwhile, mediocre engineers who happened to prepare good stories about their work sailed through.&lt;/p>
&lt;p>The stories matter. Let me show you why.&lt;/p>
&lt;h4 id="the-backfill-story">The Backfill Story&lt;/h4>
&lt;p>Every data engineer has done backfills. But here&amp;rsquo;s how most candidates talk about them:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Yes, I&amp;rsquo;ve done backfills. We had to reload historical data when we changed the schema. I wrote a script to process the data in batches.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>Okay. That tells me nothing about your judgment, your problem-solving, or your ability to handle complexity.&lt;/p>
&lt;p>Here&amp;rsquo;s what I want to hear:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Our analytics team realized they needed two years of historical data for a new churn model, but our pipeline had only been running for six months. The source system had the data, but it was in a different format—they&amp;rsquo;d migrated from Oracle to Postgres about 18 months prior. So I had three different data formats to reconcile: the current CDC stream, the Postgres historical data, and the Oracle archives.&lt;/em>&lt;/p>
&lt;p>&lt;em>I built a unified transformation layer that normalized all three formats, then created a DAG that processed the Oracle data first—about 400 million records—in weekly chunks to avoid overwhelming the warehouse. The tricky part was handling the transition period where we had data in both Oracle and Postgres with potential duplicates. I used a combination of source system timestamps and record hashes to deduplicate across the boundary.&lt;/em>&lt;/p>
&lt;p>&lt;em>The whole backfill took about four days to run, but the prep work took two weeks. The churn model team was able to start their training three weeks after the initial request.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>See the difference? The second answer demonstrates:&lt;/p>
&lt;ul>
&lt;li>Handling messy real-world complexity&lt;/li>
&lt;li>Breaking down a large problem into manageable pieces&lt;/li>
&lt;li>Making thoughtful tradeoffs (weekly chunks to avoid warehouse load)&lt;/li>
&lt;li>Understanding the business context (enabling the churn model team)&lt;/li>
&lt;li>Concrete numbers (400 million records, three weeks total)&lt;/li>
&lt;/ul>
&lt;p>That&amp;rsquo;s what gets you hired.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-technical-questions-they-actually-ask">The Technical Questions They Actually Ask&lt;/h3>
&lt;br>
&lt;p>Let&amp;rsquo;s get practical. Based on my experience on both sides of the table, here are the technical areas that come up most frequently in data engineering interviews—and more importantly, what interviewers are &lt;em>really&lt;/em> trying to learn from each question.&lt;/p>
&lt;h4 id="data-modeling-dont-just-know-it-defend-it">Data Modeling: Don&amp;rsquo;t Just Know It, Defend It&lt;/h4>
&lt;p>When someone asks about your approach to data modeling, they&amp;rsquo;re not testing whether you can define a star schema. They&amp;rsquo;re trying to understand your judgment.&lt;/p>
&lt;p>&lt;em>&amp;ldquo;We need to model customer order data. Walk me through your approach.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>The wrong answer starts rattling off dimensional modeling terminology. The right answer asks questions:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Before I design anything, I need to understand a few things. What are the primary queries this data needs to support? Are we optimizing for dashboard performance, ad-hoc analysis, or ML feature generation? What&amp;rsquo;s the data volume and growth rate? How frequently will it be updated? And who are the consumers—analysts writing SQL, a BI tool like Tableau, or data scientists in notebooks?&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>Only after understanding the requirements do you start talking about your design choices. And when you do, explain &lt;em>why&lt;/em>:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;For this use case, I&amp;rsquo;d go with a denormalized wide table rather than a traditional star schema. Your analysts are primarily doing ad-hoc analysis in notebooks, and they&amp;rsquo;ve told you query simplicity matters more than storage efficiency. A star schema would mean teaching everyone to join fact to dimension tables correctly—and in my experience, that&amp;rsquo;s where most analytical errors come from. The denormalized approach trades some storage cost for query simplicity and reduces the chance of incorrect joins.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This shows you understand that data modeling isn&amp;rsquo;t about following rules—it&amp;rsquo;s about making tradeoffs based on specific requirements.&lt;/p>
&lt;h4 id="scd-type-2-the-implementation-details-matter">SCD Type 2: The Implementation Details Matter&lt;/h4>
&lt;p>Every data engineer can explain what SCD Type 2 is. Few can explain how to actually implement it efficiently at scale.&lt;/p>
&lt;p>If you&amp;rsquo;re working with Delta Lake or Iceberg, this is where things get interesting:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Our customer dimension has about 50 million records and changes maybe 100,000 times per day. We&amp;rsquo;re on Delta Lake. How would you implement SCD Type 2?&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>Here&amp;rsquo;s where I want to see you&amp;rsquo;ve actually done this:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;With Delta Lake, I&amp;rsquo;d use the MERGE statement with a match condition on the natural key, but here&amp;rsquo;s where it gets nuanced. You can&amp;rsquo;t just do a simple merge because you need to both update existing records (set the end date) and insert new records (the current version) in the same operation.&lt;/em>&lt;/p>
&lt;p>&lt;em>The approach I&amp;rsquo;ve used is to structure the merge to match on natural key AND where the record is current (end_date is null), then on MATCHED and when there&amp;rsquo;s an actual change in the tracked columns, update the end_date to yesterday, and separately insert the new record. But that requires two passes—or you can use the multi-action MERGE syntax if your Delta version supports it.&lt;/em>&lt;/p>
&lt;p>&lt;em>The gotcha is handling late-arriving data. If you get yesterday&amp;rsquo;s changes after you&amp;rsquo;ve already processed today&amp;rsquo;s changes, your end_dates can get out of order. We solved this by including a logical sequence number in our CDC stream and processing changes in order within each merge batch.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This answer shows implementation experience, awareness of edge cases, and practical problem-solving.&lt;/p>
&lt;h4 id="merge-vs-delete-insert-know-when-to-use-each">Merge vs. Delete-Insert: Know When to Use Each&lt;/h4>
&lt;p>This comes up constantly, and the wrong answer is &amp;ldquo;I always use MERGE&amp;rdquo; or &amp;ldquo;I always use DELETE-INSERT.&amp;rdquo; The right answer is &amp;ldquo;it depends, and here&amp;rsquo;s how I decide.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>MERGE is better when:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>You need atomic upsert behavior&lt;/li>
&lt;li>You&amp;rsquo;re implementing SCD Type 2&lt;/li>
&lt;li>The update volume is small relative to table size&lt;/li>
&lt;li>Your warehouse has efficient MERGE support (most modern ones do)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>DELETE-INSERT is better when:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>You&amp;rsquo;re doing full partition replacement&lt;/li>
&lt;li>The &amp;ldquo;update&amp;rdquo; volume is close to 100% of the existing data&lt;/li>
&lt;li>You want simpler logic that&amp;rsquo;s easier to debug&lt;/li>
&lt;li>You need to handle deletes from the source system&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s how I&amp;rsquo;d explain it in an interview:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;In my experience, DELETE-INSERT is actually more common in analytics pipelines than people expect. When we refresh a daily partition, we don&amp;rsquo;t mess around trying to figure out what changed—we drop the partition and reload it. It&amp;rsquo;s simpler, it&amp;rsquo;s idempotent, and if something goes wrong, we just run it again.&lt;/em>&lt;/p>
&lt;p>&lt;em>MERGE is what we use for dimensions where we need SCD Type 2 behavior, or for fact tables where we&amp;rsquo;re getting late-arriving facts that need to be upserted into historical partitions. The key decision point is: do I actually need to identify and handle individual changes, or can I just reload the whole thing? Nine times out of ten, the answer is reload.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This shows practical wisdom about tradeoffs, not textbook regurgitation.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="problem-solving-questions-your-chance-to-shine">Problem-Solving Questions: Your Chance to Shine&lt;/h3>
&lt;br>
&lt;p>The questions I love most as an interviewer—and the ones you should love as a candidate—are the open-ended problem-solving questions. These are where you demonstrate that you&amp;rsquo;re smart &lt;em>and&lt;/em> get things done.&lt;/p>
&lt;p>&lt;em>&amp;ldquo;You&amp;rsquo;ve been asked to build a pipeline that ingests data from a new source system. Walk me through how you&amp;rsquo;d approach it.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This is a goldmine of an opportunity. Here&amp;rsquo;s how a strong candidate handles it:&lt;/p>
&lt;p>&lt;strong>Step 1: Ask clarifying questions (shows you understand context matters)&lt;/strong>&lt;/p>
&lt;p>&lt;em>&amp;ldquo;What&amp;rsquo;s the source system? Is it pushing data to us, or do we need to pull it? What&amp;rsquo;s the volume and velocity? What&amp;rsquo;s the latency requirement—does it need to be near-real-time, or is daily batch okay? Who are the consumers, and what questions are they trying to answer? Is there existing documentation, or do we need to do discovery?&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>&lt;strong>Step 2: Outline your approach at a high level (shows you can structure complex problems)&lt;/strong>&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Assuming this is a reasonably standard scenario—a REST API that we need to poll daily—here&amp;rsquo;s my approach. First, I&amp;rsquo;d spend time understanding the data. What entities are exposed? What are the relationships? What&amp;rsquo;s the grain? Are there any gotchas like soft deletes or non-standard timestamp formats?&lt;/em>&lt;/p>
&lt;p>&lt;em>Then I&amp;rsquo;d design a landing zone—probably a staging schema with minimal transformation, just enough to make the data queryable. From there, I&amp;rsquo;d build the transformation layer to reshape it into our target schema, applying data quality checks along the way.&lt;/em>&lt;/p>
&lt;p>&lt;em>Finally, I&amp;rsquo;d implement monitoring: row counts, schema change detection, and anomaly alerts for unexpected patterns.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>&lt;strong>Step 3: Dive into specific challenges (shows depth of experience)&lt;/strong>&lt;/p>
&lt;p>&lt;em>&amp;ldquo;The piece that often trips people up is handling incremental loads with APIs that don&amp;rsquo;t support proper change tracking. If the API doesn&amp;rsquo;t give you a reliable modified_timestamp, you might need to do full loads and then diff against your existing data—which gets expensive fast. I&amp;rsquo;ve handled this by implementing a hash-based change detection system where we store a hash of each record and only process records whose hash has changed.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>&lt;strong>Step 4: Discuss what could go wrong (shows you&amp;rsquo;ve been burned before)&lt;/strong>&lt;/p>
&lt;p>&lt;em>&amp;ldquo;The things I&amp;rsquo;d watch out for: API rate limits that could cause us to fall behind, schema changes from the source system that break our transformations, and timestamp timezone issues—I&amp;rsquo;ve been bitten by that one more times than I&amp;rsquo;d like to admit.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This kind of answer demonstrates everything an interviewer is looking for: structured thinking, technical depth, practical experience, and awareness of edge cases.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-incident-management-question">The Incident Management Question&lt;/h3>
&lt;br>
&lt;p>At some point, you&amp;rsquo;ll be asked about handling production incidents. This is where your war stories come in handy.&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Tell me about a time you had to debug a production data issue.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>Here&amp;rsquo;s the thing: everyone has these stories. The difference between a good answer and a great answer is how you structure it.&lt;/p>
&lt;p>&lt;strong>The Great Answer Structure:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>The alert&lt;/strong>: How did you find out something was wrong?&lt;/li>
&lt;li>&lt;strong>The triage&lt;/strong>: How did you assess the severity and scope?&lt;/li>
&lt;li>&lt;strong>The investigation&lt;/strong>: How did you identify the root cause?&lt;/li>
&lt;li>&lt;strong>The fix&lt;/strong>: What did you do to resolve it?&lt;/li>
&lt;li>&lt;strong>The prevention&lt;/strong>: What did you do to prevent it from happening again?&lt;/li>
&lt;/ol>
&lt;p>Here&amp;rsquo;s an example:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;We got an alert at 9 PM on a Friday—because of course it was Friday—that our customer metrics dashboard was showing a 30% drop in active users. At first, I thought it might be a real business event, but the drop was too sudden and too large.&lt;/em>&lt;/p>
&lt;p>&lt;em>I started with the basics: checked the pipeline run logs, all green. Checked row counts in the metrics table, normal. Checked upstream tables, normal. Nothing obviously broken.&lt;/em>&lt;/p>
&lt;p>&lt;em>Then I looked at the actual data. Our active user count was filtering by &amp;rsquo;last_activity_date &amp;gt;= current_date - 7&amp;rsquo;. Turns out, a schema change in the source system had changed the last_activity_date column from a timestamp to a date-with-timezone, and our transformation was truncating it incorrectly. Users in certain timezones were getting their dates shifted by a day, which pushed them outside the 7-day window.&lt;/em>&lt;/p>
&lt;p>&lt;em>The immediate fix was straightforward—I adjusted the timezone handling in the transformation and backfilled the last two weeks of data. Took about an hour.&lt;/em>&lt;/p>
&lt;p>&lt;em>But the prevention was the interesting part. I added explicit timezone assertions to our data quality framework. Any time we ingest a timestamp column, we now validate that the timezone handling matches our expectations. We&amp;rsquo;ve caught three similar issues since then before they hit production.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This answer hits all the marks: structured approach, technical depth, practical resolution, and systemic improvement.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-ai-and-llm-question">The AI and LLM Question&lt;/h3>
&lt;br>
&lt;p>If you&amp;rsquo;re interviewing in 2026, you &lt;em>will&lt;/em> be asked about AI. How you answer tells the interviewer a lot about whether you&amp;rsquo;re keeping current.&lt;/p>
&lt;p>&lt;em>&amp;ldquo;How have you used AI or LLMs in your data engineering work?&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>The weak answer is &amp;ldquo;I&amp;rsquo;ve experimented with ChatGPT for writing queries.&amp;rdquo;&lt;/p>
&lt;p>The strong answer shows you&amp;rsquo;ve actually integrated AI into production workflows:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;We&amp;rsquo;ve implemented LLM-assisted data classification in our pipeline. We have a data catalog with about 3,000 tables, and maintaining accurate tags for sensitivity, domain, and data type was a nightmare—the manual process was always out of date.&lt;/em>&lt;/p>
&lt;p>&lt;em>I built a service that uses Claude&amp;rsquo;s API to analyze table schemas and sample data, then suggests classifications. It&amp;rsquo;s not fully automated—we have a human review step—but it reduced the time to classify a new table from 20 minutes of manual analysis to about 2 minutes of review.&lt;/em>&lt;/p>
&lt;p>&lt;em>The tricky part was prompt engineering. The first version was too aggressive with PII classification—it was flagging everything with &amp;lsquo;ID&amp;rsquo; in the name as personally identifiable. We refined the prompts to include context about our specific data domain and examples of what we consider PII versus internal identifiers.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This shows practical application, awareness of limitations (human review), and iteration based on real-world feedback.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="building-experience-in-areas-you-dont-know">Building Experience in Areas You Don&amp;rsquo;t Know&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s a question I hear all the time: &amp;ldquo;What if I don&amp;rsquo;t have experience with [Delta Lake / Iceberg / dbt / whatever technology they&amp;rsquo;re asking about]?&amp;rdquo;&lt;/p>
&lt;p>First, be honest. Don&amp;rsquo;t claim experience you don&amp;rsquo;t have—good interviewers will expose this within two follow-up questions.&lt;/p>
&lt;p>Second, &lt;strong>go build something&lt;/strong>. You can spin up a personal project in a weekend that gives you legitimate hands-on experience.&lt;/p>
&lt;p>Want to learn Delta Lake? Create a databricks community edition account, load some public dataset, and implement a basic SCD Type 2 merge. Want to learn dbt? Fork the jaffle_shop demo project and extend it with some real transformations. Want to understand data quality at scale? Implement Great Expectations on one of your side projects.&lt;/p>
&lt;p>The beauty of data engineering is that the tools are mostly free for small-scale learning. You don&amp;rsquo;t need a $50,000/month Snowflake instance to learn Snowflake—you need the free trial and a CSV file.&lt;/p>
&lt;p>When you interview, you can honestly say: &amp;ldquo;I haven&amp;rsquo;t used Delta Lake in production, but I&amp;rsquo;ve built a personal project that implements [specific thing]. Here&amp;rsquo;s what I learned, and here&amp;rsquo;s how I&amp;rsquo;d apply it at scale.&amp;rdquo;&lt;/p>
&lt;p>That&amp;rsquo;s infinitely better than &amp;ldquo;I&amp;rsquo;ve heard of Delta Lake but haven&amp;rsquo;t used it.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-questions-you-should-ask-them">The Questions You Should Ask Them&lt;/h3>
&lt;br>
&lt;p>At the end of every interview, you&amp;rsquo;ll be asked &amp;ldquo;Do you have any questions for us?&amp;rdquo; This isn&amp;rsquo;t a formality—it&amp;rsquo;s a real opportunity to demonstrate your sophistication as a data engineer.&lt;/p>
&lt;p>Bad questions:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;What&amp;rsquo;s the work-life balance like?&amp;rdquo; (Important, but not what shows you&amp;rsquo;re a great candidate)&lt;/li>
&lt;li>&amp;ldquo;What technologies do you use?&amp;rdquo; (You should have researched this already)&lt;/li>
&lt;li>&amp;ldquo;What&amp;rsquo;s the career growth path?&amp;rdquo; (Reasonable, but generic)&lt;/li>
&lt;/ul>
&lt;p>Great questions:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;What&amp;rsquo;s the biggest data quality challenge you&amp;rsquo;re facing right now, and what&amp;rsquo;s your current approach to solving it?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Walk me through your deployment process for a new data pipeline—from development to production.&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How do you handle schema evolution in your source systems? Is it a significant pain point?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What&amp;rsquo;s the split between building new pipelines versus maintaining existing ones?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;If I joined, what would success look like in the first 90 days?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>These questions show you understand real data engineering challenges and you&amp;rsquo;re thinking about how you&amp;rsquo;d actually do the job, not just whether you want it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-meta-game-time-management-during-interviews">The Meta-Game: Time Management During Interviews&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s something nobody tells you about interviews: time management matters as much as your answers.&lt;/p>
&lt;p>If you spend 15 minutes on the first whiteboard question and only have 5 minutes for the next two, you&amp;rsquo;ve failed—even if your first answer was brilliant. Interviewers have a checklist of things they need to assess, and if you don&amp;rsquo;t give them enough data points, you&amp;rsquo;ll get a &amp;ldquo;no hire&amp;rdquo; simply because they couldn&amp;rsquo;t tell.&lt;/p>
&lt;p>&lt;strong>The Rule of Thirds:&lt;/strong>&lt;/p>
&lt;p>For a 60-minute technical interview:&lt;/p>
&lt;ul>
&lt;li>First 20 minutes: The introductory/behavioral question. Don&amp;rsquo;t ramble. Get your point across clearly and move on.&lt;/li>
&lt;li>Middle 30 minutes: The core technical assessment. This is where you prove your skills. Spend your time wisely here.&lt;/li>
&lt;li>Final 10 minutes: Wrap-up, your questions, selling you on the role.&lt;/li>
&lt;/ul>
&lt;p>If you find yourself 25 minutes into a detailed answer about your architecture philosophy and haven&amp;rsquo;t written any code yet, you&amp;rsquo;re in trouble. Learn to recognize when you&amp;rsquo;ve made your point and need to move on.&lt;/p>
&lt;p>&lt;strong>Breaking Down Problems:&lt;/strong>&lt;/p>
&lt;p>When faced with a complex technical question, don&amp;rsquo;t start writing code immediately. Spend 2-3 minutes structuring your approach out loud:&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Okay, let me break this down. There are three main pieces here: the ingestion, the transformation, and the output. For the ingestion, I need to handle [X]. For transformation, the key challenge is [Y]. Let me start with the ingestion piece, since that&amp;rsquo;s the foundation.&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>This shows structured thinking and gives the interviewer a roadmap of what you&amp;rsquo;re going to do. If you run out of time, they at least know you understood the full scope.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-uncomfortable-truth-about-interviews">The Uncomfortable Truth About Interviews&lt;/h3>
&lt;br>
&lt;p>I&amp;rsquo;m going to tell you something that might be unsettling: &lt;strong>interviews are imperfect signals&lt;/strong>.&lt;/p>
&lt;p>Smart, talented people fail interviews all the time. Mediocre people sometimes pass them. The best interviewers are wrong about 20-30% of the time.&lt;/p>
&lt;p>This is why you shouldn&amp;rsquo;t let a failed interview devastate you, and you shouldn&amp;rsquo;t let a passed interview make you complacent. The skills that make you successful in interviews are related to—but not identical to—the skills that make you successful in the job.&lt;/p>
&lt;p>What you &lt;em>can&lt;/em> control:&lt;/p>
&lt;ul>
&lt;li>Your preparation&lt;/li>
&lt;li>Your catalog of project stories&lt;/li>
&lt;li>Your ability to explain technical concepts clearly&lt;/li>
&lt;li>Your structured approach to problem-solving&lt;/li>
&lt;li>Your honesty about what you know and don&amp;rsquo;t know&lt;/li>
&lt;/ul>
&lt;p>What you &lt;em>can&amp;rsquo;t&lt;/em> control:&lt;/p>
&lt;ul>
&lt;li>Whether the interviewer is having a bad day&lt;/li>
&lt;li>Whether they&amp;rsquo;re biased toward a technology you don&amp;rsquo;t know&lt;/li>
&lt;li>Whether they&amp;rsquo;ve already decided to hire an internal candidate&lt;/li>
&lt;li>Whether you remind them of someone they didn&amp;rsquo;t like in their last job&lt;/li>
&lt;/ul>
&lt;p>Do everything you can to control the controllables. Then recognize that sometimes it just doesn&amp;rsquo;t work out, and that&amp;rsquo;s not a reflection of your worth as a data engineer.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="your-pre-interview-checklist">Your Pre-Interview Checklist&lt;/h3>
&lt;br>
&lt;p>Before any data engineering interview, make sure you can speak confidently about:&lt;/p>
&lt;p>&lt;strong>At least three implementation stories&lt;/strong> covering different areas: one about pipeline building, one about problem-solving/debugging, one about data modeling or architecture decisions.&lt;/p>
&lt;p>&lt;strong>Your approach to data quality&lt;/strong>: What checks you implement, how you handle failures, how you balance coverage with performance.&lt;/p>
&lt;p>&lt;strong>Tradeoff decisions&lt;/strong>: When to use batch vs. streaming, when to denormalize vs. normalize, when to merge vs. delete-insert, when to build vs. buy.&lt;/p>
&lt;p>&lt;strong>Your technical fundamentals&lt;/strong>: Window functions in SQL, how you handle slowly changing dimensions, your experience with your primary orchestration tool (Airflow, Dagster, dbt).&lt;/p>
&lt;p>&lt;strong>Technology-specific depth&lt;/strong>: Whatever platforms they use (Databricks, Snowflake, BigQuery), have at least surface-level familiarity and one or two interesting insights.&lt;/p>
&lt;p>&lt;strong>Questions for them&lt;/strong>: At least three substantive questions that show you&amp;rsquo;re thinking about the actual work.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-final-word">The Final Word&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s what I&amp;rsquo;ve learned after interviewing hundreds of data engineers: the candidates who get hired aren&amp;rsquo;t necessarily the ones who know the most. They&amp;rsquo;re the ones who can demonstrate that they&amp;rsquo;ve taken on hard problems and found practical solutions.&lt;/p>
&lt;p>Knowledge without execution is academic. Execution without knowledge is dangerous. The combination is rare and valuable.&lt;/p>
&lt;p>Your job in an interview is to prove you have both.&lt;/p>
&lt;p>So go catalog your victories. Remember the gnarly problems you solved, the architectural decisions you made, the production fires you put out. Write them down if you need to. Practice telling those stories until they flow naturally.&lt;/p>
&lt;p>Because in the end, data engineering interviews aren&amp;rsquo;t about reciting definitions or demonstrating mastery of every possible technology. They&amp;rsquo;re about convincing a group of smart people that if they hire you, things will get built, problems will get solved, and the pipelines will run.&lt;/p>
&lt;p>Everything else is details.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Career Development</category><category>Data Engineering</category><category>Interviews</category><category>Interviews</category><category>Career Growth</category><category>Technical Assessment</category><category>SQL</category><category>Data Modeling</category><category>Problem Solving</category><category>Delta Lake</category><category>dbt</category><category>Data Quality</category></item><item><title>Why Your Ideas Die in Planning Meetings</title><link>https://ghostinthedata.info/posts/2026/2026-01-07-brainstorming/</link><pubDate>Wed, 07 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-07-brainstorming/</guid><author>Chris Hillman</author><description>The psychology behind why data teams generate better solutions together than in isolation—and how asking for 'all ideas' instead of 'best ideas' unlocks the innovation your team actually needs.</description><content:encoded>&lt;h2 id="the-silence-that-kills-good-ideas">The silence that kills good ideas&lt;/h2>
&lt;br>
&lt;p>One morning, I sat in yet another meeting where we just spent two weeks backfilling a table then we found it was riddled with issues with the data. Even if we resolve the issue, it would then be another 2 weeks to backfill the data, there has to be a better way.&lt;/p>
&lt;p>&amp;ldquo;So, what do we think? Give me your best ideas for tackling this.&amp;rdquo;&lt;/p>
&lt;p>Silence.&lt;/p>
&lt;p>Two senior engineers stared at their laptops. One analyst started typing what looked suspiciously like Microsoft Teams messages. I knew for a fact that at least two people in that room had brilliant, half-formed thoughts about the problem. I&amp;rsquo;d heard them discussing it the day before.&lt;/p>
&lt;p>But in this moment? Nothing.&lt;/p>
&lt;p>This pattern repeats in data teams everywhere, and it&amp;rsquo;s not because people lack ideas. It&amp;rsquo;s because the way we ask for ideas practically guarantees we&amp;rsquo;ll never hear the good ones. The research on collaborative ideation reveals something that should fundamentally change how leaders run their teams: &lt;strong>the difference between &amp;ldquo;give me your best ideas&amp;rdquo; and &amp;ldquo;give me all your ideas&amp;rdquo; isn&amp;rsquo;t just semantic—it&amp;rsquo;s the difference between innovation and stagnation.&lt;/strong>&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-psychological-safety-actually-means-for-data-teams">What psychological safety actually means for data teams&lt;/h2>
&lt;br>
&lt;p>Amy Edmondson&amp;rsquo;s research at Harvard Business School established that psychological safety—&amp;ldquo;a shared belief that the team is safe for interpersonal risk taking&amp;rdquo;—is the essential precondition for teams to function effectively. But here&amp;rsquo;s what made her findings particularly striking: in a study of 51 work teams, she discovered that &lt;strong>team confidence alone didn&amp;rsquo;t predict learning behaviors when you controlled for safety.&lt;/strong>&lt;/p>
&lt;p>People need to feel that speaking up won&amp;rsquo;t damage their standing before their capability matters.&lt;/p>
&lt;p>This has profound implications.&lt;/p>
&lt;p>Think about your last sprint planning meeting. Who did most of the talking? If you&amp;rsquo;re like most organizations, it was probably the most senior person in the room—the staff engineer, the principal data scientist, or the engineering manager. Everyone else contributed sporadically, if at all.&lt;/p>
&lt;p>But, that senior person doesn&amp;rsquo;t have all the answers. The analyst who joined three months ago might have spotted a pattern in the data that the ten-year veteran missed because they&amp;rsquo;re looking at it with fresh eyes. The data engineer who&amp;rsquo;s quiet in meetings might have implemented the exact solution we need at their previous company. The junior scientist might have read a recent paper that completely reframes the problem.&lt;/p>
&lt;p>&lt;strong>You&amp;rsquo;re not hearing any of this because they&amp;rsquo;re doing what Edmondson calls the &amp;ldquo;interpersonal risk calculus&amp;rdquo;—silently weighing whether the perceived benefits of speaking up outweigh the risks of looking incompetent, being rejected, or appearing ignorant.&lt;/strong>&lt;/p>
&lt;p>When safety is low, self-protective filtering dominates. Only &amp;ldquo;safe,&amp;rdquo; conventional ideas get shared. The unconventional, half-formed, potentially breakthrough thoughts stay locked in people&amp;rsquo;s heads.&lt;/p>
&lt;p>Kerry Patterson&amp;rsquo;s research on crucial conversations provides a useful framework for understanding what&amp;rsquo;s happening here. He calls it the &lt;strong>Pool of Shared Meaning&lt;/strong>—the collective understanding that emerges when people openly share their views. When everyone contributes their complete thinking, the pool is full and decisions are stronger. When safety is low and people self-censor, the pool stays shallow.&lt;/p>
&lt;p>In data teams, this shows up constantly. Someone notices that our data quality problems stem from misaligned incentives between product and engineering, but sharing that observation feels risky—it implies criticism of another team. Someone else has experience with a similar architecture problem at their previous company, but they&amp;rsquo;re only three months in and worry about seeming presumptuous. A third person has an unconventional idea that might actually work, but it would require explaining a complex technical concept and they&amp;rsquo;re not sure they can articulate it clearly under pressure.&lt;/p>
&lt;p>Each person performs their own silent risk calculus and decides to withhold. The pool stays shallow. The meeting ends with only the safest, most obvious ideas on the table. And the best solution—which probably required combining insights from all three of those people—never emerges.&lt;/p>
&lt;p>I&amp;rsquo;ve seen this play out. An engineer might notice that the root cause of data quality issues isn&amp;rsquo;t technical at all—it&amp;rsquo;s that product managers keep requesting &amp;ldquo;quick one-off reports&amp;rdquo; that bypass all our governance processes. But suggesting that we need to have a difficult conversation with product leadership? That requires tremendous psychological safety. Easier to just say &amp;ldquo;we should add more validation checks to the pipeline&amp;rdquo; and move on.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="why-give-me-your-best-ideas-is-the-worst-possible-question">Why &amp;ldquo;give me your best ideas&amp;rdquo; is the worst possible question&lt;/h2>
&lt;br>
&lt;p>Here&amp;rsquo;s something that surprised me when I first encountered the research: &lt;strong>asking people for their &amp;ldquo;best&amp;rdquo; ideas actually produces worse outcomes than asking for &amp;ldquo;all&amp;rdquo; ideas.&lt;/strong>&lt;/p>
&lt;p>It seems counterintuitive. Surely asking for best ideas filters out the noise and gets straight to quality, right?&lt;/p>
&lt;p>Wrong.&lt;/p>
&lt;p>When you ask for &amp;ldquo;best ideas,&amp;rdquo; you trigger what psychologists call evaluation apprehension. People must judge their own ideas before sharing them, which activates fear of judgment. In studies, participants in high-apprehension conditions generated significantly fewer ideas (about 21 ideas on average) compared to low-apprehension conditions (about 33 ideas)—and explored fewer idea categories entirely.&lt;/p>
&lt;p>The request &amp;ldquo;give me your best ideas&amp;rdquo; does several harmful things simultaneously:&lt;/p>
&lt;p>First, it forces people to evaluate while they&amp;rsquo;re generating. This is cognitively expensive. Part of their mental resources are devoted to thinking up solutions, while another part is busy playing judge and jury on those solutions before they&amp;rsquo;re even fully formed.&lt;/p>
&lt;p>Second, it signals that quality judgment happens before sharing, which means people assume incomplete or uncertain ideas aren&amp;rsquo;t welcome. The very thoughts that might serve as stepping stones to breakthrough insights get filtered out before they reach the group.&lt;/p>
&lt;p>Third, it creates a high-stakes environment. If you&amp;rsquo;re only sharing your &amp;ldquo;best&amp;rdquo; ideas, you&amp;rsquo;re putting your judgment on display. What if the group disagrees? What if someone senior thinks your &amp;ldquo;best&amp;rdquo; idea is actually terrible?&lt;/p>
&lt;p>There&amp;rsquo;s a psychological mechanism at work here that Daniel Pink calls the Sawyer Effect, from Mark Twain&amp;rsquo;s observation that &amp;ldquo;work consists of whatever a body is obliged to do, and play consists of whatever a body is not obliged to do.&amp;rdquo; Pink&amp;rsquo;s research on motivation shows that external evaluation can transform inherently interesting tasks into drudgery—turning play into work.&lt;/p>
&lt;p>When you ask for &amp;ldquo;best ideas,&amp;rdquo; you&amp;rsquo;re asking people to apply external evaluation to their own thinking. What was potentially an enjoyable creative exercise—&amp;ldquo;how might we solve this interesting problem?&amp;quot;—becomes a performance task with judgment attached. The intrinsic motivation to explore solutions gets crowded out by the extrinsic pressure to look competent.&lt;/p>
&lt;p>This is particularly damaging for the kind of creative problem-solving data teams do daily. We&amp;rsquo;re not assembly line workers performing routine tasks—we&amp;rsquo;re solving novel problems that require genuine creativity. The Federal Reserve actually studied this: they found that offering higher rewards for cognitive tasks didn&amp;rsquo;t improve performance. In fact, those offered the highest bonuses performed the worst. When stakes are high and judgment is involved, external pressure actively harms the kind of thinking we need.&lt;/p>
&lt;p>I seen this play out various times. The team lead asked everyone to &amp;ldquo;bring your best approach for data migration from our monolithic data warehouse to a lake house architecture.&amp;rdquo; People showed up to the next meeting with carefully prepared ideas, each advocating for their single preferred solution. Majority of them are the same, but it doesn&amp;rsquo;t look at the problem differently.&lt;/p>
&lt;p>The meeting turned into positional warfare. People defended their chosen approach, pointed out flaws in alternatives, and nobody budged. We spent three weeks in analysis paralysis before a decision to end the stalemate occured.&lt;/p>
&lt;p>Contrast that with, instead of asking for &amp;ldquo;best approaches,&amp;rdquo; I asked the team to generate every possible way we could solve the problem, no matter how impractical or half-baked. &amp;ldquo;Give me all your ideas—the good, the bad, the weird, the incomplete.&amp;rdquo;&lt;/p>
&lt;p>We filled three whiteboards. Someone suggested new tech. Someone proposed a datamesh approach. One engineer threw out &amp;ldquo;what if we just stayed with the data warehouse and optimized it better?&amp;rdquo; Another suggested a serverless approach using AWS Glue and Athena. Someone jokingly said &amp;ldquo;rewrite everything with heavy partitioning.&amp;rdquo;&lt;/p>
&lt;p>That last &amp;ldquo;joke&amp;rdquo; actually sparked a serious conversation. Turned out our issues weren&amp;rsquo;t really about warehouse versus lake house at all—they were about query patterns and data organization. We ended up with a solution that kept our existing warehouse but completely restructured how we modeled data.&lt;/p>
&lt;p>&lt;strong>We never would have arrived at that solution if people had been filtering for their &amp;ldquo;best&amp;rdquo; ideas before speaking.&lt;/strong>&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="quantity-unlocks-quality-through-cognitive-mechanisms-you-can-actually-use">Quantity unlocks quality through cognitive mechanisms you can actually use&lt;/h2>
&lt;br>
&lt;p>The counterintuitive finding that quantity breeds quality is now well-established in cognitive science, and it has immediate practical implications for how data teams approach problem-solving.&lt;/p>
&lt;p>The mechanism works like this: when you start generating ideas, your brain naturally accesses the most available associations first—which are typically the most obvious and conventional solutions. The really original ideas live in more remote semantic connections that only get accessed after you&amp;rsquo;ve exhausted the obvious options.&lt;/p>
&lt;p>Research by Nijstad and Stroebe explains this through their Search for Ideas in Associative Memory (SIAM) model. Idea generation happens in two stages: first, a search cue activates a knowledge structure in long-term memory; then, features of that structure combine to produce specific ideas. The crucial insight is that initial ideas access what&amp;rsquo;s most readily available. Later ideas, after depleting these obvious options, require exploring more unusual mental paths.&lt;/p>
&lt;p>This produces what researchers call the &lt;strong>serial order effect&lt;/strong>: as people generate more ideas, later ideas become increasingly original.&lt;/p>
&lt;p>I use this principle deliberately now when working with teams. Here&amp;rsquo;s a practical example:&lt;/p>
&lt;p>When we were trying to improve our data backfill mechanism, I asked the team to come up with 15 different approaches for how we could optimise or improve how we backfill tables with data. The idea was to short fuse and increase the ability to understand issues, before they reached production. Not &amp;ldquo;a few good approaches&amp;rdquo;—specifically 15.&lt;/p>
&lt;p>The first five were predictable: create a cyclic job that runs over each date, another was, load a week of data then run tests.&lt;/p>
&lt;p>Ideas six through ten got more interesting: create a insert only table to be able to batch load data hisotircal data in one go, create synthetic test data that exercises a longer time span of data.&lt;/p>
&lt;p>Ideas eleven through fifteen got genuinely creative: To build on the above, loading data as insert only, but then compressing timelines, and virtually end dating the records.&lt;/p>
&lt;p>We ended up implementing a combination where we rebuilt the history doing that insert spine of data, and then compressing the timeline (where previous and current records row hash is identical)&lt;/p>
&lt;p>What was amazing here is, not only did running this script take less time, 1hr to execute. However the compute cost of running 1 query versus throusands of queries cost alot less in compute as well.&lt;/p>
&lt;p>&lt;strong>This works because high quantity targets provide psychological permission.&lt;/strong> When the goal is 15 ideas, it legitimizes including uncertain, incomplete, or seemingly foolish contributions. Nobody expects all 15 to be brilliant—which paradoxically enables some of them to be exactly that.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;hr>
&lt;h2 id="the-ikea-effect-explains-why-imposed-solutions-fail">The IKEA effect explains why imposed solutions fail&lt;/h2>
&lt;br>
&lt;p>Here&amp;rsquo;s something you already know intuitively: people are far more committed to solutions they helped create than solutions handed to them. What you might not know is just how powerful this effect is, or the psychological mechanisms that drive it.&lt;/p>
&lt;p>Research calls this the IKEA effect—consumers place disproportionately high value on products they partially created, even when those products are objectively inferior to professionally made alternatives. In studies, people valued self-assembled furniture about 63% higher than identical pre-assembled items.&lt;/p>
&lt;p>The mechanism is what psychologists call psychological ownership. When you invest effort in creating something, it becomes part of your extended self. The idea is no longer &amp;ldquo;the company&amp;rsquo;s data quality initiative&amp;rdquo;—it&amp;rsquo;s &amp;ldquo;our data quality initiative that we designed together.&amp;rdquo;&lt;/p>
&lt;p>This explains patterns I see repeatedly in organizations:&lt;/p>
&lt;p>A new senior leaders comes in and mandates that all teams must adopt a particular framework. Teams comply minimally, work around it when possible, and resist at every opportunity. Two years later, the senior leaders leaves, and the framework dies immediately.&lt;/p>
&lt;p>Contrast that with a different organization where the data team spent three months collaboratively designing their own framework. They held working sessions. They debated principles. They argued about where to draw boundaries between centralized control and team autonomy. The resulting framework wasn&amp;rsquo;t objectively better than the mandated one—arguably it was less sophisticated. But adoption was near-universal because people felt ownership of it.&lt;/p>
&lt;p>&lt;strong>People don&amp;rsquo;t just accept ideas they helped create—they champion them.&lt;/strong>&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-actually-works-practical-patterns-for-data-teams">What actually works: practical patterns for data teams&lt;/h2>
&lt;br>
&lt;p>After reviewing all this research and reflecting on what I&amp;rsquo;ve observed across multiple data teams, certain patterns emerge consistently.&lt;/p>
&lt;p>&lt;strong>Create explicit psychological safety through leader behavior.&lt;/strong> This isn&amp;rsquo;t about being nice or avoiding difficult conversations. It&amp;rsquo;s about modeling specific behaviors: openly acknowledging your own mistakes, asking for team input in group settings and responding positively even to challenging questions, treating failures as learning opportunities rather than blame opportunities.&lt;/p>
&lt;p>In practice, this means: when a data pipeline fails, your first question is &amp;ldquo;what can we learn?&amp;rdquo; not &amp;ldquo;who&amp;rsquo;s responsible?&amp;rdquo; When someone junior proposes an idea that won&amp;rsquo;t work, you explore why they thought it might before explaining the problems. When you make a decision that turns out wrong, you say so explicitly rather than quietly changing direction.&lt;/p>
&lt;p>&lt;strong>Structure ideation sessions to separate generation from evaluation.&lt;/strong> Set high quantity targets, ask for &amp;ldquo;all ideas&amp;rdquo; not &amp;ldquo;best ideas,&amp;rdquo; use anonymous contribution methods when appropriate, actively suppress evaluation during the generation phase—even subtle head shakes or skeptical looks kill psychological safety.&lt;/p>
&lt;p>&lt;strong>Manage hierarchy flexibly across phases.&lt;/strong> Here&amp;rsquo;s something that took me too long to understand: &lt;strong>power dynamics exist in every meeting whether we acknowledge them or not.&lt;/strong> Priya Parker&amp;rsquo;s research on effective gatherings makes this point forcefully—ignoring power dynamics doesn&amp;rsquo;t make them disappear, it just means they operate invisibly and unmanaged.&lt;/p>
&lt;p>In data teams, hierarchy comes from multiple sources. There&amp;rsquo;s the obvious organizational hierarchy—staff engineers outrank senior engineers who outrank mid-level engineers. But there&amp;rsquo;s also expertise hierarchy (the person who built the system has more authority than the person who joined yesterday), social capital (some people have more trust and relationships), and communication skill (articulate people get more airtime regardless of title).&lt;/p>
&lt;p>When we pretend everyone&amp;rsquo;s voice carries equal weight, we&amp;rsquo;re not creating equality—we&amp;rsquo;re just refusing to acknowledge reality. The senior architect&amp;rsquo;s skeptical look still kills ideas. The staff engineer&amp;rsquo;s casual &amp;ldquo;I don&amp;rsquo;t think that&amp;rsquo;ll work&amp;rdquo; still shuts down exploration. The difference is whether we&amp;rsquo;re managing these dynamics intentionally or letting them operate invisibly.&lt;/p>
&lt;p>Managing hierarchy flexibly means acknowledging power exists and deliberately adjusting it for different phases of work. During idea generation, we want to minimize hierarchy&amp;rsquo;s influence. During evaluation and decision-making, we can leverage senior judgment appropriately. But we have to be explicit about the shift.&lt;/p>
&lt;p>Alison Wood Brooks&amp;rsquo; research on conversation dynamics identifies specific behaviors that high-status members must practice to create genuine safety in group settings. The problem isn&amp;rsquo;t just that senior people dominate airtime—though they do—it&amp;rsquo;s that high status actually inhibits perspective-taking. When you&amp;rsquo;re senior in a group, you naturally become less attentive to others&amp;rsquo; viewpoints and less careful with your language.&lt;/p>
&lt;p>This means senior engineers and managers need to work actively against their default behaviors. Brooks recommends:&lt;/p>
&lt;p>&lt;strong>Acknowledge junior members by name&lt;/strong> before they speak and after they contribute. &amp;ldquo;Sarah, I&amp;rsquo;m curious what you think about this&amp;rdquo; and &amp;ldquo;That point Sarah made earlier about partitioning strategies is worth exploring&amp;rdquo; both signal that contributions from less senior people matter.&lt;/p>
&lt;p>&lt;strong>Be vulnerable about your own failures&lt;/strong> early in the discussion. When a principal engineer says &amp;ldquo;I tried this approach last year and completely misjudged the performance implications,&amp;rdquo; it reduces everyone else&amp;rsquo;s fear of proposing imperfect ideas. Vulnerability from the top of the hierarchy creates permission for uncertainty throughout the group.&lt;/p>
&lt;p>&lt;strong>Use genuinely inclusive eye contact.&lt;/strong> Don&amp;rsquo;t just look at other senior people or the most vocal contributors. Make eye contact with the quiet analyst, the new hire, the contractor who hasn&amp;rsquo;t spoken yet. Your attention signals whose contributions you value.&lt;/p>
&lt;p>&lt;strong>Sometimes just get out of the way.&lt;/strong> Stop talking. Give others space. If you&amp;rsquo;re the most senior person and you&amp;rsquo;ve spoken three times while others have spoken once or not at all, you&amp;rsquo;re probably dominating even if you don&amp;rsquo;t feel like you are.&lt;/p>
&lt;p>I&amp;rsquo;ve started doing something deliberate in architecture discussions: I ask a question to frame the problem, then I explicitly say &amp;ldquo;I&amp;rsquo;m going to listen for the first 15 minutes and not share my thoughts yet.&amp;rdquo; It feels uncomfortable—I usually have opinions and I&amp;rsquo;m used to sharing them. But the quality of the solutions that emerge when I&amp;rsquo;m not steering the conversation is consistently better than what I would have proposed initially.&lt;/p>
&lt;p>&lt;strong>Build genuine ownership through participatory design.&lt;/strong> Don&amp;rsquo;t ask for input on your pre-decided solution—that&amp;rsquo;s performative participation. Create actual joint solution development where the outcome isn&amp;rsquo;t predetermined. Be willing to implement approaches that differ from what you would have chosen if people genuinely helped design them and they&amp;rsquo;ll work in your context.&lt;/p>
&lt;p>&lt;strong>Develop champions, don&amp;rsquo;t just appoint them.&lt;/strong> Champions need belief in the innovation, organizational support, time to help others, experience implementing what they&amp;rsquo;re championing, and self-efficacy in leading change. You can&amp;rsquo;t just tap someone&amp;rsquo;s shoulder and expect them to magically become an effective change agent.&lt;/p>
&lt;p>&lt;strong>Run pathfinder projects before mandates.&lt;/strong> Let teams experiment with new approaches in low-risk contexts. Make results visible to others. Create opportunities for interested teams to try things without committing to wholesale adoption. NASA&amp;rsquo;s approach to Model-Based Systems Engineering provides the template: pathfinders demonstrate value, reduce perceived risk, and create internal proof points.&lt;/p>
&lt;p>&lt;strong>Respect the evidence-based culture of technical teams.&lt;/strong> Engineers want to see proof that something works before investing effort. Don&amp;rsquo;t appeal to authority or best practices—run experiments, collect data, show concrete outcomes. When adopting a new tool, the question &amp;ldquo;has anyone actually measured the improvement?&amp;rdquo; should have a data-driven answer.&lt;/p>
&lt;p>I&amp;rsquo;ve used these patterns to implement changes ranging from adopting dbt to building data contracts to migrating cloud platforms to establishing data quality frameworks. They work consistently—not because they&amp;rsquo;re revolutionary, but because they align with how humans actually engage with ideas and change.&lt;/p>
&lt;p>The fundamental insight is this: &lt;strong>process shapes ownership, ownership shapes commitment, and commitment shapes outcomes.&lt;/strong>&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="the-compounding-effects-you-actually-see">The compounding effects you actually see&lt;/h2>
&lt;br>
&lt;p>Here&amp;rsquo;s what I&amp;rsquo;ve observed when teams get this right versus when they don&amp;rsquo;t.&lt;/p>
&lt;p>&lt;strong>High psychological safety teams&lt;/strong> surface problems early before they become critical, explore diverse solution approaches, help each other debug issues without fear of judgment, openly discuss failures and what was learned, share knowledge freely across team boundaries, try new tools and approaches without waiting for mandates, and actively participate in architectural decisions.&lt;/p>
&lt;p>&lt;strong>Low psychological safety teams&lt;/strong> hide problems until they explode, implement the first obvious solution without exploring alternatives, avoid asking for help, repeat the same mistakes, hoard knowledge as job security, wait for explicit direction before trying anything new, and defer all decisions to the most senior person.&lt;/p>
&lt;p>The performance gap is enormous. Google&amp;rsquo;s Project Aristotle found that teams with low psychological safety show measurable declines in delivery throughput, recovery time, and change failure rates.&lt;/p>
&lt;p>In data teams specifically, I&amp;rsquo;ve seen the impacts manifest as: technical debt accumulation (engineers fear reporting that shortcuts were taken), narrow solution spaces (teams explore fewer alternatives because suggesting unconventional approaches feels risky), knowledge silos (learning isn&amp;rsquo;t shared because it provides competitive advantage within the team), and review avoidance (discomfort with feedback prevents thorough code reviews and pair programming).&lt;/p>
&lt;p>The mechanisms reinforce each other. Psychological safety enables idea sharing. Quantity targets reduce remaining evaluation apprehension. Removing hierarchy during generation eliminates authority-triggered self-censorship. Participation creates ownership. Ownership drives championing. Champions spread adoption. Successful adoption reinforces psychological safety for the next change.&lt;/p>
&lt;p>&lt;strong>Each mechanism matters independently, but the compounding effects explain why some teams achieve dramatically better outcomes than others.&lt;/strong>&lt;/p>
&lt;p>I watched this play out when comparing two data teams within the same company. Both had similar talent, similar tools, similar organizational challenges. But one team had a leader who&amp;rsquo;d internalized these principles, and one hadn&amp;rsquo;t.&lt;/p>
&lt;p>The first team ran weekly &amp;ldquo;all ideas&amp;rdquo; sessions when facing problems. They explicitly managed hierarchy—senior people sometimes sat out of initial brainstorming. They built solutions collaboratively. They developed internal champions who helped spread new practices. When they adopted new tools, it was because teams had experimented and demonstrated value.&lt;/p>
&lt;p>The second team operated top-down. The lead made architectural decisions and communicated them. People implemented what was asked. Adoption of new practices happened when mandated. Problems surfaced late because people were reluctant to admit issues.&lt;/p>
&lt;p>After 18 months, the performance gap was stark. The first team delivered more features, had higher data quality metrics, lower on-call burden, better retention, and higher engagement scores. The second team struggled with all of these, and turnover was high.&lt;/p>
&lt;p>&lt;strong>The difference wasn&amp;rsquo;t the people—it was the process for how ideas were generated, evaluated, and implemented.&lt;/strong>&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-this-means-for-your-team-monday-morning">What this means for your team Monday morning&lt;/h2>
&lt;br>
&lt;p>You don&amp;rsquo;t need to transform your entire organization overnight. You can start applying these principles immediately in small ways.&lt;/p>
&lt;p>Next time you&amp;rsquo;re planning a significant change—adopting a new tool, migrating architecture, implementing new processes—try this:&lt;/p>
&lt;p>Before announcing the approach, bring the team together and ask them to generate solutions. Set a specific quantity target high enough to make people uncomfortable. Ask for &amp;ldquo;all ideas&amp;rdquo; not &amp;ldquo;best ideas.&amp;rdquo; Explicitly suspend judgment during generation.&lt;/p>
&lt;p>If there&amp;rsquo;s significant hierarchy in the room, manage it. Have senior people contribute last, or run initial brainstorming without them present. Try anonymous collection methods.&lt;/p>
&lt;p>Take the ideas seriously. Not every idea will be implementable, but engage with them genuinely. Build the final solution collaboratively from the ideas generated, not from your pre-decided approach with some team input sprinkled on top.&lt;/p>
&lt;p>Identify potential champions—people who believe in the solution (because they helped create it), have experience with the domain, are willing to help others, and are trusted by peers. Support them with time, resources, and organizational backing.&lt;/p>
&lt;p>Run pathfinder projects before mandates. Let interested teams try the approach in low-risk contexts. Make results visible. Create proof points.&lt;/p>
&lt;p>&lt;strong>You&amp;rsquo;ll likely be surprised by both the quality of solutions generated and the enthusiasm for implementing them.&lt;/strong>&lt;/p>
&lt;p>The research suggests not that technical teams need different approaches than other teams, but that they may be especially well-suited to participatory methods when those methods are implemented authentically rather than performatively. Evidence-based culture, distributed expertise, and autonomy expectations all align with genuine collaborative ideation.&lt;/p>
&lt;p>But the key word is genuine. Teams can smell performative participation from a mile away. If you&amp;rsquo;re asking for input but have already decided the answer, people know. If you&amp;rsquo;re creating the appearance of collaboration while maintaining top-down control, it&amp;rsquo;s worse than just being explicitly directive.&lt;/p>
&lt;p>The fundamental shift is from &amp;ldquo;how do I get people to adopt my solution?&amp;rdquo; to &amp;ldquo;how do I create conditions where we develop better solutions together?&amp;rdquo;&lt;/p>
&lt;p>That shift is uncomfortable for many leaders. It requires genuine sharing of control, tolerance for messiness in the process, and comfort with outcomes that differ from what you would have chosen alone.&lt;/p>
&lt;p>But if you want your data team to generate better ideas, adopt changes more willingly, and sustain improvements longer—the research is clear about what works.&lt;/p>
&lt;p>The question isn&amp;rsquo;t whether these approaches are effective. The question is whether you&amp;rsquo;re willing to use them.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Leadership</category><category>Data Engineering</category><category>Career Development</category><category>Team Culture</category><category>Collaboration</category><category>Psychological Safety</category><category>Innovation</category><category>Change Management</category><category>Technical Leadership</category><category>Data Teams</category></item><item><title>The Science of Conversation for people who hate small talk</title><link>https://ghostinthedata.info/posts/2026/2026-01-04-talk/</link><pubDate>Sun, 04 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-04-talk/</guid><author>Chris Hillman</author><description>Harvard research reveals that conversation follows learnable patterns. Here's how data engineers can use the TALK framework to build relationships, manage stakeholders, and lead more effectively—even when networking feels unnatural.</description><content:encoded>&lt;p>One morning, I watched a data engineer struggle with using AI for thirty minutes, trying to debug a DBT job. The problem wasn&amp;rsquo;t the LLM&amp;rsquo;s capabilities—it was how the engineer framed the question. No context about what they&amp;rsquo;d already tried. No explanation of the expected versus actual output. Just &amp;ldquo;fix this code&amp;rdquo; followed by a massive code dump.&lt;/p>
&lt;p>This same engineer had similar struggles with stakeholders. Presentations that assumed too much context. Emails that buried the ask. Meetings where they answered questions nobody asked.&lt;/p>
&lt;p>Here&amp;rsquo;s what I realized: &lt;strong>the framework for good human conversation is the same framework for effective AI interaction&lt;/strong>. Whether you&amp;rsquo;re prompting an LLM or presenting to executives, the mechanics are identical—you&amp;rsquo;re coordinating with another entity to exchange information and achieve shared goals.&lt;/p>
&lt;p>Harvard professor Alison Wood Brooks spent years analyzing thousands of conversations, discovering that communication follows predictable, learnable patterns. Her research culminated in the TALK framework—a systematic approach to conversation that works whether you&amp;rsquo;re talking to Claude, Chat GPT or your CEO, or a date you&amp;rsquo;re not sure about. For technical professionals who recognize communication matters but don&amp;rsquo;t feel it&amp;rsquo;s their natural strength, this is a game-changer.&lt;/p>
&lt;p>The research is clear: &lt;strong>70% of career advancement for engineers stems from communication skills, not technical prowess&lt;/strong>. Yet we spend hours learning data optimization and zero minutes learning conversation science. Brooks&amp;rsquo; findings offer evidence-based techniques that play to analytical strengths—asking better questions beats being charismatic, preparation trumps improvisation, and introverts have hidden advantages.&lt;/p>
&lt;p>This isn&amp;rsquo;t about becoming an extrovert. It&amp;rsquo;s about understanding how conversation actually works and deploying specific, research-backed strategies. The same strategies that make you better at prompting AI make you better at managing stakeholders, building teams, and advancing your career.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="t-is-for-topics-preparation-isnt-performance">T Is for Topics: Preparation Isn&amp;rsquo;t Performance&lt;/h2>
&lt;br>
&lt;p>Most people balk at preparing conversation topics. It feels unnatural, performative, like you&amp;rsquo;re planning spontaneity out of existence. &lt;strong>Brooks found that 53% of people think topic prep is unnecessary&lt;/strong>, while 50% believe it will &lt;em>decrease&lt;/em> their enjoyment of conversations.&lt;/p>
&lt;p>This resistance stems from what Brooks calls the &amp;ldquo;Myth of Naturalness&amp;rdquo;—the belief that charismatic people just know what to say. We see someone excel at conversation and assume it&amp;rsquo;s effortless for them, when in reality they&amp;rsquo;ve accumulated years of deliberate practice. Same pattern we see in technical skills, but we forget to apply the logic.&lt;/p>
&lt;p>Here&amp;rsquo;s the data: before conversations, &lt;strong>27% of people spend at least five minutes deciding what to wear, while only 18% think about what they&amp;rsquo;ll talk about&lt;/strong>. We optimize our appearance and neglect the substance. Then we wonder why conversations feel awkward or shallow.&lt;/p>
&lt;h3 id="the-system-1-vs-system-2-problem">The System 1 vs System 2 Problem&lt;/h3>
&lt;p>During conversation, we rely on &amp;ldquo;system 1 thinking&amp;rdquo;—fast, instinctive, automatic processing. This guides us toward whatever&amp;rsquo;s top of mind: the weather, that random person standing nearby, the fact that someone just ordered appetizers. &lt;strong>We talk about what&amp;rsquo;s accessible rather than what&amp;rsquo;s meaningful&lt;/strong>.&lt;/p>
&lt;p>The topics most accessible to us aren&amp;rsquo;t always best for connection. We drift into safe small talk rather than searching for mutually rewarding subjects. We prove how smart we are instead of learning what our partner knows. This is natural egocentrism—optimizing for ourselves rather than the coordination game.&lt;/p>
&lt;p>Topic preparation engages &amp;ldquo;system 2 thinking&amp;rdquo;—slower, deliberative, logical processing. Before the conversation, when you have mental space, brainstorm 3-5 topics aligned with your goals. This isn&amp;rsquo;t scripting—it&amp;rsquo;s cognitive offloading. By reducing information-processing requirements beforehand, you free mental space during the conversation to actually listen and respond.&lt;/p>
&lt;h3 id="what-this-looks-like-for-data-engineers">What This Looks Like for Data Engineers&lt;/h3>
&lt;p>Before that stakeholder meeting, don&amp;rsquo;t just prepare your technical update. Prepare topics:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;I&amp;rsquo;m curious what drove the timeline pressure you mentioned last week&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What would success look like from your team&amp;rsquo;s perspective?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How does this fit with the Q2 roadmap priorities?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Before the networking event, prepare genuine curiosities:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;What made you move from engineering into product management?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What&amp;rsquo;s the hardest part of scaling data infrastructure at your company?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How did your team approach the migration to Snowflake?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>For your 1:1 with your manager, beyond status updates:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;Where do you see the biggest risks in our current architecture?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What capabilities do you wish the data team had?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How can I better support the team&amp;rsquo;s strategic goals?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Brooks uses a &amp;ldquo;topic pyramid&amp;rdquo; framework—base level is weather and small talk, middle level involves experiences and perspectives, top level consists of topics only you and your partner could discuss in that moment. &lt;strong>The mistake most people make: getting stuck at the base&lt;/strong>. Small talk isn&amp;rsquo;t the problem; lingering there too long is.&lt;/p>
&lt;p>Prepare topics that climb the pyramid. Instead of &amp;ldquo;How was your weekend?&amp;rdquo; (base level), try &amp;ldquo;I&amp;rsquo;ve been curious what made you interested in data quality originally&amp;rdquo; (middle to top level). You&amp;rsquo;re not abandoning spontaneity—you&amp;rsquo;re creating scaffolding that lets meaningful conversation emerge.&lt;/p>
&lt;h3 id="how-this-applies-to-llm-communication">How This Applies to LLM Communication&lt;/h3>
&lt;p>The exact same principle applies when prompting AI. Most people use system 1 thinking—whatever question first comes to mind. &amp;ldquo;Debug this code.&amp;rdquo; &amp;ldquo;Summarize this document.&amp;rdquo; &amp;ldquo;Write me a function.&amp;rdquo;&lt;/p>
&lt;p>Better results come from system 2 prep. Before opening Claude, ask yourself:&lt;/p>
&lt;ul>
&lt;li>What&amp;rsquo;s my actual goal here?&lt;/li>
&lt;li>What context does Claude need?&lt;/li>
&lt;li>What have I already tried?&lt;/li>
&lt;li>What specific output format do I want?&lt;/li>
&lt;li>What constraints matter?&lt;/li>
&lt;/ul>
&lt;p>Compare these prompts:&lt;/p>
&lt;p>&lt;strong>System 1&lt;/strong>: &amp;ldquo;Fix this SQL query&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>System 2&lt;/strong>: &amp;ldquo;I&amp;rsquo;m getting duplicate rows in this SQL query that joins orders to customers. I expected one row per order, but I&amp;rsquo;m getting multiple. Here&amp;rsquo;s the query, sample input data (remove sensitive data/mask it), and current output. Can you identify why duplicates are appearing and suggest a fix?&amp;rdquo;&lt;/p>
&lt;p>The second provides context (what&amp;rsquo;s wrong), expected behavior (one row per order), actual behavior (multiple rows), and supporting information (query, data, output). This is topic preparation for AI—giving the LLM the topics it needs to help you effectively.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="a-is-for-asking-follow-up-questions-are-your-superpower">A Is for Asking: Follow-Up Questions Are Your Superpower&lt;/h2>
&lt;br>
&lt;p>Brooks&amp;rsquo; team analyzed 900+ speed dates and conversations among 398 strangers, classifying every question asked. They identified four types: introductory (&amp;ldquo;What&amp;rsquo;s your name?&amp;rdquo;), mirror (&amp;ldquo;I&amp;rsquo;m good. How are you?&amp;rdquo;), topic-switching, and follow-up questions.&lt;/p>
&lt;p>The finding that should change how you approach every conversation: &lt;strong>follow-up questions drive almost all positive effects on likeability&lt;/strong>.&lt;/p>
&lt;p>The numbers are striking. People in the top third of question-askers got the most second dates. Asking just &lt;strong>one more follow-up question&lt;/strong> per date across 20 dates resulted in one additional second date agreement. Mirror questions—the polite reciprocations we default to—produced no measurable increase in likeability.&lt;/p>
&lt;h3 id="why-follow-ups-work">Why Follow-Ups Work&lt;/h3>
&lt;p>Follow-up questions signal three things simultaneously: you were actually listening, you care about what the person said, and you&amp;rsquo;re thoughtful enough to connect their previous statement to a deeper inquiry. They make people feel heard and validated.&lt;/p>
&lt;p>When a product manager explains a feature request, the instinct is to immediately propose technical solutions. That&amp;rsquo;s a shift response—redirecting attention to your expertise. A follow-up question maintains focus: &amp;ldquo;What&amp;rsquo;s driving the timeline pressure you mentioned?&amp;rdquo; This signals engagement far more effectively than demonstrating knowledge.&lt;/p>
&lt;p>Brooks warns against &amp;ldquo;ZQs&amp;rdquo;—people who leave conversations having asked zero questions. Professional matchmaker Rachel Greenwald says it directly: &lt;strong>&amp;ldquo;Zero questions means zero second dates.&amp;rdquo;&lt;/strong> But even when we ask questions, we vastly overestimate how many.&lt;/p>
&lt;p>In one study, negotiators estimated that over 50% of their turns included a question. In reality, less than 10% did—less than one-fifth of what they thought. We&amp;rsquo;ve found the same pattern in friend conversations and first dates. In the chaos of conversation, we don&amp;rsquo;t notice the link between asking questions and being liked, so we don&amp;rsquo;t get clear feedback on what&amp;rsquo;s working.&lt;/p>
&lt;h3 id="the-four-question-types-and-which-matter-most">The Four Question Types (And Which Matter Most)&lt;/h3>
&lt;p>&lt;strong>Introductory questions&lt;/strong> (&amp;ldquo;What&amp;rsquo;s your name?&amp;rdquo; &amp;ldquo;How are you?&amp;rdquo;) are necessary social ritual. Important but don&amp;rsquo;t linger.&lt;/p>
&lt;p>&lt;strong>Mirror questions&lt;/strong> (&amp;ldquo;I&amp;rsquo;m good. How are you?&amp;rdquo;) reciprocate what was just asked. They&amp;rsquo;re polite but add little value—they&amp;rsquo;re &amp;ldquo;low-hanging improvisational fruit&amp;rdquo; that feel effortless but don&amp;rsquo;t increase likeability. Use them when you genuinely want to hear the answer, not out of reflex.&lt;/p>
&lt;p>&lt;strong>Topic-switching questions&lt;/strong> initiate noticeably new subjects. Instead of mirroring &amp;ldquo;How was your day?&amp;rdquo; with your own day summary, try: &amp;ldquo;Did you see that new data governance framework GitHub just released?&amp;rdquo; This climbs the topic pyramid rather than floundering at the base.&lt;/p>
&lt;p>&lt;strong>Follow-up questions&lt;/strong> are the superheroes. They keep you on the present subject, probing deeper on something your partner said. &amp;ldquo;You mentioned the migration was stressful—what made it complicated?&amp;rdquo; &amp;ldquo;How did your team approach that problem?&amp;rdquo; &amp;ldquo;What would you do differently next time?&amp;rdquo;&lt;/p>
&lt;p>In Brooks&amp;rsquo; studies, when people were prompted to ask more questions, the extra questions were mainly follow-ups. &lt;strong>This means the rewards of asking more questions were driven almost entirely by follow-up questions&lt;/strong>. They unlock deeper learning, feel personal and validating, and show you&amp;rsquo;ve actually been heard.&lt;/p>
&lt;h3 id="the-never-ending-follow-up-exercise">The Never-Ending Follow-Up Exercise&lt;/h3>
&lt;p>Brooks challenges her students to have conversations using &lt;em>only&lt;/em> follow-up questions. They can make statements for smoothness, but must always respond with a follow-up. The goal: see how easy and powerful sustained curiosity is.&lt;/p>
&lt;p>Students are surprised by three things:&lt;/p>
&lt;ol>
&lt;li>How easily they can maintain never-ending follow-ups&lt;/li>
&lt;li>How much they learn about their partner&lt;/li>
&lt;li>How engaging it is—for everyone involved&lt;/li>
&lt;/ol>
&lt;p>Great conversationalists strike the right balance between topic-switching questions (to hop to new islands) and follow-up questions (to explore islands once you&amp;rsquo;re there). Typically, asking mostly follow-up questions doesn&amp;rsquo;t mean staying on the same topic—they lead your partner through exciting drift, opening new worlds even as they spring from what was previously said.&lt;/p>
&lt;h3 id="for-data-engineers-learning-from-masters">For Data Engineers: Learning from Masters&lt;/h3>
&lt;p>Terry Gross, Oprah Winfrey, and Barbara Walters are follow-up masters. They don&amp;rsquo;t need specialized expertise to discuss any topic because they can ask follow-up questions. &lt;strong>It&amp;rsquo;s the ultimate improv tool, especially when you don&amp;rsquo;t understand or know what to say.&lt;/strong>&lt;/p>
&lt;p>Specific patterns to emulate:&lt;/p>
&lt;p>&lt;strong>Emotional probing&lt;/strong> : &amp;ldquo;How did that make you &lt;em>feel&lt;/em>?&amp;rdquo; When a stakeholder says &amp;ldquo;the migration was challenging,&amp;rdquo; instead of proposing solutions: &amp;ldquo;What was most frustrating about the process?&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Past-present-future&lt;/strong> : &amp;ldquo;If you had to do it again, would you choose Snowflake?&amp;rdquo; &amp;ldquo;Are you happy with the decision now?&amp;rdquo; &amp;ldquo;What will you tell the next team considering this migration?&amp;rdquo; This breaks people out of present-myopia and reveals new insights.&lt;/p>
&lt;p>&lt;strong>Journalistic curiosity&lt;/strong> : When in doubt, just keep asking. The key to good conversation isn&amp;rsquo;t knowing—it&amp;rsquo;s learning. Be interested in your partner, not interesting yourself.&lt;/p>
&lt;p>When a colleague mentions they&amp;rsquo;re struggling with data quality, resist immediately showcasing your expertise. Instead:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;What specific quality issues are you seeing?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How is this affecting downstream teams?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What have you tried so far?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What would ideal state look like?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>You&amp;rsquo;ll learn more, build better rapport, and position yourself as someone who actually listens—a rarity in technical organizations.&lt;/p>
&lt;h3 id="the-boomerasking-trap">The Boomerasking Trap&lt;/h3>
&lt;p>Brooks identifies a destructive pattern called &amp;ldquo;boomerasking&amp;rdquo;—asking questions purely to talk about yourself. &amp;ldquo;Have you won any contests lately?&amp;rdquo; followed immediately by &amp;ldquo;I won the Pick a Present contest!&amp;rdquo; The question goes out and returns like a boomerang.&lt;/p>
&lt;p>In technical contexts, this manifests as:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;How did your team handle the migration?&amp;rdquo; → Immediate pivot to &amp;ldquo;Here&amp;rsquo;s how &lt;em>we&lt;/em> did ours&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What challenges are you facing?&amp;rdquo; → Instant &amp;ldquo;Let me tell you about &lt;em>our&lt;/em> challenges&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Sociologist Charles Derber distinguishes between &lt;strong>shift responses&lt;/strong> (redirecting to yourself) and &lt;strong>support responses&lt;/strong> (maintaining focus on your partner):&lt;/p>
&lt;p>&lt;strong>Shift&lt;/strong>: &amp;ldquo;Yeah, I had the same issue on my last project—let me tell you what happened&amp;rdquo;
&lt;strong>Support&lt;/strong>: &amp;ldquo;What&amp;rsquo;s making the timeline feel tight?&amp;rdquo;&lt;/p>
&lt;p>For technical professionals, the instinct to demonstrate expertise through personal examples creates precisely the wrong impression. Three types of support responses build trust:&lt;/p>
&lt;ul>
&lt;li>Background acknowledgments: &amp;ldquo;Seriously?&amp;rdquo; &amp;ldquo;Oh wow!&amp;rdquo;&lt;/li>
&lt;li>Supportive assertions: &amp;ldquo;That sounds really difficult&amp;rdquo;&lt;/li>
&lt;li>Supportive questions: &amp;ldquo;What happened next?&amp;rdquo; &amp;ldquo;How did that affect the team?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;h3 id="how-this-applies-to-llm-communication-1">How This Applies to LLM Communication&lt;/h3>
&lt;p>The best LLM interactions are conversations, not commands. Follow-up with Claude based on responses:&lt;/p>
&lt;p>&lt;strong>Initial&lt;/strong>: &amp;ldquo;Can you help me optimize this SQL query?&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Claude responds with a solution&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Follow-up&lt;/strong>: &amp;ldquo;Why did you choose a CTE instead of a subquery here? What are the performance implications?&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Claude explains&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Follow-up&lt;/strong>: &amp;ldquo;In what scenarios would the subquery approach be better?&amp;rdquo;&lt;/p>
&lt;p>This creates a learning conversation rather than a one-shot transaction. You&amp;rsquo;re not just getting code—you&amp;rsquo;re understanding the reasoning, which makes you better at writing queries independently.&lt;/p>
&lt;p>Same pattern works in code review. Instead of &amp;ldquo;Why did you do it this way?&amp;rdquo; (which sounds accusatory), try:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;Can you walk me through your thinking here?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;What were you optimizing for?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Did you consider [alternative]? What made you choose this approach?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Follow-up questions signal curiosity rather than criticism, making people more willing to explain and learn.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="l-is-for-levity-energy-management-not-entertainment">L Is for Levity: Energy Management, Not Entertainment&lt;/h2>
&lt;br>
&lt;p>Brooks introduces levity using an emotion chart with two dimensions: arousal (high vs low energy) and valence (pleasure vs displeasure). When conversations drift into the lower-left quadrant (sad, bored, disengaged), people disengage. Minds wander. Eye contact drops. Words become neutral or negative. Pauses lengthen.&lt;/p>
&lt;p>In the upper-right quadrant (excited, engaged, joyous), the opposite happens. People lean forward, make eye contact, give back-channel feedback (&amp;ldquo;yeah,&amp;rdquo; &amp;ldquo;uh-huh&amp;rdquo;), respond quickly, even interrupt—because they&amp;rsquo;re excited to know what happens next.&lt;/p>
&lt;p>&lt;strong>Levity is any conversational move—playful, funny, unexpected, warm—that infuses positive energy&lt;/strong>. It&amp;rsquo;s the yeast that turns dense dough into fluffy bread, the helium keeping a balloon aloft. It moves conversations from disengagement to engagement.&lt;/p>
&lt;h3 id="why-engineers-underuse-levity">Why Engineers Underuse Levity&lt;/h3>
&lt;p>Research shows people &lt;strong>overestimate how often humor goes poorly&lt;/strong> and underestimate the upside. At age 23, 84% of people report smiling and laughing boosts mood, draws people closer, and increases perceptions of competence.&lt;/p>
&lt;p>Brooks&amp;rsquo; research found that managers randomly assigned to make one joke in one conversation were &lt;strong>over 9% more likely to be voted into leadership positions&lt;/strong>. Bosses with &lt;em>any&lt;/em> sense of humor are &lt;strong>27% more motivating and admired&lt;/strong> than those with none, and their employees are &lt;strong>15% more engaged&lt;/strong>.&lt;/p>
&lt;p>The individual risk that one-off jokes will flop is much smaller than the aggregated risk of a lifetime of boring, disengaged conversation. But our worries aren&amp;rsquo;t totally unfounded—the trick is getting it right.&lt;/p>
&lt;h3 id="the-benign-violation-theory">The Benign Violation Theory&lt;/h3>
&lt;p>Psychologists Peter McGraw and Caleb Warren suggest humor works when things are neither too benign (boring) nor too violating (scary, aggressive, inappropriate)—but somewhere between.&lt;/p>
&lt;p>Simply walking downstairs: benign. Falling and breaking your arm: violation. Pretending to fall, landing in a silly pose: benign violation.&lt;/p>
&lt;p>The challenge: the sweet spot moves depending on context, relationship, and individual humor styles. What seems funny from sweet-Olivia might seem mundane from sarcastic-Jimmy.&lt;/p>
&lt;h3 id="affiliative-vs-aggressive-humor">Affiliative vs Aggressive Humor&lt;/h3>
&lt;p>&lt;strong>Affiliative humor&lt;/strong> brings people together, makes everyone feel included, increases psychological safety.&lt;/p>
&lt;p>&lt;strong>Aggressive humor&lt;/strong> involves put-downs or insults, decreases psychological safety, fragments people into categories.&lt;/p>
&lt;p>When in doubt, be gentle. It&amp;rsquo;s less costly to flop for being too benign than too aggressive. Failed gentle humor means people don&amp;rsquo;t laugh and move on. Failed aggressive humor means hurt feelings that take a long time to heal.&lt;/p>
&lt;h3 id="self-deprecation-the-technical-professionals-secret-weapon">Self-Deprecation: The Technical Professional&amp;rsquo;s Secret Weapon&lt;/h3>
&lt;p>Former congressman Ric Keller traces his political career start to a joke at age 34. After sitting through hours of serious speakers, he opened: &amp;ldquo;I feel like Elizabeth Taylor&amp;rsquo;s seventh husband on his wedding night. Technically, I know what I&amp;rsquo;m supposed to do. But at this point, I don&amp;rsquo;t know how to make it interesting.&amp;rdquo;&lt;/p>
&lt;p>Cheesy? Sure. But it got a huge laugh from a crowd hungry for a fresh voice. Self-deprecation works best when leaders and high-status people use it—it makes them seem more approachable and human.&lt;/p>
&lt;p>For technical professionals presenting to executives:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;This dashboard has so many charts I&amp;rsquo;m not sure if it&amp;rsquo;s a Tableau workbook or modern art&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;I&amp;rsquo;ve explained this architecture so many times I dream in flowcharts&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Our data pipeline is held together with duct tape and prayers—but it&amp;rsquo;s &lt;em>documented&lt;/em> duct tape and prayers&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Self-deprecation shows vulnerability while maintaining competence. You&amp;rsquo;re acknowledging imperfection without undermining your expertise.&lt;/p>
&lt;h3 id="what-levity-isnt">What Levity Isn&amp;rsquo;t&lt;/h3>
&lt;p>Don&amp;rsquo;t make fun of stakeholders, team members, or anyone with lower status. &amp;ldquo;Punching down&amp;rdquo; kills psychological safety and trust. The &lt;em>New Yorker&lt;/em> cartoon wisdom—&amp;ldquo;If you can&amp;rsquo;t say something nice, say something clever but devastating&amp;rdquo;—might work for comedy, but it&amp;rsquo;s terrible for professional relationships.&lt;/p>
&lt;p>If you&amp;rsquo;re choosing between gentle-but-cheesy and clever-but-devastating, choose the former. Everyone likes to laugh, but nobody likes to be laughed &lt;em>at&lt;/em>.&lt;/p>
&lt;h3 id="for-data-engineers-practical-levity">For Data Engineers: Practical Levity&lt;/h3>
&lt;p>Levity isn&amp;rsquo;t about being a comedian. It&amp;rsquo;s about energy management—keeping conversations in the engaged quadrant. Specific tactics:&lt;/p>
&lt;p>&lt;strong>In presentations&lt;/strong>: Start with a light observation. &amp;ldquo;I know the last thing anyone wants Monday morning is another architecture diagram, but I promise this one has colors.&amp;rdquo; Not hilarious, but it acknowledges shared reality and signals you&amp;rsquo;re human.&lt;/p>
&lt;p>&lt;strong>In meetings&lt;/strong>: When discussions get too heavy, inject perspective. &amp;ldquo;We&amp;rsquo;ve been debating this for 30 minutes. I propose we decide by seeing who can juggle the most coffee cups.&amp;rdquo; Gets a laugh, resets energy, helps people realize they&amp;rsquo;re overthinking.&lt;/p>
&lt;p>&lt;strong>In Slack&lt;/strong>: Use gentle humor to soften messages. Instead of &amp;ldquo;This code has bugs,&amp;rdquo; try &amp;ldquo;Found some unexpected features in the code—I think the bugs are multiplying via asexual reproduction.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>In documentation&lt;/strong>: &amp;ldquo;Note: This regex looks like someone sat on the keyboard. I promise it makes sense.&amp;rdquo;&lt;/p>
&lt;p>The goal isn&amp;rsquo;t entertainment—it&amp;rsquo;s keeping people engaged when discussing complex topics. Small moments of levity prevent cognitive fatigue and make information more memorable.&lt;/p>
&lt;h3 id="how-this-applies-to-llm-communication-2">How This Applies to LLM Communication&lt;/h3>
&lt;p>LLMs respond to tone. Compare:&lt;/p>
&lt;p>&lt;strong>No levity&lt;/strong>: &amp;ldquo;Explain dependency injection&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>With levity&lt;/strong>: &amp;ldquo;Explain dependency injection like I&amp;rsquo;m a curious golden retriever who somehow learned to code but still gets confused by fancy patterns&amp;rdquo;&lt;/p>
&lt;p>The second isn&amp;rsquo;t just more fun—it actually produces better results because it gives Claude clear framing about complexity level and explanation style. You&amp;rsquo;re using levity to establish shared context.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="k-is-for-kindness-putting-others-conversational-needs-first">K Is for Kindness: Putting Others&amp;rsquo; Conversational Needs First&lt;/h2>
&lt;br>
&lt;p>Brooks&amp;rsquo; final maxim is the most challenging: &lt;strong>trying your utmost to put others&amp;rsquo; conversational needs first&lt;/strong>. Not always possible, not always optimal, but reaching for it relentlessly gives you the best chance of being the partner you mean to be.&lt;/p>
&lt;p>The Anderson Cooper and Stephen Colbert conversation about grief demonstrates this beautifully. Both men lost fathers and brothers tragically young. When Cooper interviews Colbert on CNN—no live audience, just two people in director&amp;rsquo;s chairs—they create one of the most powerful public conversations on grief you&amp;rsquo;ll watch.&lt;/p>
&lt;p>What makes it work isn&amp;rsquo;t just the topics (grief, loss, mothers) or the questions (14 questions in 20 minutes) or even the levity (warm laughter every 2-3 minutes). &lt;strong>It&amp;rsquo;s their invisible but palpable focus on the other person&lt;/strong>—their words, their struggle, their life, their needs.&lt;/p>
&lt;p>Cooper prepared extensively—read previous Colbert interviews, can quote his words, has notes and questions ready. Colbert is self-deprecating, allows Cooper to lead, responds to vulnerability with vulnerability. When one offers a thought, the other builds on it or discloses in return.&lt;/p>
&lt;p>Ten minutes in, Cooper says: &amp;ldquo;I wish I had, like, a scar. A Bond villain scar running down my eye and face&amp;rdquo; to signal &amp;ldquo;I&amp;rsquo;m not the person I was meant to be.&amp;rdquo;&lt;/p>
&lt;p>Colbert shifts in his seat. Takes off his glasses. Smiles. &amp;ldquo;But you&amp;rsquo;re &lt;em>entirely&lt;/em> the person you were meant to be.&amp;rdquo;&lt;/p>
&lt;p>This is kindness in conversation—gently challenging a thought that might not serve Cooper, even when it creates tension. Cooper asks, voice breaking, &amp;ldquo;Do you really believe that?&amp;rdquo; Colbert pauses, maintains unwavering gaze, replies simply: &amp;ldquo;Yes.&amp;rdquo;&lt;/p>
&lt;p>Kindness doesn&amp;rsquo;t mean agreeing with everything. It means figuring out what your partner needs and helping them get it—encouragement, hard feedback, new ideas, a laugh, a sounding board, challenging questions.&lt;/p>
&lt;h3 id="the-egocentrism-problem">The Egocentrism Problem&lt;/h3>
&lt;p>Psychologist Jean Piaget showed that around age seven, children begin recognizing others have different perspectives. Before that: extreme egocentrism. Hands over eyes during hide-and-seek? They think you can&amp;rsquo;t see them. &amp;ldquo;That&amp;rsquo;s mine!&amp;rdquo; isn&amp;rsquo;t compelling justification—it&amp;rsquo;s self-centered assertion.&lt;/p>
&lt;p>&lt;strong>Here&amp;rsquo;s the devastating fact: we don&amp;rsquo;t shed egocentrism after age seven.&lt;/strong> It operates constantly in the background, sabotaging our ability to converse effectively.&lt;/p>
&lt;p>Egocentrism makes us choose topics &lt;em>we&lt;/em> like (assuming our partner will like them because we do). It makes us ask questions &lt;em>we&lt;/em> find interesting rather than ones &lt;em>they&amp;rsquo;d&lt;/em> be excited to answer. It makes us create humor that &lt;em>we&lt;/em> think is funny without considering whether it serves &lt;em>them&lt;/em>.&lt;/p>
&lt;p>Researcher Boaz Keysar demonstrated this brilliantly. He had pairs sit back-to-back as speaker and listener. Speakers imagined scenarios (&amp;ldquo;suspect your friend has been planning a surprise&amp;rdquo;) then delivered lines (&amp;ldquo;What have you been up to?&amp;rdquo;) with any inflection. Listeners chose the intended meaning from four options.&lt;/p>
&lt;p>Results: Listeners thought they correctly identified meaning &lt;strong>85% of the time&lt;/strong>. Speakers thought listeners understood &lt;strong>70% of the time&lt;/strong>. Actual accuracy: &lt;strong>44%&lt;/strong>.&lt;/p>
&lt;p>Even more striking: Keysar repeated this with people who &lt;em>didn&amp;rsquo;t speak the same language&lt;/em>. Chinese speakers, English listeners. American listeners believed they understood &lt;strong>65% of the time&lt;/strong>. Chinese speakers believed they&amp;rsquo;d been understood &lt;strong>50% of the time&lt;/strong>. Actual accuracy: &lt;strong>32%&lt;/strong>.&lt;/p>
&lt;p>George Bernard Shaw: &lt;strong>&amp;ldquo;The single biggest problem with communication is the illusion that it has taken place.&amp;rdquo;&lt;/strong>&lt;/p>
&lt;p>We can&amp;rsquo;t prioritize others&amp;rsquo; conversational needs if we don&amp;rsquo;t understand them, especially when we mistakenly believe we do. Kindness requires continuously checking that illusion.&lt;/p>
&lt;h3 id="for-data-engineers-practical-kindness">For Data Engineers: Practical Kindness&lt;/h3>
&lt;p>&lt;strong>In technical discussions&lt;/strong>, kindness means matching your partner&amp;rsquo;s level:&lt;/p>
&lt;p>With junior engineers: Don&amp;rsquo;t overwhelm with advanced concepts. Ask &amp;ldquo;Does this make sense?&amp;rdquo; and actually wait for response. Explain the &amp;ldquo;why&amp;rdquo; behind decisions, not just the &amp;ldquo;what.&amp;rdquo;&lt;/p>
&lt;p>With senior leaders: Don&amp;rsquo;t bury them in technical details. Start with business impact, then offer technical depth as needed. Ask &amp;ldquo;What level of detail would be helpful?&amp;rdquo;&lt;/p>
&lt;p>With non-technical stakeholders: Use their domain language, not yours. Instead of &amp;ldquo;We need to denormalize the schema to improve query performance,&amp;rdquo; try &amp;ldquo;We need to restructure how we store data so reports run faster. Think of it like rearranging a library—same books, faster to find what you need.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>In difficult conversations&lt;/strong>, kindness means creating safety:&lt;/p>
&lt;p>When delivering bad news: &amp;ldquo;I need to tell you something that&amp;rsquo;s going to be disappointing&amp;rdquo; (prepares them) vs. abrupt revelation.&lt;/p>
&lt;p>When receiving feedback: &amp;ldquo;I appreciate you bringing this up—help me understand what you&amp;rsquo;re seeing&amp;rdquo; vs. defensive response.&lt;/p>
&lt;p>When disagreeing: &amp;ldquo;I see it differently—can I explain my thinking?&amp;rdquo; vs. &amp;ldquo;That&amp;rsquo;s wrong.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>In team meetings&lt;/strong>, kindness means ensuring everyone&amp;rsquo;s heard:&lt;/p>
&lt;p>Notice who hasn&amp;rsquo;t spoken: &amp;ldquo;Jamie, you&amp;rsquo;ve been quiet—what&amp;rsquo;s your take on this?&amp;rdquo;&lt;/p>
&lt;p>Build on others&amp;rsquo; ideas: &amp;ldquo;Building on what Alex said&amp;hellip;&amp;rdquo; vs. introducing competing idea.&lt;/p>
&lt;p>Redirect credit: &amp;ldquo;That was Sarah&amp;rsquo;s insight—Sarah, can you explain how you approached it?&amp;rdquo;&lt;/p>
&lt;h3 id="the-hardest-part">The Hardest Part&lt;/h3>
&lt;p>Always putting others&amp;rsquo; needs first is unrealistic—and sometimes not optimal. You have legitimate needs on the low-relational side of the conversational compass. The advice isn&amp;rsquo;t to be selfless martyr.&lt;/p>
&lt;p>&lt;strong>The advice is to try relentlessly&lt;/strong>. To continuously ask yourself: &amp;ldquo;What does this person need from this conversation?&amp;rdquo; Then help them get it.&lt;/p>
&lt;p>Topics and questions provide substance. Levity keeps people engaged. Kindness creates space where meaningful exchange can flourish—where people feel respected and valued. It&amp;rsquo;s the master class that takes other skills to elite level.&lt;/p>
&lt;h3 id="how-this-applies-to-llm-communication-3">How This Applies to LLM Communication&lt;/h3>
&lt;p>Yes, kindness applies to AI conversation. Not because LLMs have feelings, but because kindness frameworks produce better results.&lt;/p>
&lt;p>&lt;strong>Provide context about what you need&lt;/strong>:&lt;/p>
&lt;p>Bad: &amp;ldquo;Write me a function&amp;rdquo;&lt;/p>
&lt;p>Good: &amp;ldquo;I need a function that parses JSON from an API response. I&amp;rsquo;m fairly comfortable with Python but new to this API, so I&amp;rsquo;d appreciate comments explaining the approach.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Acknowledge what&amp;rsquo;s working&lt;/strong>:&lt;/p>
&lt;p>&amp;ldquo;That solution worked perfectly for the first case, but when I tried it with nested JSON, I got an error. Here&amp;rsquo;s what I&amp;rsquo;m seeing&amp;hellip;&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>Be specific about confusion&lt;/strong>:&lt;/p>
&lt;p>Instead of &amp;ldquo;This doesn&amp;rsquo;t make sense,&amp;rdquo; try &amp;ldquo;I understand the first two steps, but I&amp;rsquo;m confused about why you&amp;rsquo;re using a set instead of a list in step 3. Can you explain the reasoning?&amp;rdquo;&lt;/p>
&lt;p>You&amp;rsquo;re not being polite to a machine—you&amp;rsquo;re practicing communication patterns that work with humans too. Every interaction is practice for the coordination game.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="the-through-line-system-design-for-human-connection">The Through-Line: System Design for Human Connection&lt;/h2>
&lt;br>
&lt;p>Brooks&amp;rsquo; research points to a fundamental reframe: &lt;strong>&amp;ldquo;The key to good conversation isn&amp;rsquo;t knowing, but learning. It&amp;rsquo;s about being interested in your partner, not interesting yourself.&amp;rdquo;&lt;/strong>&lt;/p>
&lt;p>This should resonate with analytical professionals. You don&amp;rsquo;t need charisma or extroversion. You need to deploy genuine curiosity—something most technical people already possess in abundance for technical problems—toward the humans you work with.&lt;/p>
&lt;p>The TALK framework provides structure:&lt;/p>
&lt;p>&lt;strong>T - Topics&lt;/strong>: Prepare 3-5 topics before important conversations. System 2 thinking frees mental space for system 1 responsiveness.&lt;/p>
&lt;p>&lt;strong>A - Asking&lt;/strong>: Ask more follow-up questions. They signal listening, care, and thoughtfulness. Master conversationalists don&amp;rsquo;t need specialized expertise—they have specialized curiosity.&lt;/p>
&lt;p>&lt;strong>L - Levity&lt;/strong>: Manage energy, not entertainment. Small moments of playfulness prevent disengagement and make information memorable.&lt;/p>
&lt;p>&lt;strong>K - Kindness&lt;/strong>: Continuously try to understand and meet others&amp;rsquo; conversational needs. Check your egocentrism at the door.&lt;/p>
&lt;p>These aren&amp;rsquo;t difficult techniques: brainstorm topics beforehand, ask more follow-up questions, inject occasional levity, focus on your partner rather than yourself. What makes them powerful is consistent application.&lt;/p>
&lt;h3 id="the-career-impact">The Career Impact&lt;/h3>
&lt;p>&lt;strong>Communication skills ranked #2&lt;/strong> among the most important factors when hiring engineering project managers—higher than technical proficiency at #10. Executive presence accounts for &lt;strong>26% of what it takes to get promoted&lt;/strong>. Google found their most productive employees weren&amp;rsquo;t those with highest technical skills but those with developed soft skills who could turn expertise into influence.&lt;/p>
&lt;p>Research shows conversation is a learnable skill with predictable patterns. For data engineers willing to treat communication with the same analytical rigor we bring to technical problems, the evidence suggests significant returns—career advancement, team performance, stakeholder trust.&lt;/p>
&lt;p>You don&amp;rsquo;t need to become someone different. You need to become more strategically curious. The research shows exactly how.&lt;/p>
&lt;h3 id="implementation-checklist">Implementation Checklist&lt;/h3>
&lt;p>&lt;strong>Before your next stakeholder meeting&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Brainstorm 3-5 questions based on their likely concerns&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Prepare topics that climb beyond status updates&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Plan one moment of levity (even just acknowledging shared reality)&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Identify what &lt;em>they&lt;/em> need from the conversation&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>During the conversation&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Ask at least one follow-up question for every major point&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Notice when you&amp;rsquo;re shift-responding vs support-responding&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Watch for lower-left quadrant (disengagement) and inject energy&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Check: am I making this about me or about them?&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>After the conversation&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> What topics worked? Which fell flat?&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Did I ask enough questions?&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Was there authentic laughter?&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Did they seem to get what they needed?&lt;/li>
&lt;/ul>
&lt;p>Start with one conversation. Apply one element of TALK. Notice the difference. Build from there.&lt;/p>
&lt;p>The same framework that makes you better at technical writing makes you better at stakeholder management. The same curiosity that drives your debugging makes you better at understanding people. You already have the skills—you just need to deploy them systematically.&lt;/p>
&lt;hr>
&lt;h2 id="further-reading">Further Reading&lt;/h2>
&lt;br>
&lt;p>Brooks, Alison Wood. &lt;em>Talk: The Science of Conversation and the Art of Being Ourselves&lt;/em>. Random House, 2024.&lt;/p>
&lt;p>Key research papers from Brooks and colleagues:&lt;/p>
&lt;ul>
&lt;li>Brooks, A. W., &amp;amp; John, L. K. (2018). &amp;ldquo;The Surprising Power of Questions.&amp;rdquo; &lt;em>Harvard Business Review&lt;/em>. (On question-asking in sales, dates, and negotiations)&lt;/li>
&lt;li>Huang, K., Yeomans, M., Brooks, A. W., Minson, J., &amp;amp; Gino, F. (2017). &amp;ldquo;It Doesn&amp;rsquo;t Hurt to Ask: Question-Asking Increases Liking.&amp;rdquo; &lt;em>Journal of Personality and Social Psychology&lt;/em>. (Speed dating and question types study)&lt;/li>
&lt;li>Brooks, A. W. (2014). &amp;ldquo;Get Excited: Reappraising Pre-Performance Anxiety as Excitement.&amp;rdquo; &lt;em>Journal of Experimental Psychology: General&lt;/em>. (Anxiety reframing research)&lt;/li>
&lt;/ul>
&lt;p>Related conversation science research:&lt;/p>
&lt;ul>
&lt;li>Derber, C. (1979). &lt;em>The Pursuit of Attention: Power and Ego in Everyday Life&lt;/em>. Oxford University Press. (Conversational narcissism and shift vs support responses)&lt;/li>
&lt;li>Edmondson, A. C. (2018). &lt;em>The Fearless Organization: Creating Psychological Safety in the Workplace for Learning, Innovation, and Growth&lt;/em>. Wiley. (Psychological safety research)&lt;/li>
&lt;/ul>
&lt;p>For technical professionals specifically:&lt;/p>
&lt;ul>
&lt;li>Google&amp;rsquo;s Project Aristotle research on team effectiveness: &lt;a href="https://rework.withgoogle.com/guides/understanding-team-effectiveness/" target="_blank" rel="noopener">https://rework.withgoogle.com/guides/understanding-team-effectiveness/&lt;/a>&lt;/li>
&lt;li>Stanford technical communication resources: &lt;a href="https://online.stanford.edu/technical-communication" target="_blank" rel="noopener">https://online.stanford.edu/technical-communication&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>Career Development</category><category>Leadership</category><category>Personal Development</category><category>Communication</category><category>Soft Skills</category><category>Leadership</category><category>Career Growth</category><category>Team Building</category><category>Stakeholder Management</category><category>Professional Development</category></item><item><title>When AI Stops Treating Us Like People: The Twin Crises Nobody Wants to Discuss</title><link>https://ghostinthedata.info/posts/2025/2025-12-12-ai-bubble/</link><pubDate>Fri, 12 Dec 2025 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2025/2025-12-12-ai-bubble/</guid><author>Chris Hillman</author><description>AI companies have burned $560 billion chasing $35 billion in revenue while public trust collapses. The intersection of financial instability and human cost could trigger a correction that reshapes the industry.</description><content:encoded>&lt;h3 id="the-moment-it-stops-being-theoretical">The moment it stops being theoretical&lt;/h3>
&lt;hr>
&lt;p>A friend was telling me about their recent job interviews. Not the questions they struggled with or the technical challenge they bombed—that&amp;rsquo;s normal interview anxiety. No, they were frustrated because they&amp;rsquo;d spent forty-five minutes talking to an AI chatbot that analyzed their &amp;ldquo;enthusiasm for remote work&amp;rdquo; and tracked their eye movements while asking whether they&amp;rsquo;d &amp;ldquo;misrepresent themselves 3.8x more than average candidates.&amp;rdquo;&lt;/p>
&lt;p>The speed with which I would exit an AI interview would rival even the most seasoned teenager&amp;rsquo;s alt-tab skills.&lt;/p>
&lt;p>But here&amp;rsquo;s the thing—this isn&amp;rsquo;t just another complaint about awkward technology or corporate efficiency theater. This moment, repeated thousands of times daily across hiring processes, represents something more fundamental breaking down. AI was supposed to eliminate the boring parts of work, the repetitive drudgery that didn&amp;rsquo;t require human judgment. Instead, it&amp;rsquo;s eliminating the human parts of work—the connection, the authenticity, the actual assessment of whether someone can do the job.&lt;/p>
&lt;p>And that pattern—AI deployed to dehumanize rather than enhance, to reduce costs rather than create value—isn&amp;rsquo;t limited to hiring. It&amp;rsquo;s the story of a $560 billion investment that&amp;rsquo;s generated just $35 billion in revenue. It&amp;rsquo;s the story of public trust collapsing from cautious optimism to outright skepticism. It&amp;rsquo;s the story of two interconnected crises that could reshape the entire technology industry if either one fails.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="what-in-the-dystopian-hellscape-is-going-on">What in the dystopian hellscape is going on&lt;/h3>
&lt;hr>
&lt;p>Let&amp;rsquo;s stay with that hiring example for a moment, because it crystallizes the problem. Companies now use AI to screen resumes, conduct first-round interviews, analyze voice patterns for &amp;ldquo;culture fit,&amp;rdquo; track eye movements for &amp;ldquo;engagement signals,&amp;rdquo; and evaluate &amp;ldquo;authenticity&amp;rdquo; through speech pattern analysis. The justification? Candidates are supposedly committing fraud &amp;ldquo;3.8x higher rates&amp;rdquo;—though higher than what, when, or where remains conveniently unspecified.&lt;/p>
&lt;p>So AI is good when companies use it to dehumanize candidates, but it&amp;rsquo;s bad when candidates use it to stay competitive in that same dehumanizing process. Got it.&lt;/p>
&lt;p>The fundamental issue, is that &amp;ldquo;AI was supposed to do the parts of the jobs humans found boring or repetitive.&amp;rdquo; Taking meeting notes. Generating documentation. Processing expense reports. The stuff that makes you think &amp;ldquo;surely a computer could do this.&amp;rdquo; Instead, we&amp;rsquo;re using it for the parts that desperately need human judgment—assessing whether someone will thrive in a role, whether they&amp;rsquo;ll mesh with a team, whether their particular combination of skills and experience makes them the right fit for this specific challenge.&lt;/p>
&lt;p>If I&amp;rsquo;m hiring a human for a role, I don&amp;rsquo;t care about grading their every word or analyzing their eye movement patterns. Doing so is fundamentally counterproductive since it encourages candidates to be as boring and uninteresting as possible while maintaining perfect eye contact and regurgitating perfect phrases, lest they don&amp;rsquo;t get the job.&lt;/p>
&lt;p>Honestly? I don&amp;rsquo;t think I want to hang out with the people who AI likes anyway.&lt;/p>
&lt;p>But this pattern—deploying AI in ways that optimize for the wrong things—appears everywhere once you start looking. McDonald&amp;rsquo;s AI drive-thru adding 260 Chicken McNuggets to orders. Klarna claiming AI &amp;ldquo;did the work of 700 agents&amp;rdquo; before quietly reversing course when quality tanked. Google&amp;rsquo;s AI Overview suggesting you add glue to pizza. These aren&amp;rsquo;t bugs. They&amp;rsquo;re symptoms of a systematic misapplication of technology by companies that haven&amp;rsquo;t figured out what problems AI actually solves.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-numbers-that-should-terrify-you">The numbers that should terrify you&lt;/h3>
&lt;hr>
&lt;p>OpenAI, the company most synonymous with the AI revolution, lost &lt;strong>$5 billion in 2024 on revenue of just $3.7 billion&lt;/strong>. Let that sink in. They&amp;rsquo;re spending $2.25 for every dollar they earn. The projected loss for 2025? $14.4 billion. Between 2023 and 2028, the company expects cumulative losses of &lt;strong>$44 billion&lt;/strong> before potentially—possibly, theoretically—reaching cash-flow positive status in 2029 or 2030.&lt;/p>
&lt;p>Despite these staggering losses, OpenAI&amp;rsquo;s valuation rocketed from $157 billion in October 2024 to somewhere between $300-500 billion today. That&amp;rsquo;s a tripling in twelve months with no profitability in sight. These aren&amp;rsquo;t valuations based on business fundamentals. They&amp;rsquo;re bets that future revenue will somehow, miraculously, justify present spending.&lt;/p>
&lt;p>Anthropic tells the same story with different numbers. Revenue jumped from $1 billion annually in 2024 to a projected $9 billion by end of 2025—impressive growth, right? Except they&amp;rsquo;re burning through &lt;strong>$2.7-3 billion annually&lt;/strong> and don&amp;rsquo;t expect to break even until 2028. The valuation inflated tenfold in eighteen months, from $18.4 billion to $183 billion. Again: not a sustainable business, but a bet on a future that may never materialize.&lt;/p>
&lt;p>The cost structure explains why profitability remains this elusive unicorn. Every ChatGPT query costs real money to compute. Every Claude conversation. Every AI-generated image. &lt;strong>Inference—running trained models—accounts for 80-90% of total AI lifetime expenses&lt;/strong>. Unlike traditional software where marginal costs approach zero as you scale, AI scales in reverse. More users mean more costs, linearly and predictably.&lt;/p>
&lt;p>Cursor, the AI coding assistant, reportedly sends 100% of its revenue to Anthropic just for model access. Perplexity AI spent &lt;strong>164% of revenue&lt;/strong> on compute providers in 2024. When your biggest customers operate at negative margins, when the companies building businesses on your platform lose money on every transaction, the entire ecosystem becomes brittle.&lt;/p>
&lt;p>The hyperscalers—Microsoft, Google, Amazon, Meta—are betting their futures on AI infrastructure with combined 2025 capital expenditures projected at &lt;strong>$325-380 billion&lt;/strong>, up 46% from 2024. These spending levels now exceed 1% of U.S. GDP quarterly. Microsoft committed $88.7 billion for fiscal 2025. Amazon projected $105-125 billion. Yet Meta&amp;rsquo;s CFO acknowledged that &amp;ldquo;genAI work is not going to be a meaningful driver of revenue this year or next year.&amp;rdquo;&lt;/p>
&lt;p>Read that again. Companies are spending hundreds of billions building infrastructure for revenue that their own financial officers admit won&amp;rsquo;t materialize for years. The gap between investment and returns has never been wider in technology history.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="when-the-guy-who-called-2008-places-a-billion-dollar-bet">When the guy who called 2008 places a billion-dollar bet&lt;/h3>
&lt;hr>
&lt;p>Michael Burry isn&amp;rsquo;t some permabear shouting about every market downturn. He&amp;rsquo;s the investor who correctly identified the 2008 subprime mortgage crisis before it happened, who placed massive bets against the housing market when everyone called him crazy, who was proven devastatingly right when the entire financial system nearly collapsed.&lt;/p>
&lt;p>In early November 2025, Burry disclosed regulatory filings showing over &lt;strong>$1 billion in put options against AI leaders&lt;/strong>: $912 million against Palantir (66% of his portfolio) and $187 million against Nvidia. His thesis is explicit and detailed, laid out in his Substack newsletter &amp;ldquo;Cassandra Unchained.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;I am not claiming Nvidia is Enron. It is clearly Cisco,&amp;rdquo; Burry wrote. He&amp;rsquo;s identifying Nvidia as today&amp;rsquo;s equivalent of Cisco during the dot-com bubble—the essential hardware supplier powering a massive capital investment cycle that will eventually see overbuilt supply meet far less demand than expected.&lt;/p>
&lt;p>But his most damning observation concerns accounting practices. Burry projects that tech companies are &lt;strong>understating depreciation by $176 billion between 2026-2028&lt;/strong> by extending the useful life of AI computing equipment from realistic 2-3 year product cycles to artificially long periods. This accounting treatment inflates reported earnings—Oracle by 26.9%, Meta by 20.8%—creating the illusion of profitability where losses actually exist.&lt;/p>
&lt;p>His criticism of &amp;ldquo;circular financing&amp;rdquo; cuts deeper. OpenAI owns 10% of AMD while Nvidia invested $100 billion in OpenAI. Microsoft is both a major OpenAI shareholder and one of Nvidia&amp;rsquo;s largest customers (representing roughly 20% of Nvidia revenue). CoreWeave, funded by Nvidia, serves OpenAI&amp;rsquo;s compute needs. &amp;ldquo;True end demand is ridiculously small,&amp;rdquo; Burry observed. &amp;ldquo;Almost all customers are funded by their dealers.&amp;rdquo;&lt;/p>
&lt;p>When an investor who successfully predicted the last major financial crisis places billion-dollar bets against your industry and explicitly compares your accounting practices to pre-2008 mortgage fraud, that&amp;rsquo;s not speculation. That&amp;rsquo;s pattern recognition.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-trust-collapse-nobody-wants-to-acknowledge">The trust collapse nobody wants to acknowledge&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s the part where we connect the financial instability back to that AI interview experience. Because these crises aren&amp;rsquo;t separate—they&amp;rsquo;re feeding each other in ways that threaten the entire AI business model.&lt;/p>
&lt;p>According to Pew Research, &lt;strong>only 17% of Americans believe AI will have a positive impact&lt;/strong> on the U.S. over the next 20 years. A full 51% are more concerned than excited about AI&amp;rsquo;s increased use in daily life. These figures represent dramatic worsening since 2021, when optimism still outweighed concern.&lt;/p>
&lt;p>The gap between expert opinion and public sentiment has become a chasm. While 56% of AI experts see positive impact ahead, only 17% of the general public agrees. While 73% of experts expect positive employment effects, only 23% of the public shares that view. Either the experts are completely disconnected from reality, or the public has accurately identified risks the industry refuses to acknowledge.&lt;/p>
&lt;p>The trust erosion isn&amp;rsquo;t abstract. It&amp;rsquo;s rooted in lived experience. That AI interview that made you feel reduced to &amp;ldquo;an n-dimensional matrix.&amp;rdquo; The AI-generated customer service response that completely missed your problem. The hiring algorithm that rejected your application before a human ever saw it. The chatbot that confidently gave you wrong information. The image generator that couldn&amp;rsquo;t distinguish historical accuracy from Reddit jokes.&lt;/p>
&lt;p>That &amp;ldquo;authenticity is a limited resource that is diminishing every day.&amp;rdquo; Every time someone encounters AI deployed to reduce costs rather than add value, deployed to eliminate human judgment rather than enhance it, that person becomes incrementally more skeptical. Not of technology in general—of this specific application pattern where efficiency trumps everything else.&lt;/p>
&lt;p>Consumer behavior reflects this skepticism in ways that directly threaten revenue. A Washington State University study found that products described as &amp;ldquo;AI-powered&amp;rdquo; are consistently less popular with consumers. Gartner surveys show &lt;strong>64% of customers prefer companies NOT use AI for customer service&lt;/strong>, with 53% willing to switch to competitors that don&amp;rsquo;t. Only &lt;strong>3% of smartphone owners&lt;/strong> are willing to pay extra for AI features.&lt;/p>
&lt;p>Three percent.&lt;/p>
&lt;p>When you&amp;rsquo;re building a business model that depends on massive consumer adoption and widespread willingness to pay premium prices, 3% willingness to pay is a death sentence. It means the revenue models underpinning all those billion-dollar valuations are built on fundamentally wrong assumptions about demand.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-enterprise-adoption-crisis">The enterprise adoption crisis&lt;/h3>
&lt;hr>
&lt;p>Maybe consumer skepticism doesn&amp;rsquo;t matter, you might argue. Maybe the real money is in enterprise. Except enterprise adoption is stalling for the same reasons, just with bigger price tags attached to the disappointment.&lt;/p>
&lt;p>The MIT NANDA study found that &lt;strong>95% of enterprise AI pilots yield zero measurable business return&lt;/strong> despite $30-40 billion in GenAI investment. BCG reports 74% of companies haven&amp;rsquo;t seen tangible value from AI initiatives. McKinsey&amp;rsquo;s surveys show 80%+ of organizations haven&amp;rsquo;t realized significant bottom-line AI impact. Gartner predicts &lt;strong>30% of GenAI projects will be abandoned by end of 2025&lt;/strong> due to poor data quality, escalating costs, or unclear value.&lt;/p>
&lt;p>Monte Carlo&amp;rsquo;s 2024 survey of data professionals reveals the pressure cooker: &lt;strong>100% feel pressure&lt;/strong> from leadership to implement GenAI strategy, yet &lt;strong>90% believe their leaders do NOT have realistic expectations&lt;/strong> for what&amp;rsquo;s technically feasible or capable of driving business value. Only 12% of companies have dedicated AI teams—meaning &lt;strong>84% rely on existing data teams&lt;/strong> to figure it out.&lt;/p>
&lt;p>This is the reality on the ground for data professionals. Leadership demands AI transformation. Budgets flow to AI initiatives. But the actual work of implementation falls to teams who know the technology isn&amp;rsquo;t mature enough, the data isn&amp;rsquo;t clean enough, the use cases aren&amp;rsquo;t well-defined enough. The pressure to deploy anyway—to show &amp;ldquo;innovation,&amp;rdquo; to justify spending, to keep up with competitors—leads to implementations that fail quietly, generate minimal value, and burn credibility.&lt;/p>
&lt;p>The case studies pile up. McDonald&amp;rsquo;s ended its three-year AI drive-thru partnership with IBM in June 2024 after viral videos showed the system adding 260 Chicken McNuggets to orders and repeatedly misinterpreting requests. Klarna initially claimed AI was &amp;ldquo;doing the work of 700 agents&amp;rdquo; after cutting 22% of its workforce, then reversed course with a &amp;ldquo;major hiring initiative&amp;rdquo; in May 2025. The CEO admitted AI was actually &amp;ldquo;doing work of 700 really bad agents&amp;rdquo;—quality decline cost customers, and the rollback cost approximately $15 million with no net gain.&lt;/p>
&lt;p>IBM&amp;rsquo;s Watson Health, once touted as transformative for cancer diagnosis, never reached production use, ran over budget, and was eventually sold for approximately $1 billion after multi-billion dollar investment. The Humane Ai Pin and Rabbit R1 represent hardware category failures, with slashed prices following weak sales and critical reviews noting the products solve &amp;ldquo;problems that don&amp;rsquo;t exist.&amp;rdquo;&lt;/p>
&lt;p>Each failure compounds the trust deficit. Each over-promised and under-delivered implementation makes the next pitch harder. Each news story about AI gone wrong creates more skeptics who start questioning whether this technology delivers value or just disrupts things that were working fine.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-2008-playbook-now-in-tech">The 2008 playbook, now in tech&lt;/h3>
&lt;hr>
&lt;p>The parallels between current AI investment patterns and pre-2008 financial crisis have moved from concerning to alarming. Deutsche Bank is now using &lt;strong>Synthetic Risk Transfers (SRTs) to hedge AI infrastructure loans&lt;/strong>—structures that analysts say &amp;ldquo;strongly resemble CDOs and CDSs that amplified the 2008 financial crisis.&amp;rdquo; Tech companies shift AI spending into Special Purpose Vehicles (SPVs) to obscure true costs, echoing Enron&amp;rsquo;s infamous off-balance-sheet accounting.&lt;/p>
&lt;p>The leverage buildup mirrors pre-crisis patterns. Goldman Sachs reports hyperscalers took on &lt;strong>$121 billion in debt&lt;/strong> over the past year—a 300%+ increase from typical levels. Meta raised $29 billion in debt for a single Louisiana data center. Oracle operates at a &lt;strong>500% debt-to-equity ratio&lt;/strong> to compete in AI data centers. Morgan Stanley estimates Big Tech will spend $3 trillion on AI infrastructure through 2028, with cash flows covering only half.&lt;/p>
&lt;p>The dot-com parallels are equally sobering. In the late 1990s, &lt;strong>80+ million miles of fiber optic cable&lt;/strong> were laid based on inflated demand projections; 85-95% remained unused (&amp;ldquo;dark fiber&amp;rdquo;) years after the bubble burst. Today&amp;rsquo;s equivalent is the massive AI data center buildout—Meta constructing a facility covering a &amp;ldquo;significant part of Manhattan&amp;rdquo;—predicated on demand growth that may never materialize. The $500 billion Stargate Project promises unprecedented AI infrastructure investment for enterprise AI adoption that&amp;rsquo;s already stalling.&lt;/p>
&lt;p>The NASDAQ took &lt;strong>15 years to recover&lt;/strong> from its 2000 peak. Cisco shares peaked at $80 and still haven&amp;rsquo;t returned to that level twenty-five years later. Those aren&amp;rsquo;t cautionary tales from ancient history—they&amp;rsquo;re previews of what happens when infrastructure investment radically outpaces actual demand.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="what-the-insiders-are-saying">What the insiders are saying&lt;/h3>
&lt;hr>
&lt;p>Perhaps most telling are the warnings from industry insiders themselves. Sam Altman, OpenAI&amp;rsquo;s CEO, acknowledges that investors are &amp;ldquo;overexcited about AI&amp;rdquo; and predicts &amp;ldquo;people will overinvest and lose money during this phase.&amp;rdquo; Google CEO Sundar Pichai warns of &amp;ldquo;elements of irrationality&amp;rdquo; and says &amp;ldquo;no company is going to be immune&amp;rdquo; if the bubble bursts. Jeff Bezos called the current environment &amp;ldquo;kind of an industrial bubble.&amp;rdquo;&lt;/p>
&lt;p>When the people building and funding AI companies publicly warn about bubble dynamics, when they&amp;rsquo;re literally saying investors will lose money, that&amp;rsquo;s not bearish speculation. That&amp;rsquo;s reality acknowledgment.&lt;/p>
&lt;p>Ray Dalio&amp;rsquo;s proprietary bubble indicator sits at approximately &lt;strong>80% of historical bubble peaks&lt;/strong>, with his AI chatbot estimating a 65-75% probability of meaningful correction in AI equities by end of 2026. Morgan Stanley&amp;rsquo;s Lisa Shalett warned of a &amp;ldquo;Cisco moment&amp;rdquo; within 24 months, describing the market as a &amp;ldquo;one-note narrative&amp;rdquo; entirely dependent on AI capital expenditures.&lt;/p>
&lt;p>The timing predictions are getting specific. Motley Fool analysts suggest the bubble could burst in 2025 due to unsustainable valuations. Ruchir Sharma of Rockefeller International predicts 2026 if interest rates rise. BeyondTrust researchers argue &amp;ldquo;artificial inflation of AI has already peaked in 2024.&amp;rdquo; A November 2025 Nature analysis noted that &amp;ldquo;many financial analysts now agree there is an &amp;lsquo;AI bubble,&amp;rsquo; some speculate it could finally burst in the next few months.&amp;rdquo;&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="preparing-for-what-comes-next">Preparing for what comes next&lt;/h3>
&lt;hr>
&lt;p>Whether the bubble bursts catastrophically or deflates gradually, data professionals can take specific steps to build resilient careers. The skills that remain human-essential include critical thinking and complex decision-making, cross-contextual reasoning (connecting insights across domains), ethical judgment in novel situations, relationship building and stakeholder management, and what researchers call &amp;ldquo;AI discernment&amp;rdquo;—knowing when to trust AI versus human judgment.&lt;/p>
&lt;p>Technical preparation should emphasize both breadth and depth. Core competencies remain essential: probability, statistics, Python, SQL. But add ML fundamentals including embeddings and model monitoring. Develop cloud platform expertise. Build governance and ethics capabilities that will be increasingly valuable as regulation tightens. Document measurable business impact, not just technical achievements—when budgets tighten, demonstrated ROI becomes the difference between retention and reduction.&lt;/p>
&lt;p>For data leaders, the most important preparation may be honest communication. Setting realistic expectations with executive leadership about what AI can deliver—and on what timeline—protects both the organization and the team. The companies that navigate the potential correction successfully will be those treating AI as a tool for specific problems rather than a magic solution for every challenge.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="what-happens-when-authenticity-runs-out">What happens when authenticity runs out&lt;/h3>
&lt;hr>
&lt;p>I desperately want to be surrounded by real people with real hobbies and passions. I don&amp;rsquo;t care if a candidate loves remote work, I care about their humanity. I don&amp;rsquo;t care if a candidate tripped over their words a few times, I care about their authenticity.&lt;/p>
&lt;p>This isn&amp;rsquo;t nostalgia for pre-AI times. It&amp;rsquo;s recognition that authenticity, human connection, genuine judgment—these aren&amp;rsquo;t inefficiencies to optimize away. They&amp;rsquo;re the core value proposition. They&amp;rsquo;re what makes work meaningful, what makes organizations effective, what makes products worth buying.&lt;/p>
&lt;p>When companies deploy AI to eliminate these elements—to reduce humans to vectors, to automate judgment, to replace connection with efficiency—they&amp;rsquo;re not just failing to capture value. They&amp;rsquo;re actively destroying it. The companies that will survive aren&amp;rsquo;t those with the most sophisticated AI. They&amp;rsquo;re those that figured out how to use AI to enhance human capability rather than replace it, to preserve authenticity rather than eliminate it.&lt;/p>
&lt;p>The crisis isn&amp;rsquo;t coming. It&amp;rsquo;s already here, building pressure daily in the gap between investment and returns, between promises and delivery, between what AI can actually do and what we&amp;rsquo;ve been told it will become. The question isn&amp;rsquo;t whether there will be a reckoning. The question is whether you&amp;rsquo;ll be positioned to navigate it when it arrives—and whether the companies you work for will still treat you like a person when they do.&lt;/p></content:encoded><category>Artificial Intelligence</category><category>Data Ethics</category><category>Industry Analysis</category><category>AI Bubble</category><category>Financial Crisis</category><category>Trust Crisis</category><category>Data Teams</category><category>AI Ethics</category><category>Career Strategy</category></item><item><title>The Invisible PR You're Building Right Now</title><link>https://ghostinthedata.info/posts/2025/2025-12-08-invisible-pr/</link><pubDate>Mon, 08 Dec 2025 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2025/2025-12-08-invisible-pr/</guid><author>Chris Hillman</author><description>Every interaction is either a deposit or withdrawal in someone's invisible ledger of you. Here's why the small moments matter most.</description><content:encoded>&lt;h3 id="the-email-that-changed-everything">The Email That Changed Everything&lt;/h3>
&lt;hr>
&lt;p>I received a meeting invite labeled &amp;ldquo;Check In&amp;rdquo; and it was to discuss news about the restructure. In a few months, my role would be gone. I was still processing it, sitting with that particular brand of numbness that comes when your career gets upended. In the following weeks, my inbox started filling up. LinkedIn messages. Texts. Emails from people I&amp;rsquo;d worked with years ago.&lt;/p>
&lt;p>They weren&amp;rsquo;t condolences. They were stories.&lt;/p>
&lt;p>&amp;ldquo;You probably don&amp;rsquo;t remember, but five years ago you sat with me for an hour and helped me understand why my pipeline kept failing. You didn&amp;rsquo;t have to do that—I wasn&amp;rsquo;t even on your team.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;The way you ran those workshops on values and purpose changed how I lead. I still use it now.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;You once told me I was ready for a promotion before I believed it myself. That conversation changed my career trajectory.&amp;rdquo;&lt;/p>
&lt;p>Message after message. Hundreds of them. People I&amp;rsquo;d mentored, challenged, collaborated with, sometimes clashed with. And sitting there, reading through them, I realized something:&lt;/p>
&lt;p>I&amp;rsquo;d been building a reputation without knowing it. Every small interaction, every moment I thought was insignificant—they&amp;rsquo;d all been compounding silently in the background.&lt;/p>
&lt;p>This is what I&amp;rsquo;ve come to think of as invisible PR.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-marble-jar-of-trust">The Marble Jar of Trust&lt;/h3>
&lt;hr>
&lt;p>Brené Brown talks about trust as a marble jar. Every time someone shows up for you, follows through, or demonstrates integrity in a small way, they put a marble in your jar. Every time they let you down or act inconsistently, they take marbles out. Over time, the jar fills or empties based on these micro-transactions.&lt;/p>
&lt;p>But here&amp;rsquo;s what Brené doesn&amp;rsquo;t emphasize enough: you can&amp;rsquo;t see the jar. You don&amp;rsquo;t know how many marbles you have until you need them. And you definitely don&amp;rsquo;t know which specific moments put marbles in or took them out.&lt;/p>
&lt;p>That&amp;rsquo;s the invisible part.&lt;/p>
&lt;p>We spend so much time thinking about the big moments—the presentations to executives, the crucial project deliveries, the performance reviews. But your reputation isn&amp;rsquo;t built there. It&amp;rsquo;s built in the margins. In the small interactions that feel inconsequential in the moment but echo for years.&lt;/p>
&lt;p>Let me show you what I mean.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="jennys-birthday-cake">Jenny&amp;rsquo;s Birthday Cake&lt;/h3>
&lt;hr>
&lt;p>About four years ago, someone on one of the teams I worked with was leaving. Her name was Jenny (the name is fictional for this purpose), quiet, did solid work but never made a lot of noise. The team was busy, everyone had deadlines, and honestly, the person leaving wasn&amp;rsquo;t particularly senior or well-connected. It would have been easy to let them slip out with just a generic goodbye email.&lt;/p>
&lt;p>Jenny organized a celebration lunch. She bought a cake, got people to sign stories in a card, and made sure the person felt genuinely appreciated on their last day. It took her maybe two hours of effort and fifty dollars of her own money.&lt;/p>
&lt;p>I watched her do this and filed it away. I didn&amp;rsquo;t say anything at the time—it wasn&amp;rsquo;t about praise. It was just data about who Jenny was when nobody was watching.&lt;/p>
&lt;p>Fast forward two years. A senior analyst position opened up, and I had two candidates who were roughly equal on technical skills. One was Jenny. The other had a slightly stronger resume on paper. But I kept thinking about that birthday cake. Not the cake itself, but what it revealed: Jenny showed up for people. She created culture. She understood that teams are built in the small moments, not just the big deliveries.&lt;/p>
&lt;p>Jenny got the promotion. She has no idea that birthday cake was part of the decision. And that&amp;rsquo;s exactly the point—she didn&amp;rsquo;t do it for recognition. She did it because that&amp;rsquo;s who she is. That&amp;rsquo;s invisible PR at work.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h2 id="when-it-goes-wrong">When It Goes Wrong&lt;/h2>
&lt;hr>
&lt;p>But here&amp;rsquo;s the thing about invisible PR: it works both ways. And it compounds in both directions.&lt;/p>
&lt;p>Jenny spent two hours and fifty dollars building trust. But you can destroy trust in thirty seconds. And the withdrawal compounds faster than the deposit ever did.&lt;/p>
&lt;p>I&amp;rsquo;ve watched this play out dozens of times over the years. The person who consistently doesn&amp;rsquo;t respond to requests from other teams. The leader who&amp;rsquo;s sharp with people when they&amp;rsquo;re under pressure. The colleague who&amp;rsquo;s helpful to senior stakeholders but dismissive to juniors. The teammate who commits to something in a meeting and then goes silent.&lt;/p>
&lt;p>None of these are career-ending moments. But they&amp;rsquo;re reputation-defining ones.&lt;/p>
&lt;p>Because here&amp;rsquo;s what happens: someone has that negative experience, and they tell a story. Not maliciously—just factually. &amp;ldquo;Oh, that team? Yeah, I tried working with them once and&amp;hellip;&amp;rdquo; or &amp;ldquo;Be careful with that person, they tend to&amp;hellip;&amp;rdquo; And that story spreads. It compounds. Five people hear it. Then fifty. Then it becomes the thing people &amp;ldquo;know&amp;rdquo; about you before they&amp;rsquo;ve ever met you.&lt;/p>
&lt;p>I&amp;rsquo;ve seen this up close with my own work. I do this exercise with new team members called personal maps. It&amp;rsquo;s simple: sit down with someone for 30-45 minutes and just understand their story. Where did they grow up? Where did they go to school? What did they study? What are their hobbies? What matters to them outside of work?&lt;/p>
&lt;p>It&amp;rsquo;s not an interrogation—it&amp;rsquo;s an invitation to be seen. And it&amp;rsquo;s remarkably effective at building trust quickly with new teams. I&amp;rsquo;ve done personal maps with over 200 people at this point. Some have been surface-level, some have gone deep and emotional. But they&amp;rsquo;ve all been valuable.&lt;/p>
&lt;p>Only one ever went badly.&lt;/p>
&lt;p>The person became immediately defensive. They didn&amp;rsquo;t want to share anything personal. They questioned why I was asking. They made it clear they thought the whole exercise was invasive and pointless. I could feel the tension in the room, so I stopped. &amp;ldquo;No problem,&amp;rdquo; I said. &amp;ldquo;We don&amp;rsquo;t have to do this.&amp;rdquo;&lt;/p>
&lt;p>I&amp;rsquo;ve told that story dozens of times—not to shame, but because people are curious if it has always worked. It taught me something important about consent and boundaries in team-building. Not everyone wants to connect the same way, and that&amp;rsquo;s okay.&lt;/p>
&lt;p>But think about how that interaction cascades. People will hear the story and form an impression of someone who treats connection as invasion. That&amp;rsquo;s their invisible PR working against them.&lt;/p>
&lt;p>Now, here&amp;rsquo;s the critical distinction: if this was a one-off, if other people had completely different experiences, the story would fade. But if people hear it and think, &amp;ldquo;Oh yeah, I had a similar experience with them&amp;hellip;&amp;quot;—if the pattern repeats—then you&amp;rsquo;ve got a reputation problem.&lt;/p>
&lt;p>That&amp;rsquo;s the asymmetry of invisible PR. Positive interactions need repetition and consistency to build trust. Negative interactions? They only need a pattern. Two or three similar stories, and suddenly that&amp;rsquo;s who you are in other people&amp;rsquo;s minds.&lt;/p>
&lt;p>You can&amp;rsquo;t control whether someone tells your story. But you can control whether the stories align or contradict each other.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-hard-truth-about-micro-transactions">The Hard Truth About Micro-Transactions&lt;/h3>
&lt;hr>
&lt;p>Here&amp;rsquo;s what I&amp;rsquo;ve come to understand: every single interaction you have is either a deposit or a withdrawal in someone&amp;rsquo;s invisible ledger of you. You don&amp;rsquo;t get to choose which interactions matter. You don&amp;rsquo;t get to designate some as &amp;ldquo;important&amp;rdquo; and others as &amp;ldquo;throwaway.&amp;rdquo;&lt;/p>
&lt;p>The junior person you&amp;rsquo;re curt with in the hallway might be the hiring manager in three years. The colleague whose email you ignore might be your only advocate when you&amp;rsquo;re facing redundancy. The person whose birthday cake you buy might become your strongest reference when you&amp;rsquo;re job hunting.&lt;/p>
&lt;p>Or flip it around: the person you snap at under stress might tell that story for a decade. The email you send when you&amp;rsquo;re annoyed might define how an entire team sees you. The meeting where you&amp;rsquo;re dismissive might be the only interaction someone has with you before making a judgment about your character.&lt;/p>
&lt;p>You can&amp;rsquo;t control how people remember you. But you can control how you show up.&lt;/p>
&lt;p>This connects directly to the research on trust and psychological safety. Amy Edmondson&amp;rsquo;s work on psychological safety shows that teams perform better when people feel safe to take interpersonal risks—to speak up, to ask questions, to admit mistakes. But that safety isn&amp;rsquo;t created through policy or pronouncements. It&amp;rsquo;s created through thousands of small interactions where people experience acceptance rather than judgment.&lt;/p>
&lt;p>Your invisible PR is the sum of how you show up in these moments when you think nobody&amp;rsquo;s keeping score.&lt;/p>
&lt;p>Plot twist: everyone&amp;rsquo;s keeping score. They just don&amp;rsquo;t tell you.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-offer-i-make-every-new-team">The Offer I Make Every New Team&lt;/h3>
&lt;hr>
&lt;p>Whenever I inherit a new team or start leading a new group, I do something that makes some people uncomfortable. In our first team meeting, I tell them this:&lt;/p>
&lt;p>&amp;ldquo;I know you don&amp;rsquo;t know me yet. I know you&amp;rsquo;re taking a risk trusting me as your leader. So here&amp;rsquo;s what I&amp;rsquo;m going to offer: if you want to talk to anyone I&amp;rsquo;ve worked with in the past—anyone—I will personally help you get in touch with them. I will give you names, email addresses, LinkedIn profiles. You can ask them anything you want about what it&amp;rsquo;s like to work with me.&amp;rdquo;&lt;/p>
&lt;p>Some people think this is weird. Some think it&amp;rsquo;s a power move. But it&amp;rsquo;s neither. It&amp;rsquo;s confidence born from consistency.&lt;/p>
&lt;p>I can make this offer because I trust my invisible PR. I know that if you talk to people I&amp;rsquo;ve led, you&amp;rsquo;ll hear stories about me being demanding, about me challenging people, about me pushing them outside their comfort zones. Not everyone loved working with me—I&amp;rsquo;m not trying to be everyone&amp;rsquo;s friend. But you&amp;rsquo;ll also hear about me showing up, following through, investing in their growth, and treating them with respect even when we disagreed.&lt;/p>
&lt;p>That&amp;rsquo;s the invisible PR I&amp;rsquo;ve built. And it&amp;rsquo;s portable. It travels with me from team to team, role to role. It&amp;rsquo;s why, when I got the redundancy news, my first thought wasn&amp;rsquo;t panic—it was curiosity about what would come next. Because I knew the reputation I&amp;rsquo;d built would carry me through.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="inconvenience-vs-problem">Inconvenience vs. Problem&lt;/h3>
&lt;hr>
&lt;p>This is where we get to the distinction I&amp;rsquo;ve been thinking about a lot lately: the difference between an inconvenience and a problem.&lt;/p>
&lt;p>Losing your job is objectively bad news. But whether it&amp;rsquo;s an inconvenience or an existential problem depends entirely on the invisible PR you&amp;rsquo;ve built. If you&amp;rsquo;ve spent years burning bridges, ignoring relationships, and treating people as transactional resources, that job loss becomes a crisis. Your network is thin. Your references are weak. Your reputation precedes you in the wrong way.&lt;/p>
&lt;p>But if you&amp;rsquo;ve been making deposits—showing up for people, building trust, creating value even when it didn&amp;rsquo;t directly benefit you—that same job loss becomes navigable. Your network activates. People reach out. Opportunities surface. The transition becomes an inconvenience you&amp;rsquo;re managing rather than a catastrophe you&amp;rsquo;re surviving.&lt;/p>
&lt;p>I&amp;rsquo;m living this right now. Yes, it&amp;rsquo;s disruptive and stressful. But I&amp;rsquo;m also flooded with support, with referrals, with people wanting to help. Not because I asked for it, but because I&amp;rsquo;ve been building invisible PR for years without realizing it. Even to the extent of people wanting to know where I go, as they want to work with me wherever I am.&lt;/p>
&lt;p>The variations in work and life won&amp;rsquo;t always go your way. You&amp;rsquo;ll face setbacks, restructures, health issues, relationship challenges. Whether these remain inconveniences or escalate into existential problems often depends less on the event itself and more on the reservoir of trust you&amp;rsquo;ve built.&lt;/p>
&lt;p>Connection is the buffer. Community is the shock absorber. And both are built through invisible PR—one small interaction at a time.&lt;/p>
&lt;/br>
&lt;/br>
&lt;h3 id="the-work-that-matters-most">The Work That Matters Most&lt;/h3>
&lt;hr>
&lt;p>Your reputation is being built right now. Not in the big presentation next week or the annual review in three months. Right now. In how you respond to the next email. In whether you remember to thank someone. In your tone on the next Teams message. In whether you show up for the small things when nobody&amp;rsquo;s watching.&lt;/p>
&lt;p>You&amp;rsquo;re making deposits or withdrawals with every single interaction. The jar is filling or emptying. And you won&amp;rsquo;t know which moments mattered most until much later—maybe years later, maybe when you&amp;rsquo;re sitting at your desk reading messages from people whose marbles you earned without knowing it.&lt;/p>
&lt;p>Jenny didn&amp;rsquo;t know the birthday cake would matter. The person who attacked me over email didn&amp;rsquo;t know that interaction would define how I see their entire team. We never know in the moment. That&amp;rsquo;s why the only rational strategy is to treat every moment as if it matters.&lt;/p>
&lt;p>Because it does.&lt;/p>
&lt;p>Your invisible PR is working right now, compounding in ways you can&amp;rsquo;t see. The only question is: which direction is it compounding?&lt;/p>
&lt;p>When the moment comes that you need to draw on your reputation—and that moment always comes eventually—will you find a full marble jar or an empty one?&lt;/p>
&lt;p>Start filling it now. One small interaction at a time.&lt;/p></content:encoded><category>Personal Development</category><category>Leadership</category><category>Career Growth</category><category>Professional Relationships</category><category>Trust</category><category>Reputation</category><category>Leadership</category><category>Career Development</category><category>Team Culture</category></item></channel></rss>