<?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: Dhaivat Jambudia</title>
    <description>The latest articles on DEV Community by Dhaivat Jambudia (@dhaivat_jambudia).</description>
    <link>https://dev.to/dhaivat_jambudia</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3857020%2Fb134fbe2-5fa0-404d-a53d-1005d73a0d59.png</url>
      <title>DEV Community: Dhaivat Jambudia</title>
      <link>https://dev.to/dhaivat_jambudia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhaivat_jambudia"/>
    <language>en</language>
    <item>
      <title>Building a Production-Ready ML System for Supply Chain Late Delivery Risk Prediction</title>
      <dc:creator>Dhaivat Jambudia</dc:creator>
      <pubDate>Fri, 24 Apr 2026 14:35:05 +0000</pubDate>
      <link>https://dev.to/dhaivat_jambudia/building-a-production-ready-ml-system-for-supply-chain-late-delivery-risk-prediction-1bbd</link>
      <guid>https://dev.to/dhaivat_jambudia/building-a-production-ready-ml-system-for-supply-chain-late-delivery-risk-prediction-1bbd</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: “Will This Order Arrive Late?”
&lt;/h2&gt;

&lt;p&gt;In any supply chain, late deliveries hurt customer trust and increase support costs. At order placement, logistics teams usually have no reliable way to know which orders will fail their delivery promise. Our client was flagging orders manually, based on gut feeling and experience no formal metric, no repeatability, and a lot of missed late deliveries.&lt;/p&gt;

&lt;p&gt;We were asked to build a system that predicts late delivery risk at the moment an order is placed, enabling proactive interventions: expedited shipping, customer notifications, priority routing. The model had to be production‑ready, reproducible, and monitorable. &lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Formulation – It’s All About the Cost of Missing a Late Order
&lt;/h2&gt;

&lt;p&gt;We framed it as binary classification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Target: Late_delivery_risk (0 = on time, 1 = late)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Primary metric: F2‑score because missing a late delivery (false negative) is twice as costly as a false alarm.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Guardrail: Recall ≥ 0.80 (catch at least 80% of true late orders)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dataset contained 180,519 orders with 53 columns. Class distribution was near‑balanced (54.8% late, 45.2% on time).&lt;/p&gt;

&lt;h2&gt;
  
  
  First Step: Remove Leakage
&lt;/h2&gt;

&lt;p&gt;The raw data included columns that would be impossible to know at order placement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Delivery Status – literally the target&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Days for shipping (real) – actual transit days, known only after delivery&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;shipping date (DateOrders) – when the order actually left the warehouse&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order Status – may reflect post‑order events (we dropped it to be safe)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also dropped PII (email, names, password), pure IDs, 100% null columns (Product Description), and columns with extreme cardinality (e.g., Order City with 3,597 unique values).&lt;/p&gt;

&lt;p&gt;After cleaning, we kept ~30 features: scheduled shipping days, benefit per order, shipping mode, market, customer segment, order hour, day of week, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Engineering – Preventing the #1 Silent Bug
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preprocessor&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ColumnTransformer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;numeric&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;imputer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SimpleImputer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;median&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scaler&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;StandardScaler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;NUMERIC_COLS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;onehot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;imputer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SimpleImputer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;constant&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UNKNOWN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;encoder&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OneHotEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle_unknown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;ONEHOT_COLS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;imputer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SimpleImputer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;constant&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UNKNOWN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;encoder&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TargetEncoder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="c1"&gt;# out‑of‑fold encoding
&lt;/span&gt;        &lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;TARGET_ENC_COLS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At serving time, the same frozen artifact is loaded – zero chance of applying a different imputation median or a different one‑hot encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  MLOps Architecture: Ten Stages, Five Pipelines
&lt;/h2&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%2Fiq1getfyv6pmpboqj3ah.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%2Fiq1getfyv6pmpboqj3ah.png" alt="10 stages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These stages are implemented as five pipelines in ZenML:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Training pipeline (stages 1‑6)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inference pipeline (stage 7)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Drift detection pipeline (stage 9)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitoring pipeline (stage 8)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retraining pipeline (stage 10)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All experiments are tracked in MLflow, integrated with ZenML. Every run logs hyperparameters, metrics, the exact dataset version (ZenML artifact ID), and the git commit hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Training &amp;amp; Evaluation – Why We Chose LightGBM
&lt;/h2&gt;

&lt;p&gt;We trained a series of models, starting from a Dummy Classifier (majority class) as the absolute floor.&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%2Fklabil6o79hpb4yxj5wr.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%2Fklabil6o79hpb4yxj5wr.png" alt="training and eval"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We used stratified 80/10/10 train/validation/test splits. The test set is touched exactly once at final evaluation only.&lt;/p&gt;

