<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Robson Müller</title>
    <description>The latest articles on DEV Community by Robson Müller (@rbmuller).</description>
    <link>https://dev.to/rbmuller</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1001805%2Ffc6b6042-f64e-4e4e-a6bd-b060414f4a00.jpeg</url>
      <title>DEV Community: Robson Müller</title>
      <link>https://dev.to/rbmuller</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rbmuller"/>
    <language>en</language>
    <item>
      <title>I built the "anti-Elementary" for dbt data quality and here's why</title>
      <dc:creator>Robson Müller</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:08:24 +0000</pubDate>
      <link>https://dev.to/rbmuller/i-built-the-anti-elementary-for-dbt-data-quality-and-heres-why-16ga</link>
      <guid>https://dev.to/rbmuller/i-built-the-anti-elementary-for-dbt-data-quality-and-heres-why-16ga</guid>
      <description>&lt;p&gt;Elementary is great. I use it. But after writing YAML for the 200th anomaly test, I asked myself: &lt;strong&gt;why am I teaching the tool what "normal" looks like when the data already knows?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/rbmuller/scherlok" rel="noopener noreferrer"&gt;Scherlok&lt;/a&gt; — a zero-config data quality monitor that learns your data's patterns and detects anomalies automatically. No YAML. No thresholds. No rules to maintain.&lt;/p&gt;

&lt;p&gt;It just shipped on the &lt;a href="https://hub.getdbt.com" rel="noopener noreferrer"&gt;dbt Package Hub&lt;/a&gt;, so you can install it as a native dbt package today. Here's what makes it different, and when you should use which.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core difference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Elementary&lt;/strong&gt; asks you to define what's normal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# schema.yml — per model, per test, per column&lt;/span&gt;
&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fct_orders&lt;/span&gt;
    &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;elementary.volume_anomaly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;timestamp_column&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
          &lt;span class="na"&gt;time_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day&lt;/span&gt;
          &lt;span class="na"&gt;sensitivity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You configure the timestamp column, the time bucket, the sensitivity, the training period, the detection method. For every model. For every test. It's powerful, but it's work — and the configurations accumulate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scherlok&lt;/strong&gt; inverts the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scherlok connect postgres://user:pass@host/db