&lt;p&gt;The success criteria for production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;F2‑score ≥ 0.75 on held‑out test set&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recall ≥ 0.80&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pipeline is reproducible – same data + same code = same result&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(After first run, we can revise the F2 threshold based on actual business value.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Batch Inference with Human‑in‑the‑Loop Promotion
&lt;/h2&gt;

&lt;p&gt;We chose batch inference because order volume is moderate (~500 orders/day). Predictions are refreshed hourly. This is enough to trigger expedited shipping or customer notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model Promotion Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Training run completes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Evaluation gates pass (F2 &amp;gt; 0.75, recall ≥ 0.80).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Model is automatically registered in MLflow as “Staging”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A human reviews metrics against the current production model in MLflow UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Human approves → model promoted to “Production”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Previous production model is moved to “Archived” (retained for 90 days).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Rollback &amp;lt; 5 minutes&lt;/strong&gt;. If a bad model slips through, we simply promote the archived version back to Production via MLflow UI. Because predictions are stored with a model_version column, the dashboard can immediately start using the restored model without data deletion.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Design: How New Orders Get Scored
&lt;/h2&gt;

&lt;p&gt;We needed a reliable way to identify unscored orders and write predictions without double‑writing. The solution uses an absence check and an idempotent insert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prediction Table Schema
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;predictions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;prediction_id&lt;/span&gt;   &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt;        &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;late_risk_score&lt;/span&gt; &lt;span class="nb"&gt;FLOAT&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;late_risk_score&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;predicted_late&lt;/span&gt;  &lt;span class="nb"&gt;SMALLINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicted_late&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;scored_at&lt;/span&gt;       &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;model_version&lt;/span&gt;   &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- MLflow model version tag&lt;/span&gt;
    &lt;span class="n"&gt;pipeline_run_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- ZenML run ID for audit&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting Unscored Orders
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;predictions&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;model_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;current_version&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Idempotent Write
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;predictions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;late_risk_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_late&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="n"&gt;model_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline_run_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring &amp;amp; Drift Detection – Don’t Wait for Ground Truth
&lt;/h2&gt;

&lt;p&gt;Ground truth (Late_delivery_risk) arrives only after delivery – days after the prediction. That’s too late to notice data distribution changes. We use Evidently to detect drift in input features as soon as new orders come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Reality What We Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Start with the simplest deployment that works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At 500 orders/day, a single scheduled pipeline writing to one database table is correct. We did not need Kubernetes, real‑time APIs, or feature stores. Adding complexity too early kills velocity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Train‑serving skew is the silent killer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ackaging the fitted sklearn.Pipeline as a single artifact and reloading it in inference is non‑negotiable. Every imputation median, one‑hot mapping, and target encoding must be frozen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Idempotency saves weekends.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ON CONFLICT DO NOTHING and the absence‑based order detection meant we never had to worry about replaying or cleaning up duplicate predictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Human promotion gates build trust.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first production model was promoted manually after reviewing slice metrics. After three stable cycles, we may automate promotion – but not before. Trust is earned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Monitor input drift, not just output.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We caught a carrier SLA change because Days for shipment (scheduled) distribution shifted. The Slack alert arrived two days before any ground truth label was available we fixed the feature mapping before any performance degradation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;We deferred a few items that were not needed for MVP:&lt;/p&gt;

&lt;p&gt;Real‑time API serving (batch is fine for now)&lt;/p&gt;

&lt;p&gt;Fully automated retraining (earn it after evaluation gates are proven)&lt;/p&gt;

&lt;p&gt;Cloud infrastructure (local ZenML stack is sufficient for this scale)&lt;/p&gt;

&lt;p&gt;When order volume grows beyond ~10,000/day, we will revisit. But for now, the system is stable, reproducible, and delivers an F2‑score above the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The complete codebase follows the structure described in our internal design docs, with a core/ module containing pure business logic (no framework imports) and steps/ as thin ZenML wrappers. This makes testing fast and framework migration cheap.&lt;/p&gt;

&lt;p&gt;Happy building, and may your deliveries always be on time. 🚚&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>mlops</category>
    </item>
    <item>
      <title>MAC Cosmetics Generated 53,000 Leads in 2 Days and 17.2X ROI with AI, Here's Exactly How They Did It</title>
      <dc:creator>Dhaivat Jambudia</dc:creator>
      <pubDate>Tue, 21 Apr 2026 13:52:30 +0000</pubDate>
      <link>https://dev.to/dhaivat_jambudia/mac-cosmetics-generated-53000-leads-in-2-days-and-172x-roi-with-ai-heres-exactly-how-they-did-it-5ec5</link>
      <guid>https://dev.to/dhaivat_jambudia/mac-cosmetics-generated-53000-leads-in-2-days-and-172x-roi-with-ai-heres-exactly-how-they-did-it-5ec5</guid>
      <description>&lt;p&gt;MAC Cosmetics is a 40-year-old beauty brand operating in 120+ countries with a product line that spans lipsticks, foundations, eyeshadows, and everything in between.&lt;/p&gt;

&lt;p&gt;When MAC decided to expand into a new region, they hit a wall every growth engineer knows well: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No local customer database. Anonymous web traffic with no identity attached.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cart abandonment bleeding revenue. Shoppers browsing, adding to cart, disappearing. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mobile engagement tanking. Short attention spans, no immersive experience to hold them. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Personalization running blind. Product recommendations showing irrelevant items because customer data lived in silos. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal wasn't just "run some AI campaigns." It was to build a full-stack personalization engine from anonymous visitor to loyal customer across web, mobile, email, and push in a new market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's how they did it, use case by use case.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 1: Turn Anonymous Visitors Into Leads — 53,000 in 2 Days
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Standard lead capture (newsletter popups, discount banners) was failing. Conversion was low, bounce rates were high, and the few emails they collected came with low engagement downstream.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here they found solution based on Human psychology not ML Algorithm&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gamification as a Data Acquisition Engine
&lt;/h2&gt;

&lt;p&gt;MAC implemented a &lt;strong&gt;Wheel of Fortune&lt;/strong&gt; interactive overlay. First time visitors were invited to spin the wheel for a chance to win a discount coupon — but to receive it, they had to enter their email.&lt;/p&gt;

&lt;p&gt;This works because of a well-understood psychological mechanism: variable reward schedules. Unlike a fixed "get 10% off" banner (predictable, easy to ignore), a spin mechanic introduces uncertainty. The outcome isn't guaranteed, which makes the action feel exciting rather than transactional.  &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%2Ftptsqysk6yi39pylyy1b.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%2Ftptsqysk6yi39pylyy1b.png" alt="spinning wheel image" width="634" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The coupon is single-use and expiry-gated this prevents abuse while creating purchase urgency, The email capture is the real prize: &lt;/p&gt;

&lt;p&gt;it's the moment an anonymous visitor becomes a trackable, targetable customer. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Numbers&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Metric --------------------- Result
------------------------------------------------
New leads generated -------- 53,000
Time period ---------------- 2 days
Click-through rate (overlay → spin) -- 64.95%
VR increase ---------------- 4.43%
------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 64.95% CTR from an overlay is extraordinary. Standard popups run 1–3%. The gamification mechanic was doing significant heavy lifting here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 2: Fix Irrelevant Recommendations +2.3% CVR, 20.56% Add-to-Cart Rate
&lt;/h2&gt;

&lt;p&gt;MAC's ecommerce team knew that upselling and cross-selling were their fastest path to higher AOV. But their existing recommendation system was showing random products not products relevant to what a shopper had been browsing.&lt;/p&gt;

&lt;p&gt;Promoting irrelevant items doesn't just fail to convert. It actively damages trust and makes the shopping experience feel generic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: AI-Powered Behavioral Recommendations
&lt;/h2&gt;

&lt;p&gt;Before any recommendation logic could run, MAC needed a unified customer profile. They consolidated data from all channels into a Customer Data Platform (CDP), giving each customer a 360° identity that merged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browsing history (product views, category affinity)&lt;/li&gt;
&lt;li&gt;Purchase history (what they've bought, how recently, how often)&lt;/li&gt;
&lt;li&gt;Cart behavior (what they added but didn't buy)&lt;/li&gt;
&lt;li&gt;Channel data (mobile vs desktop, email engagement)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A 2.3% CVR lift sounds modest but compounds heavily at scale. On high-traffic e-commerce, every tenth of a percent in conversion rate is real money.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 3: Recover Abandoned Carts 16.69% Conversion Rate
&lt;/h2&gt;

&lt;p&gt;Cart abandonment is the most expensive leak in e-commerce. The industry average abandonment rate sits around 70%. Most brands send a single reminder email and call it recovery.&lt;/p&gt;

&lt;p&gt;MAC needed a smarter approach one that didn't rely on a single channel or a single send time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution: Cross-Channel Journey Orchestration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using their CDP data, MAC knew exactly which products each user had abandoned. This enabled personalized recovery not "you left something behind," but "you left MAC Ruby Woo Lipstick and Studio Fix Foundation behind, here they are."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 16.69% cart recovery rate is 2–3x the industry average. The combination of personalized content, multi-channel sequencing, and send-time optimization is doing the work here — no single tactic accounts for it alone.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 4: Fix Mobile Engagement +123.5% Mobile CVR
&lt;/h2&gt;

&lt;p&gt;Mobile visitors have shorter attention spans, smaller screens, and higher friction. MAC's mobile web experience wasn't holding people long enough to drive discovery and purchase.&lt;/p&gt;

&lt;p&gt;The core issue: &lt;strong&gt;product discovery on mobile is broken by default&lt;/strong&gt;. Scrolling through category pages on a 6-inch screen is tedious. Users bounce before they find something they love.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Instagram-Style Immersive Stories
&lt;/h2&gt;

&lt;p&gt;MAC deployed InStory a fullscreen story overlay on mobile web that mimics the Instagram/TikTok story format.&lt;/p&gt;

&lt;p&gt;The +123.5% mobile CVR is the headline number, but the 5X productivity gain is the one that compounds over time.&lt;/p&gt;

&lt;p&gt;MAC's results aren't magic. They're the output of a coherent architecture where every layer reinforces every other layer and a team willing to move through distinct experiments to find what works.&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%2Fgp76f4es1bmzhiv4bar3.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%2Fgp76f4es1bmzhiv4bar3.png" alt="Full workflow" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the actual playbook.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>marketing</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>I Replaced 12 Kitchen Managers Guessing "How Much Chicken Do We Need" With 3 ML Models. Here's the Entire Architecture.</title>
      <dc:creator>Dhaivat Jambudia</dc:creator>
      <pubDate>Sat, 11 Apr 2026 04:58:28 +0000</pubDate>
      <link>https://dev.to/dhaivat_jambudia/i-replaced-12-kitchen-managers-guessing-how-much-chicken-do-we-need-with-3-ml-models-heres-the-421e</link>
      <guid>https://dev.to/dhaivat_jambudia/i-replaced-12-kitchen-managers-guessing-how-much-chicken-do-we-need-with-3-ml-models-heres-the-421e</guid>
      <description>&lt;p&gt;This is a case study: AI in Supply Chain&lt;br&gt;
Every restaurant chain has the same dirty secret. Nobody actually knows how much food they waste.&lt;/p&gt;

&lt;p&gt;I worked on a system for a 12-location restaurant chain where the entire inventory process was running on vibes. Kitchen manager walks in at 7 AM, looks around, thinks "yeah we're low on chicken", calls procurement, says "send 20 kg". Thats it. Thats the system. &lt;/p&gt;

&lt;p&gt;No data. No tracking. No feedback loop. Just a human eyeballing a fridge and making a phone call. &lt;/p&gt;
&lt;h2&gt;
  
  
  The mess we started with
&lt;/h2&gt;

&lt;p&gt;Let me walk you through what actually happens every single day across 12 restaurants.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Morning (per restaurant):&lt;br&gt;
Kitchen manager does a visual walkthrough. Decides whats low based on gut feeling and experience. Writes it down on paper. Sometimes doesnt write it down at all, just remembers. Calls the central procurement office and dictates what they need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Procurement office:&lt;br&gt;
Officer enters the order into a spreadsheet. Then calls 3-4 suppliers asking for price quotes. Same suppliers. Same conversation. Every single week. Compares quotes, picks the cheapest, places the order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next day:&lt;br&gt;
Delivery arrives. Sometimes its complete, sometimes its not. Kitchen manager checks it against the order. If somethings wrong, calls procurement again. More phone calls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The gap nobody talks about:&lt;br&gt;
Throughout the day the kitchen uses ingredients. But nobody tracks how much was actually used versus how much was ordered. You ordered 20 kg chicken. The POS system shows you sold dishes that should use about 12 kg. End of day you have 3 kg left. Where did the other 5 kg go? Nobody knows. Nobody is even asking the question.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Month end:&lt;br&gt;
Each of the 12 restaurant managers compiles their ordering costs into a spreadsheet. Emails it to head office. Someone at head office manually merges 12 spreadsheets into one P&amp;amp;L report. Takes about 8 hours. The report is full of errors but nobody has the energy to check.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Head office looks at the consolidated report and tries to spot problems. But they cant. They dont have waste data. They dont have supplier performance data. They dont know which location is over-ordering. They're making decisions blind.&lt;/p&gt;

&lt;p&gt;I mapped 14 steps in this workflow. Here's how they break down.&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%2Ft225obs9htuu29pardwt.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%2Ft225obs9htuu29pardwt.png" alt="Steps in tables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern is clear. Most steps are mechanical and can be automated with tools. The judgment steps can be replaced with ML models. And the biggest problem (step 11) isnt even a bad process its a missing process entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Inventory &amp;amp; Procurement Automation Pipeline&lt;/span&gt;

| Step                          | Component                              | Why |
|-------------------------------|----------------------------------------|-----|
| Visual inventory check        | TOOL (digital scales + POS data) as TRIGGER | Replace eyeballing with actual measurement. POS tells you what was sold. Scales tell you what's left. This data arriving each morning triggers the whole system. |
| Write order list              | DETERMINISTIC ML (Demand Predictor)    | A regression model takes day-of-week, reservations, weather, past sales, current inventory and outputs precise order quantities. This is not a language problem. It's a math problem. |
| Call procurement              | TOOL (automated routing)               | The call is eliminated entirely. System sends computed order to the procurement workflow. |
| Enter into spreadsheet        | TOOL (database)                        | Spreadsheet replaced by structured database. Orders logged automatically. |
| Call suppliers for quotes     | TOOL (supplier API)                    | Suppliers provide price lists via API or weekly upload. System queries automatically. |
| Compare quotes, pick supplier | DETERMINISTIC ML (Supplier Scorer)     | Score by price + on-time delivery rate + quality rejection rate. A weighted scoring model, not an LLM having a think about it. |
| Place order                   | TOOL (supplier API)                    | Auto-execute within guardrails. |
| Delivery arrives              | TOOL (delivery confirmation)           | Kitchen manager confirms receipt in app. Quantities logged against order. |
| Check delivery vs order       | TOOL (automated matching)              | System compares received vs ordered. Flags mismatches. |
| Resolve mismatches            | HUMAN CHECKPOINT                       | Mismatches above 10% escalated to procurement manager with full context. Small variances auto-accepted and logged. |
| Track usage vs ordered        | DETERMINISTIC ML (Waste Detector)      | Ordered 20 kg. POS shows 12 kg used in dishes. Scale shows 3 kg remaining. 5 kg unaccounted = waste. Model flags restaurants above benchmark. |
| Monthly cost compilation      | TOOL (automated report)                | All orders already in database. Monthly totals are a SQL query. |
| Consolidate 12 restaurants    | TOOL (dashboard)                       | One query across all locations. Real-time. |
| Review for anomalies          | LLM (narrative) + HUMAN CHECKPOINT     | LLM generates readable brief. "Restaurant 7 has 23% higher chicken costs than chain average. Waste rate is 18% vs chain average 9%." Human reads it and decides what to do. |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Notice:&lt;/strong&gt; the LLM shows up exactly once. At the very end. For narration. It does not make a single decision in this entire pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  System architecture
&lt;/h2&gt;

&lt;p&gt;The system has three layers that run in parallel. &lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Restaurant agents (x12, running independently)
&lt;/h2&gt;

&lt;p&gt;Each restaurant runs its own agent. Same logic, different data. They don't wait for each other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRIGGER: Daily at 5 AM

→ [TOOL] Pull POS sales data (yesterday)
→ [TOOL] Pull digital scale readings (current inventory)

→ [DETERMINISTIC ML] Demand Predictor
    Inputs: day of week, reservations, weather,
            yesterday's sales, historical patterns,
            current inventory
    Output: order quantity per ingredient
            with confidence interval

→ [DETERMINISTIC ML] Waste Detector
    Inputs: yesterday's opening inventory,
            deliveries received, POS-derived usage,
            closing inventory from scale
    Output: waste amount per ingredient,
            flag if above threshold

→ [TOOL] Query supplier prices via API

→ [DETERMINISTIC ML] Supplier Scorer
    Inputs: current prices, historical on-time rate,
            quality rejection rate
    Output: ranked supplier per ingredient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Layer 2: Decision branching
&lt;/h2&gt;

&lt;p&gt;This is where the system decides what needs a human and what doesnt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF order &amp;lt; ₹50K
   AND all items from preferred suppliers
   AND waste levels normal:
   → AUTO-EXECUTE order via supplier API
   → Log to database

IF order ≥ ₹50K
   OR non-standard supplier
   OR unusual quantity (&amp;gt;2x average):
   → HUMAN CHECKPOINT
   → Procurement manager approves/modifies/rejects
   → Execute approved order

IF waste flag triggered (&amp;gt;15% waste on any ingredient):
   → ALERT restaurant manager + head office
   → [LLM] Generate waste analysis brief
   → HUMAN investigates and logs root cause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auto-execute path handles the routine. The human checkpoint catches the unusual. The waste alert handles the unknown. Three paths, clear rules, no ambiguity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: Head office consolidation agent
&lt;/h2&gt;

&lt;p&gt;Runs daily and monthly. Aggregates everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DAILY:
→ [TOOL] Aggregate orders, costs, waste across 12 locations
→ [DETERMINISTIC ML] Anomaly detector
   Flags restaurants above/below cost benchmarks
→ Dashboard updated

MONTHLY:
→ [TOOL] Generate consolidated P&amp;amp;L from database
→ [DETERMINISTIC ML] Trend analysis
→ [LLM] Generate monthly narrative:
   "Chain-wide food cost: 32.4% of revenue (target 30%).
    Top performer: Restaurant 2 at 28.1%.
    Needs attention: Restaurant 9 at 37.2%,
    driven by 22% waste rate on vegetables."
→ HUMAN CHECKPOINT: Head office reviews and decides
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The feedback loop
&lt;/h2&gt;

&lt;p&gt;This is what makes the system get smarter over time.&lt;br&gt;
Every order, every delivery, every waste measurement, every demand prediction gets stored. The system tracks prediction accuracy. &lt;/p&gt;

&lt;p&gt;"We predicted 18 kg chicken demand for Restaurant 3 on a Tuesday. Actual was 21 kg. Error: 16%."&lt;/p&gt;

&lt;p&gt;This feeds back into the demand predictor. Over weeks and months, the model learns each restaurant's patterns. It learns that Restaurant 5 always spikes on Fridays. &lt;/p&gt;

&lt;p&gt;That Restaurant 9 has higher waste on vegetables every monsoon season. That Supplier B's delivery times slip during festivals.&lt;/p&gt;

&lt;p&gt;The ML models dont stay static. They improve because the memory layer captures outcomes, not just decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode analysis
&lt;/h2&gt;

&lt;p&gt;Every component will fail eventually. The question isnt "will it fail" but "what happens when it does."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POS data feed goes down:&lt;/strong&gt; Kitchen manager enters yesterday's estimated covers manually. System uses last similar day as a proxy. Alert goes to IT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scales malfunction or staff skip weighing:&lt;/strong&gt; System flags missing reading and falls back to calculated inventory: yesterday's stock + deliveries - POS usage. Not perfect but better than nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demand predictor is wildly wrong:&lt;/strong&gt; Confidence intervals widen automatically when recent errors are high. Low-confidence predictions get a 20% buffer and human review flag. Model retrains weekly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supplier API is down:&lt;/strong&gt; Order gets queued. SMS notification sent to procurement officer with order details for manual phone placement. Order logged in app when confirmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Waste detector throws false positives:&lt;/strong&gt; Waste alerts require 2+ consecutive days above threshold before escalating. Single day spikes get noted but dont trigger alarms. Reduces alert fatigue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-execution bug orders 10x quantity:&lt;/strong&gt; Hard guardrail. No auto-order can exceed 3x the 4-week average for that ingredient at that restaurant. Anything above requires human approval. Daily spend cap per location.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM hallucinates a number in the monthly report:&lt;/strong&gt; LLM never generates numbers. All figures come from the database as structured data. LLM only wraps them in natural language. If LLM is unavailable, the dashboard with raw numbers still works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The design principle:&lt;/strong&gt; If any ML component fails, the system degrades to the current manual process. Not to something worse. The worst case scenario is "things go back to how they are today." Thats the floor, not a cliff.&lt;/p&gt;

&lt;p&gt;Conclusion:&lt;br&gt;
ML decides. LLM explains. Humans approve. Thats the pattern.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Replaced a $200/mo AI Stack with OpenClaw + Free Models. Here's the Exact Setup (and Why Security Almost Killed It)</title>
      <dc:creator>Dhaivat Jambudia</dc:creator>
      <pubDate>Fri, 03 Apr 2026 14:10:22 +0000</pubDate>
      <link>https://dev.to/dhaivat_jambudia/i-replaced-a-200mo-ai-stack-with-openclaw-free-models-heres-the-exact-setup-and-why-security-24d1</link>
      <guid>https://dev.to/dhaivat_jambudia/i-replaced-a-200mo-ai-stack-with-openclaw-free-models-heres-the-exact-setup-and-why-security-24d1</guid>
      <description>&lt;p&gt;I spent 2 weeks building a setup with OpenClaw, free/cheap models for the boring stuff, and only routing the hard problems to expensive models. The result? Our AI bill went from ~$200/mo to around $10/mo. And honestly, the quality for 90% of tasks is the same.&lt;/p&gt;

&lt;p&gt;But this is the part few people talks about on twitter the security side of OpenClaw nearly burnt us. I'll get into that too because if you're a founder or CTO thinking about deploying this, you need to hear the ugly parts.&lt;/p&gt;

&lt;p&gt;Lets get into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OpenClaw, and Why Should You Care
&lt;/h2&gt;

&lt;p&gt;If you haven't heard of OpenClaw yet... where have you been? Its the fastest growing open source project in GitHub history. Over 163k stars. The project started as a personal AI assistant by Peter Steinberger (yes, the iOS dev legend) and exploded because it does something no other tool does well — it gives you a persistent AI agent that lives in your messaging apps and actually does things on your behalf.&lt;/p&gt;

&lt;p&gt;WhatsApp, Telegram, Slack, Discord, email — OpenClaw connects to all of them through a single gateway. Its not a chatbot. Its an agent that can run shell commands, browse the web, manage your calendar, read and write files, and more. Think of it like having a junior employee that never sleeps, never complains, and costs pennies per hour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quick Context
OpenClaw is model-agnostic. You can plug in Claude, GPT, Gemini, or run completely free local models through Ollama. This is the key that makes the cost optimization possible.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For founders and CTOs, heres why it matters: you can automate 80% of your operational busywork without building custom software. No Python scripts, no Zapier chains, no hiring a developer to glue APIs together. You write a SOUL.md config file, connect your channels, and you're live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Idea: Not Every Task Needs a $75/M-token Model
&lt;/h2&gt;

&lt;p&gt;This is the thing that took me embarrassingly long to realize. When someone emails us saying "hey can you send me the latest invoice?", your AI doesn't need Claude Opus to understand that. A 7B parameter model can handle it. When someone fills a form and you need to parse it into structured data — again, small model territory.&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;openclaw.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;saved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;us&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$$$/mo&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;"agents"&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;"defaults"&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;"model"&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;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ollama/qwen3:32b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"thinking"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic/claude-sonnet-4-20250514"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fallbacks"&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="s2"&gt;"openai/gpt-4.1-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"google/gemini-2.5-flash"&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;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;h2&gt;
  
  
  What We Actually Automate (With Examples)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Email Reply Drafts — Free Model
&lt;/h3&gt;

&lt;p&gt;Our support inbox gets maybe 60-80 emails a day. Most of them are variants of the same 15 questions. We wrote a skill that reads incoming emails, matches them against our FAQ knowledge base, and drafts a reply. The human just reviews and hits send.&lt;/p&gt;

&lt;p&gt;Model used: Qwen3 32B (local, $0). For templated email replies, this model is more then adequate. It follows instructions well, keeps the tone professional, and doesn't hallucinate company policies because we feed it the exact docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# skills/email-drafter/SKILL.md&lt;/span&gt;
name: email-reply-drafter
trigger: new email in support inbox
model_override: ollama/qwen3:32b

&lt;span class="gh"&gt;# Steps:&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Read incoming email
&lt;span class="p"&gt;2.&lt;/span&gt; Search FAQ knowledge base for matching topics
&lt;span class="p"&gt;3.&lt;/span&gt; Draft reply using company voice guidelines
&lt;span class="p"&gt;4.&lt;/span&gt; Send draft to #email-review Slack channel
&lt;span class="p"&gt;5.&lt;/span&gt; Wait for human approval before sending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Form Processing — Free Model
&lt;/h3&gt;

&lt;p&gt;We have clients who fill out onboarding forms. The data comes in messy — sometimes PDF, sometimes Google Form, sometimes literally a photo of a handwritten form (yes, in 2026). The OpenClaw skill extracts the data, structures it, and pushes it to our CRM.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Code Reviews &amp;amp; Bug Fixes — Expensive Model (Sub-Agent)
&lt;/h3&gt;

&lt;p&gt;This is where it gets interesting. When our agent encounters a coding task — someone reports a bug, or we need to generate a script — it spawns a sub-agent that uses Claude Sonnet 4 or routes to Claude Code.&lt;/p&gt;

&lt;p&gt;Why not use the free model here? Because I tried, and the results were... lets just say, not production-ready. Qwen 32B can write a for loop fine. But ask it to debug a race condition in an async Node.js service and it falls apart. The bigger models just get it in ways that smaller models don't.&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Sub-agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;coding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tasks&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;"skills"&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;"code-reviewer"&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;"model_override"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic/claude-sonnet-4-20250514"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file-edit"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sandbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;You can also pipe coding tasks directly to Claude Code or OpenAI Codex as external tools. OpenClaw's tool system lets you call any CLI tool, so you literally just wrap claude-code or codex as a skill and the agent delegates to them when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now the important part: Security
&lt;/h2&gt;

&lt;p&gt;OpenClaw's security track record is... not great. And I say this as someone who genuinely loves the project. The speed of adoption outpaced the security hardening by a huge margin. which is user's mistake not openclaw's.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Actually Did to Lock It Down
&lt;/h2&gt;

&lt;p&gt;After reading the Cisco blog and the Microsoft post, I spent a full weekend hardening our setup. Heres the non-negotiable stuff:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Docker isolation.&lt;/strong&gt; Our OpenClaw instance runs in a container with no access to the host filesystem except for a single mounted volume for the workspace. The agent physically cannot touch anything outside its sandbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.Dedicated credentials.&lt;/strong&gt; The agent has its own email account, its own API keys, its own everything. If it gets compromised, the blast radius is limited. Its not sharing my personal Google account or our company's AWS root credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.No third-party skills.&lt;/strong&gt; Zero. We write all our skills in-house. I don't care how cool a ClawHub skill looks — its not going on our machine until the ecosystem has proper code review and signing. Maybe in 6 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.Network segmentation.&lt;/strong&gt; The container runs on an isolated network. It can reach the model APIs and our internal services, but nothing else. No random outbound connections to god-knows-where.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.Human-in-the-loop for destructive actions.&lt;/strong&gt; Any action that sends an email, modifies a file, or runs a command requires approval in Slack. The agent proposes, the human approves. No autonomous destructive operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line: Is It Worth It?
&lt;/h2&gt;

&lt;p&gt;Absolutely Yes, You need technical chops. This is not a "click three buttons and you're done" setup. You need to understand Docker, networking, API keys, model capabilities, and security basics. If your team doesn't have someone who can set this up and maintain it, pay for a managed solution instead.&lt;/p&gt;

&lt;p&gt;Want help setting this up for your team?&lt;br&gt;
I've helped people deploy this exact architecture over the past month. If your burning money on AI API bills and want to cut costs without losing quality, reach out. I do a free 30-minute audit call where we look at your current setup and identify where free models can replace expensive ones.&lt;/p&gt;

&lt;p&gt;DM me on &lt;a href="https://x.com/dhaivat00" rel="noopener noreferrer"&gt;Twitter/X&lt;/a&gt; or on &lt;a href="https://www.linkedin.com/in/dhaivat-jambudia/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
    </item>
    <item>
      <title>I Built a RAG System to Chat With Newton's Entire Wikipedia</title>
      <dc:creator>Dhaivat Jambudia</dc:creator>
      <pubDate>Thu, 02 Apr 2026 16:29:02 +0000</pubDate>
      <link>https://dev.to/dhaivat_jambudia/i-built-a-rag-system-to-chat-with-newtons-entire-wikipedia-1ndg</link>
      <guid>https://dev.to/dhaivat_jambudia/i-built-a-rag-system-to-chat-with-newtons-entire-wikipedia-1ndg</guid>
      <description>&lt;p&gt;Most RAG tutorials just say "chunk your PDF and call OpenAI". I wanted to build something more real — a proper pipeline that actually ingests, cleans, embeds, and serves knowledge from Isaac Newton's Wikipedia page end to end.  &lt;/p&gt;

&lt;p&gt;The result is Newton LLM. You can now ask things like "What are Newton's contributions in Calculus?" and get proper answers with sources instead of made-up stuff.&lt;br&gt;
Here's how I actually built it and what I learned.&lt;br&gt;
The Problem With Most RAG Demos&lt;br&gt;
Every YouTube RAG tutorial follows the same boring steps: load PDF, split into chunks, put in vector store, done.&lt;br&gt;
But nobody talks about the real issues:&lt;/p&gt;

&lt;p&gt;How do you keep the data fresh when the source changes?&lt;br&gt;
How do you clean messy web data before embedding?&lt;br&gt;
How do you separate the ingestion part from the serving part?&lt;br&gt;
How do you make the whole thing actually deployable?&lt;/p&gt;

&lt;p&gt;Newton LLM tries to solve these. Its not just a notebook — its a small system.&lt;br&gt;
Architecture Overview&lt;br&gt;
The system has two main layers:&lt;/p&gt;

&lt;p&gt;Data Ingestion Layer (the offline part)&lt;/p&gt;

&lt;p&gt;Source → Airflow → MongoDB&lt;br&gt;
I pull data from Wikipedia about Newton — his life, physics, math, optics etc.&lt;br&gt;
Apache Airflow runs the whole ETL pipeline through a DAG. It fetches, cleans, and transforms the raw content. No random scripts or cron jobs. Airflow handles retries, scheduling and monitoring.&lt;br&gt;
MongoDB stores the cleaned documents. This is my "source of truth" before anything gets embedded.&lt;br&gt;
Why not embed straight from Wikipedia? Because raw scraped pages are full of garbage — menus, references, bad HTML. You need to clean it first. MongoDB gives me a clean staging area.&lt;/p&gt;

&lt;p&gt;RAG Serving Layer (the online part)&lt;/p&gt;

&lt;p&gt;Qdrant ← Batch Embeddings ← MongoDB&lt;br&gt;
Since Newton's Wikipedia doesn't change every day, I use batch embedding instead of doing it live. Documents go from MongoDB → embedding model → Qdrant in scheduled batches. Its cheaper and faster.&lt;br&gt;
When user asks a question:&lt;br&gt;
User Question&lt;br&gt;
→ FastAPI gets it&lt;br&gt;
→ Query gets embedded&lt;br&gt;
→ Qdrant finds similar chunks&lt;br&gt;
→ Retrieved docs + question → LLM&lt;br&gt;
→ Answer with sources&lt;br&gt;
The LLM always gets context. It helps a lot with hallucinations.&lt;br&gt;
Tech Stack&lt;/p&gt;

&lt;p&gt;Orchestration: Apache Airflow (for DAGs, retries, monitoring)&lt;br&gt;
Document Store: MongoDB (flexible for messy Wikipedia data)&lt;br&gt;
Vector Store: Qdrant (fast and open source)&lt;br&gt;
Backend: FastAPI (quick and clean)&lt;br&gt;
Frontend: Next.js / Streamlit (Next for real use, Streamlit for quick tests)&lt;/p&gt;

&lt;p&gt;Key Decisions&lt;br&gt;
Batch Embedding &amp;gt; Real-time Embedding&lt;br&gt;
Most tutorials embed on the fly. For static data like this, its stupid to keep re-embedding the same things. I run batch embedding once or on schedule and save a lot of time and money.&lt;br&gt;
Airflow instead of simple Python script&lt;br&gt;
I could have just written one scrape_and_embed.py file. But Airflow gives retries, proper logging, scheduling and makes each step separate. If Wikipedia is down, it retries automatically. For anything bigger than a toy project, orchestration actually matters.&lt;br&gt;
Separating Ingestion from Serving&lt;br&gt;
The scraping/cleaning part and the answering part are completely separate. Ingestion can break or update without touching the live RAG system. The serving layer just reads from Qdrant.&lt;br&gt;
What I'd Do Differently Next Time&lt;/p&gt;

&lt;p&gt;Add a reranker — simple vector search isn't enough. A reranker would make results much better.&lt;br&gt;
Build evaluation from the start — without proper eval, you don't know if your changes actually help.&lt;br&gt;
Add more sources — right now only Wikipedia. Academic papers would make it way stronger.&lt;br&gt;
Try hybrid search — combine vector search with keyword search (BM25).&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
Building a simple RAG demo is easy. Building something that actually works properly is much harder. Most of the work is in the boring parts: cleaning data, setting up orchestration, separating concerns, and deciding when to use batch vs real-time.&lt;br&gt;
Newton LLM showed me that good retrieval matters more than which LLM you use. If your pipeline is solid, even a smaller model gives good answers.&lt;br&gt;
If you're building RAG, focus on the data pipeline first, not fancy prompts.&lt;br&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%2Fiecjz1nego547ylj2qxu.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%2Fiecjz1nego547ylj2qxu.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
    </item>
  </channel>
</rss>