scherlok investigate    &lt;span class="c"&gt;# profiles everything&lt;/span&gt;
scherlok watch          &lt;span class="c"&gt;# detects anomalies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three commands. Done. No YAML, no per-model configuration, no timestamp columns to specify. Scherlok profiles every table and column automatically, learns the distributions, and flags when something deviates from the learned baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  An honest comparison
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend Scherlok is better at everything. Here's where each tool wins:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Elementary&lt;/th&gt;
&lt;th&gt;Scherlok&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30-60 min (package + config per model)&lt;/td&gt;
&lt;td&gt;5 min (connect + investigate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Granular per-model YAML&lt;/td&gt;
&lt;td&gt;Zero (auto-discovers everything)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Detection methods&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple algorithms, configurable&lt;/td&gt;
&lt;td&gt;Shewhart control limits (mean ± kσ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;dbt integration depth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep (runs inside dbt)&lt;/td&gt;
&lt;td&gt;CLI + native dbt package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Beautiful hosted UI (Elementary Cloud)&lt;/td&gt;
&lt;td&gt;Self-contained HTML file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lineage-aware alerts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (reads manifest.json)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI explanations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Claude, opt-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (AI agents can run checks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-dbt usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires dbt&lt;/td&gt;
&lt;td&gt;Works with or without dbt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free package, paid Cloud&lt;/td&gt;
&lt;td&gt;Free, forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connectors&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;dbt adapters&lt;/td&gt;
&lt;td&gt;Postgres, BigQuery, Snowflake, MySQL, DuckDB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use Elementary when&lt;/strong&gt; you want fine-grained control over every detection parameter, need the hosted dashboard for non-technical stakeholders, or your team already has the YAML config in place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Scherlok when&lt;/strong&gt; you want anomaly detection running in minutes without writing configuration, need it outside of dbt, or want AI-powered explanations on your alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dbt package — best of both worlds
&lt;/h2&gt;

&lt;p&gt;As of v0.9.0, Scherlok ships as a &lt;a href="https://hub.getdbt.com" rel="noopener noreferrer"&gt;native dbt package&lt;/a&gt;. This means you can use Scherlok's tests inside your existing &lt;code&gt;schema.yml&lt;/code&gt; without leaving dbt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# packages.yml&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbmuller/scherlok&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=0.9.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;1.0.0"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# schema.yml&lt;/span&gt;
&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fct_orders&lt;/span&gt;
    &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;scherlok.volume_anomaly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sensitivity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.0&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;scherlok.row_count_between&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;min_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;email&lt;/span&gt;
        &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;scherlok.not_null_proportion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;max_rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;updated_at&lt;/span&gt;
        &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;scherlok.recency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six tests available:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instant&lt;/strong&gt; (no setup, works on first &lt;code&gt;dbt test&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;not_null_proportion&lt;/code&gt; — NULL rate exceeds threshold&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;row_count_between&lt;/code&gt; — row count outside bounds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recency&lt;/code&gt; — data is stale&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unique_proportion&lt;/code&gt; — cardinality drops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Auto-learning&lt;/strong&gt; (builds baseline over time):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;volume_anomaly&lt;/code&gt; — row count outside Shewhart control limits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null_anomaly&lt;/code&gt; — NULL rate spike vs historical&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The auto-learning tests use an incremental &lt;code&gt;scherlok_metrics&lt;/code&gt; model that runs with &lt;code&gt;dbt run&lt;/code&gt; and captures row counts per model. After 5 runs, the anomaly tests activate automatically. Before that, they pass silently — first runs are baseline, not false alarms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI — where Scherlok really shines
&lt;/h2&gt;

&lt;p&gt;The dbt package is one distribution channel. The CLI is where the "zero-config" promise delivers the most value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect to any warehouse
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scherlok connect postgres://user:pass@host/db
scherlok connect bigquery://project-id/dataset
scherlok connect snowflake://account/database/schema
scherlok connect mysql://user:pass@host/db
scherlok connect duckdb:///path/to/file.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Profile everything in one command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;scherlok investigate

  Profiling 12 tables...
  ✓ &lt;span class="nb"&gt;users&lt;/span&gt;         — 45,231 rows, 8 columns
  ✓ orders        — 1,203,847 rows, 15 columns
  ✓ products      — 892 rows, 12 columns
  ...
  Done. Profiles saved.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Detect and alert
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;scherlok watch &lt;span class="nt"&gt;--webhook&lt;/span&gt; &lt;span class="nv"&gt;$SLACK_URL&lt;/span&gt; &lt;span class="nt"&gt;--explain&lt;/span&gt;

  🔴 CRITICAL  orders    volume_drop     Row count dropped 52%
  🟡 WARNING   &lt;span class="nb"&gt;users     &lt;/span&gt;null_increase   Column &lt;span class="s2"&gt;"email"&lt;/span&gt;: NULL rate 2.1% → 18.7%

  💡 Hypothesis: The orders volume drop correlates with stg_orders
     upstream. The &lt;span class="nb"&gt;source &lt;/span&gt;table likely stopped receiving data from
     the payments API after the 2026-08-09 deploy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--explain&lt;/code&gt; flag is opt-in. When anomalies fire, Scherlok sends the anomaly batch (not your data — just the anomaly messages) to Claude for a root-cause hypothesis. On dbt projects, it includes upstream lineage so cascading failures get traced to the source model. Costs under $0.003 per run. If the API fails, the alert is delivered unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI in one line
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scherlok ci $DATABASE_URL --fail-on critical --webhook $SLACK_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI agents can use it directly
&lt;/h3&gt;

&lt;p&gt;Scherlok ships an MCP server since v0.7.0. Claude Code or Claude Desktop can profile tables and detect anomalies without you typing commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scherlok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scherlok-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"SCHERLOK_CONNECTION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql://..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent gets &lt;code&gt;list_tables&lt;/code&gt;, &lt;code&gt;investigate&lt;/code&gt;, &lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;history&lt;/code&gt;, and &lt;code&gt;check&lt;/code&gt; as tools. Credentials stay server-side. Every operation is read-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical decisions
&lt;/h2&gt;

&lt;p&gt;A few choices that might be interesting if you're building in this space:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shewhart over ML.&lt;/strong&gt; I chose &lt;a href="https://en.wikipedia.org/wiki/Control_chart" rel="noopener noreferrer"&gt;Shewhart control limits&lt;/a&gt; (mean ± kσ) over fancy ML models. It's boring, debuggable, and transparent. When a test fires, you can explain why in one sentence: "row count was 3.2 standard deviations below the 30-day average." No black box. The tradeoff: no seasonality modeling. If your data has strong weekly patterns, you'll get false positives on Mondays. For most tables, Shewhart catches 90% of real incidents with zero tuning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQLite for profiles.&lt;/strong&gt; Your baseline data lives in &lt;code&gt;~/.scherlok/profiles.db&lt;/code&gt;. You can inspect it with &lt;code&gt;sqlite3&lt;/code&gt;, copy it to another machine, or sync it to S3/GCS/Azure for team use. No hosted service required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-only by contract.&lt;/strong&gt; Scherlok never writes to your warehouse. The test suite enforces this. Your DBA will appreciate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One repo, two channels.&lt;/strong&gt; The same GitHub repo (&lt;code&gt;rbmuller/scherlok&lt;/code&gt;) ships as a PyPI package (&lt;code&gt;pip install scherlok&lt;/code&gt;) and a dbt package (&lt;code&gt;package: rbmuller/scherlok&lt;/code&gt;). &lt;code&gt;pyproject.toml&lt;/code&gt; and &lt;code&gt;dbt_project.yml&lt;/code&gt; coexist at the root without conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use Scherlok
&lt;/h2&gt;

&lt;p&gt;Being honest about limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strong seasonality patterns&lt;/strong&gt; — Shewhart control limits don't model day-of-week or seasonal cycles. You'll need to tune &lt;code&gt;sensitivity&lt;/code&gt; higher or use Elementary's timestamp-aware algorithms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom business rules&lt;/strong&gt; — "revenue must match between systems A and B" is a rule, not an anomaly. Use &lt;code&gt;dbt test&lt;/code&gt; or Great Expectations for that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-technical stakeholders&lt;/strong&gt; — Elementary Cloud's dashboard is built for people who don't live in the terminal. Scherlok's HTML dashboard is useful but developer-oriented.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;scherlok
scherlok connect YOUR_DATABASE_URL
scherlok investigate
scherlok watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or as a dbt package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbmuller/scherlok&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=0.9.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;1.0.0"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/rbmuller/scherlok" rel="noopener noreferrer"&gt;github.com/rbmuller/scherlok&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/scherlok/" rel="noopener noreferrer"&gt;pypi.org/project/scherlok&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dbt Hub:&lt;/strong&gt; &lt;a href="https://hub.getdbt.com" rel="noopener noreferrer"&gt;hub.getdbt.com&lt;/a&gt; (pending approval)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed. 344 tests. 5 connectors. Zero config.&lt;/p&gt;

</description>
      <category>python</category>
      <category>dbt</category>
      <category>opensource</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Valor Software Medusa app and GitHub</title>
      <dc:creator>Robson Müller</dc:creator>
      <pubDate>Thu, 12 Jan 2023 12:39:02 +0000</pubDate>
      <link>https://dev.to/rbmuller/valor-software-medusa-app-and-github-4bon</link>
      <guid>https://dev.to/rbmuller/valor-software-medusa-app-and-github-4bon</guid>
      <description>&lt;h1&gt;
  
  
  The Problem.
&lt;/h1&gt;

&lt;p&gt;Here at Valor Software we had the challenge of analyzing some metrics from developers productivity. So, we've started questioning ourselves; What are the core daily activities of a developer? On a macro level we could say that is to deliver clean and reliable code on top of a consistent base, but with which commit frequency? And how to correlate code deployment with bugs and other stuff? Is this related to teams, to specific projects or even to the used technology stack? &lt;/p&gt;

&lt;p&gt;In Data Science, we usually start our investigations based on the scientific method ( and &lt;a href="https://www.datascience-pm.com/crisp-dm-2/" rel="noopener noreferrer"&gt;CRISP-DM&lt;/a&gt;) approach), to know better the target situation, its surroundings and mainly doing the questions that will drive us to catch the root cause of our problems. &lt;/p&gt;

&lt;p&gt;OK, so much fancy stuff. What is the relationship between all of this and the Valor Software Medusa project? We realized that GitHub is a good provider of data when it comes to developers productivity as we can track information about repositories, commits, tests and much more, this drove us to develop a Data Pipeline to extract and process data from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution.
&lt;/h2&gt;

&lt;p&gt;We have designed a business framework containing the daily core activities of a developer and have splitted it into four Pilars, when it comes to GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version Control Management &lt;/li&gt;
&lt;li&gt;Compatibility&lt;/li&gt;
&lt;li&gt;Infrastructure &lt;/li&gt;
&lt;li&gt;Team &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem organization is a very important step in Data Science/Engineering projects as it gives us the direction of what data sources we should consume what metrics we are willing to build, as makes no sense to build a rocket dashboard, having metrics that are not related to the operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it was built.
&lt;/h3&gt;

&lt;p&gt;The idea is based in hitting the GitHub API, collecting the necessary metrics from a range of endpoints, Saving the result in an intermediary layer and than load it to the DataBase. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why do we use an intermediary layer rather than saving it directly to the DataBase? &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We usually follow this approach in order to make the pipeline more resilient, imagine we spent hours iterating over API pagination, and than some error occur, in some cases we can suffer data loss and have to restart it all again, saving the data in an intermediary layer such as AWS S3 or Google Storage make the pipeline to execute in steps, and also allows us to process the data later, use it in Data Science experiments and so on. &lt;/p&gt;

&lt;p&gt;The application design is based in OOP and contains the following mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Deployment&lt;/li&gt;
&lt;li&gt;Pipeline orchestration&lt;/li&gt;
&lt;li&gt;Storage layer&lt;/li&gt;
&lt;li&gt;Visualization layer &lt;/li&gt;
&lt;li&gt;Infrastructure management &lt;/li&gt;
&lt;li&gt;Data pipeline source code &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  App deployment
&lt;/h3&gt;

&lt;p&gt;The application deployment is done using Docker and the containers needed to run Airflow with it's services are all described in a docker-compose file &lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline Orchestration
&lt;/h3&gt;

&lt;p&gt;The triggering of the data ingestion and processing jobs can be done throughout the Airflow UI, which uses DAGs to manage all the working code (DAGs stands by Direct Acyclic Graphs and are responsible to manage the tasks of the data pipeline) &lt;/p&gt;

&lt;h3&gt;
  
  
  Storage Layers
&lt;/h3&gt;

&lt;p&gt;There are two of them in this project, one as the intermediary layer, that stores the raw data from the API calls, organizing it into year/month/day of the request. The other one is the Data warehouse, a database based in PostgreSQL to store the tables containing the processed information. &lt;/p&gt;

&lt;h3&gt;
  
  
  Visualization layer
&lt;/h3&gt;

&lt;p&gt;The chosen app for visualizing data at our data warehouse is Apache &lt;a href="https://superset.apache.org" rel="noopener noreferrer"&gt;Superset&lt;/a&gt; it is an incredible tool considering it is free, and from my experience it has most of the features we can find the famous and paid Power BI. In addition, Superset is also ready for streaming needs and is cluster scalable. &lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure management
&lt;/h3&gt;

&lt;p&gt;The infra is deployed at the Google Cloud, and the necessary resources are created and managed by &lt;a href="https://www.terraform.io" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Data pipeline source code
&lt;/h3&gt;

&lt;p&gt;The code design is based in two strong objects which are responsible to interact with each other to ingest, process and write data from different data sources to different destinations, all based in json configuration files. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Hook&lt;/strong&gt;: Responsible to interface with external services, like the GitHub API, the Cloud storage (GCP) and the Data warehouse, holding its credentials and authentication methods. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Operator:&lt;/strong&gt; Responsible to operate (call) different methods on top of the data and trigger functions like: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download data&lt;/li&gt;
&lt;li&gt;Filter data &lt;/li&gt;
&lt;li&gt;Calculate metrics&lt;/li&gt;
&lt;li&gt;Upload logs &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;also holding configurations, restrictions and other information about the data object that is being ingested. &lt;/p&gt;

&lt;p&gt;For the specific case of GitHub, the authentication is done using the account token. It can be a User or an Organization, resulting in a flexible object as User and Organizations have different API calls to retrieve similar categories of data. Just like in the representation bellow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn35x7ucq6hyuvdm557z7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn35x7ucq6hyuvdm557z7.png" alt="Image description" width="687" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the data is requested from the API, based in the configuration file, it is stored in Google Cloud Storage, once the data is properly downloaded to this intermediary layer, the Operator calls the configuration file to filter the correct information from the raw data, opening a way to the next step: data transformation, and metrics calculation. &lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The creation of data pipelines can become something really complex if we do not care about details like the creation of generic functions, the config files approach, as per reuse code and make the processing more flexible. &lt;/p&gt;

&lt;p&gt;So, at the end we have a data warehouse to consume data, making it available to the Medusa App and to the dashboard tool. This way, managers, product managers or PO's can create their own views, test their hypothesis or even find answers with the power of the data. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc7eg20nok2n5otn7bqtj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc7eg20nok2n5otn7bqtj.jpg" alt="Image description" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View of the data pipeline architecture &lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
  </channel>
</rss>
