<?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: Adrika Pandey</title>
    <description>The latest articles on DEV Community by Adrika Pandey (@adrikapandey).</description>
    <link>https://dev.to/adrikapandey</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%2F4020684%2F48f9b08e-9243-415c-b670-a497e3a138a4.jpg</url>
      <title>DEV Community: Adrika Pandey</title>
      <link>https://dev.to/adrikapandey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adrikapandey"/>
    <language>en</language>
    <item>
      <title>How I Built a Full-Stack Smart City Air Quality Dashboard with React + FastAPI</title>
      <dc:creator>Adrika Pandey</dc:creator>
      <pubDate>Mon, 13 Jul 2026 08:59:54 +0000</pubDate>
      <link>https://dev.to/adrikapandey/how-i-built-a-full-stack-smart-city-air-quality-dashboard-with-react-fastapi-1na</link>
      <guid>https://dev.to/adrikapandey/how-i-built-a-full-stack-smart-city-air-quality-dashboard-with-react-fastapi-1na</guid>
      <description>&lt;p&gt;Every winter, Delhi's air becomes so toxic that schools shut down and hospitals overflow. The city has 40+ sensors measuring pollution — but no tool to tell officials &lt;em&gt;where&lt;/em&gt; it is coming from, &lt;em&gt;what&lt;/em&gt; will happen next, or &lt;em&gt;what to do about it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I built that tool. Here is how.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The entire app runs from two things: a React frontend and a single Python file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────┐         ┌───────────────────────────┐
│    React Dashboard (UI)   │         │  FastAPI Backend (Python)  │
│                           │  JSON   │                           │
│  GIS Map · Charts · Chat  │◄───────►│  Dispersion Math (NumPy)  │
│  Sliders · Policy Toggles │  APIs   │  Source Attribution       │
│                           │         │  Forecast Engine          │
└───────────────────────────┘         └───────────────────────────┘
          ▲ Compiled into static files         │
          └────────────────────────────────────┘
                    Served by FastAPI
                         │
              ┌──────────┴──────────┐
              │   Docker Container   │
              │     Port 7860        │
              │  (Hugging Face Hub)  │
              └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; handles the UI — map, charts, sliders, toggles, chat widget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; handles the logic — it runs physics equations using NumPy and returns JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; packages both into one container. React compiles into static files, which the Python server hosts alongside its API routes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No database. No external APIs. No ML model. The math runs in under 5ms per request.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Stack (And What I Considered Instead)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;I chose&lt;/th&gt;
&lt;th&gt;I considered&lt;/th&gt;
&lt;th&gt;Why I went this way&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React + Vite&lt;/td&gt;
&lt;td&gt;Next.js, plain HTML/JS&lt;/td&gt;
&lt;td&gt;I needed fast state management for 6 connected panels (map clicks update charts, sliders update forecasts). Plain JS would be spaghetti. Next.js adds SSR overhead I did not need — this is a single-page dashboard, not a content site.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CSS approach&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vanilla CSS with custom properties&lt;/td&gt;
&lt;td&gt;Tailwind, MUI&lt;/td&gt;
&lt;td&gt;Tailwind classes clutter JSX when you have complex glassmorphic styles. MUI enforces Material Design, which does not fit a dark control-room aesthetic. Vanilla CSS with HSL variables gave me full control.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;td&gt;Flask, Django, Express&lt;/td&gt;
&lt;td&gt;Flask has no built-in request validation. Django is heavy for a single-file API. Express would mean running Node alongside Python (NumPy lives in Python). FastAPI gives me Pydantic validation, async support, and static file serving in one process.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Math engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NumPy&lt;/td&gt;
&lt;td&gt;TensorFlow, scikit-learn&lt;/td&gt;
&lt;td&gt;I am not training a model. I am evaluating physics equations with array math. NumPy does this in microseconds. TensorFlow would add 500MB of dependencies to do the same multiplication.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docker on Hugging Face&lt;/td&gt;
&lt;td&gt;Vercel, Railway, AWS&lt;/td&gt;
&lt;td&gt;Hugging Face gives free Docker hosting with a public URL. Vercel is frontend-only. Railway and AWS need payment setup. One Dockerfile, one push, done.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Connecting Frontend to Backend
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐                          ┌──────────────┐
│              │   GET /api/stations       │              │
│              │ ────────────────────────► │              │
│              │                           │  Computes    │
│              │   GET /api/attribution    │  baseline    │
│    React     │ ────────────────────────► │  AQI using   │
│   Dashboard  │                           │  dispersion  │
│              │   POST /api/simulate      │  equations   │
│              │ ────────────────────────► │              │
│              │                           │   FastAPI    │
│              │   POST /api/chat          │   + NumPy    │
│              │ ────────────────────────► │              │
│              │ ◄──── JSON responses ──── │              │
└──────────────┘                          └──────────────┘
       ▲ In production, both served from same origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In development, Vite proxies &lt;code&gt;/api/*&lt;/code&gt; requests to the Python server running on a different port. In production, FastAPI serves the compiled React bundle directly — so there is no CORS issue and no separate hosting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dist_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frontend/dist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;StaticFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dist_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;static&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;uvicorn&lt;/code&gt; command runs the entire app — APIs and UI — on a single port.&lt;/p&gt;

&lt;p&gt;The React side keeps all simulation parameters (wind speed, direction, mixing height, active fires, policy toggles) in component state. Whenever a slider moves or a policy is toggled, a debounced &lt;code&gt;useEffect&lt;/code&gt; fires API calls to fetch updated attribution and forecast data. Every panel on the dashboard — the map, the charts, the advisory, the chat — reacts to the same shared state.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Simulation Engine — Where the Real Solution Lives
&lt;/h2&gt;

&lt;p&gt;The features I described above — the map, the charts, the chat — are just interfaces. The actual solution to the air quality problem lives in one Python function: &lt;code&gt;compute_aqi_components()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the core idea. Delhi's pollution comes from four sources: &lt;strong&gt;traffic, factories, farm fires, and construction dust&lt;/strong&gt;. Each source behaves differently depending on the weather. The function models these physical relationships:&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic exhaust builds up when winds are slow
&lt;/h3&gt;

&lt;p&gt;When winds stall, vehicle emissions have nowhere to go. They pile up at ground level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_traffic&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rush_hour_curve&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wind_speed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rush_hour_curve&lt;/code&gt; is a sine wave that peaks at 9 AM and 6 PM — matching real commute patterns. When wind speed drops toward zero, the denominator shrinks and the contribution spikes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Factory smoke gets trapped by cold air layers
&lt;/h3&gt;

&lt;p&gt;In winter, cold air settles over Delhi like a lid on a pot. Meteorologists call this the "mixing height" — the altitude below which pollutants are trapped. When it drops from 1000m to 200m, concentrations multiply.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;industry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_industry&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;600.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;mixing_height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Farm fire smoke depends on wind direction
&lt;/h3&gt;

&lt;p&gt;Crop fires happen north-west of Delhi, in Punjab and Haryana. The smoke only reaches Delhi when the wind blows from that direction (~310°). I modeled this using a bell curve — full impact at 310°, falling off sharply as the angle shifts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;alignment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wind_direction&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;310&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;biomass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_biomass&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active_fires&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;alignment&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wind_speed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chemical fingerprinting cross-validates the math
&lt;/h3&gt;

&lt;p&gt;Different sources produce different sized particles. Farm smoke is ultra-fine (mostly PM2.5). Construction dust is coarse (mostly PM10). I used fixed ratios from published atmospheric research to split the total into PM2.5 and PM10:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pm25&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.70&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;industry&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.88&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;biomass&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dust&lt;/span&gt;
&lt;span class="n"&gt;pm10&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;industry&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;biomass&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dust&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the computed PM2.5/PM10 ratio is above 0.80, it confirms that combustion (traffic or farm fires) is the dominant source. Below 0.35, it confirms coarse dust. This acts as a built-in sanity check on the source attribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting it all together
&lt;/h3&gt;

&lt;p&gt;For the &lt;strong&gt;source attribution&lt;/strong&gt; panel, the function runs once with current slider values and returns percentage breakdowns.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;72-hour forecast&lt;/strong&gt;, it loops through each hour with evolving weather — winds slow, cold air traps pollution, then winds pick up and clear the air. The function runs twice per hour: once without policies (baseline) and once with them (mitigated). The gap between the two lines on the chart is the measurable impact of a policy decision.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;chat assistant&lt;/strong&gt;, the function runs with whatever the user is currently looking at on the dashboard, and the results get formatted into a natural-language response.&lt;/p&gt;

&lt;p&gt;One function. Three features. Every number on every panel traces back to a physical mechanism an official can understand and defend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying in One Container
&lt;/h2&gt;

&lt;p&gt;The Dockerfile is a two-stage build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Build React&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frontend-builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app/frontend&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; frontend/package*.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; frontend/ .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Stage 2: Run Python&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.10-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; main.py .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=frontend-builder /app/frontend/dist ./frontend/dist&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 7860&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node compiles the React code, then the output gets copied into a Python image. The final container has no Node.js — just Python serving static files and API routes. I pushed this to Hugging Face Spaces, which pulls the Dockerfile and builds it automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code&lt;/strong&gt;: &lt;a href="https://github.com/adrikapandey/AI-Powered-Urban-Air-Quality-Intelligence-for-Smart-City-Intervention" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://huggingface.co/spaces/adrikap/AI-PoweredUrbanAirQuality" rel="noopener noreferrer"&gt;Hugging Face Space&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run it locally in two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;frontend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..
uvicorn main:app &lt;span class="nt"&gt;--port&lt;/span&gt; 7860
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Built by Adrika Pandey.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>python</category>
      <category>fastapi</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Bypassing Deep Learning Bottlenecks: A Deep Dive into Redis Caching for AI APIs</title>
      <dc:creator>Adrika Pandey</dc:creator>
      <pubDate>Wed, 08 Jul 2026 08:43:33 +0000</pubDate>
      <link>https://dev.to/adrikapandey/bypassing-deep-learning-bottlenecks-a-deep-dive-into-redis-caching-for-ai-apis-13kh</link>
      <guid>https://dev.to/adrikapandey/bypassing-deep-learning-bottlenecks-a-deep-dive-into-redis-caching-for-ai-apis-13kh</guid>
      <description>&lt;h2&gt;
  
  
  The Project: Introducing AnemiaSense AI
&lt;/h2&gt;

&lt;p&gt;In modern healthcare, accessibility is everything. Standard screenings for conditions like anemia—a shortage of healthy red blood cells—traditionally require invasive blood draws, specialized lab equipment, and days of waiting for results. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AnemiaSense AI&lt;/strong&gt; was designed to change this. It is a full-stack medical diagnostic web application that analyzes a simple, non-invasive photograph of a patient’s palm to predict the likelihood of anemia. &lt;/p&gt;

&lt;p&gt;At the core of the application is a custom Convolutional Neural Network (CNN) trained using TensorFlow and Keras. The model acts as a digital set of eyes, scanning the unique color distributions, pale skin tones, and creases of the palm to output a diagnostic prediction. &lt;/p&gt;

&lt;p&gt;However, as the application transitioned from a local development script to a live, cloud-hosted service, it encountered a classic software engineering roadblock: &lt;strong&gt;computational latency&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The Latency and Resource Cost of AI Inference
&lt;/h2&gt;

&lt;p&gt;In software development, we refer to running an active machine learning model as &lt;strong&gt;inference&lt;/strong&gt;. Unlike a simple web request that displays a profile page or fetches text from a database, inference is a massive mathematical calculation. &lt;/p&gt;

&lt;p&gt;When a user uploads an image, the server must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Downsample the image into a uniform $256 \times 256$ pixel matrix.&lt;/li&gt;
&lt;li&gt;Normalize the color values.&lt;/li&gt;
&lt;li&gt;Pass those arrays through multiple layer weights, executing millions of matrix multiplications to calculate a prediction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In production, this entire process took upwards of &lt;strong&gt;300 milliseconds&lt;/strong&gt; per request. &lt;/p&gt;

&lt;p&gt;For a single user, 300ms feels like a minor lag. But at scale, this latency poses severe engineering challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wasted Compute:&lt;/strong&gt; Users frequently re-upload the same photo, refresh their screens, or double-tap buttons. Running heavy model math over the exact same pixels repeatedly wastes expensive server CPU and GPU cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server Congestion:&lt;/strong&gt; If multiple users hit the API at the same time, the server's processor spikes to 100%, causing incoming requests to queue up, time out, and crash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sluggish User Experience:&lt;/strong&gt; Modern web applications are expected to react instantly. A 300ms delay degrades the feel of the user interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To scale the app, we needed to make it smarter. We needed a way to remember past calculations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architectural Solution: MD5 Hashing + Redis
&lt;/h2&gt;

&lt;p&gt;To prevent our AI model from recalculating results it had already solved, we implemented a caching layer. Caching is the process of storing previously calculated results in a temporary, high-speed storage location so future requests can be served instantly.&lt;/p&gt;

&lt;p&gt;Our caching architecture relies on two key technologies:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. MD5 Cryptographic Hashing (The "Digital Fingerprint")
&lt;/h3&gt;

&lt;p&gt;Comparing raw images pixel-by-pixel to see if they are identical is slow and memory-intensive. Instead, we use &lt;strong&gt;MD5 Hashing&lt;/strong&gt;. An MD5 algorithm takes the binary data of an image file and compresses it into a unique, fixed-length 32-character string (e.g., &lt;code&gt;8fbe12c9...&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Think of this hash as a unique digital fingerprint. If even a single pixel in the image changes, the fingerprint changes completely. This fingerprint serves as our lookup key.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Redis (The "High-Speed Memory Scratchpad")
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; (Remote Dictionary Server) is an in-memory database. While traditional databases write data to physical hard drives (which is relatively slow), Redis stores data directly in the server’s RAM. Because reading from RAM is incredibly fast, Redis can retrieve data in under 2 milliseconds.&lt;/p&gt;

&lt;p&gt;By storing our digital fingerprints and their corresponding AI predictions in Redis, we built a lookup loop: when an image is uploaded, we check the cache. If we have the fingerprint on file (a &lt;strong&gt;Cache Hit&lt;/strong&gt;), we bypass the neural network entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  System Architecture Flow
&lt;/h2&gt;

&lt;p&gt;Here is how a palm image travels through the AnemiaSense API:&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%2Fmermaid.ink%2Fimg%2FZ3JhcGggVEQKICAgIEFbQ2xpZW50IFVJIC8gQnJvd3Nlcl0gLS0%2BfFVwbG9hZCBQYWxtIEltYWdlfCBCKEZsYXNrIEFQSSBFbmRwb2ludCkKICAgIEIgLS0%2BfDEuIEdlbmVyYXRlIE1ENSBIYXNofCBDe1JlZGlzIENhY2hlfQogICAgQyAtLT588J%2BOiSBDYWNoZSBIaXR8IERbU2VydmUgQ2FjaGVkIERpYWdub3Npc10KICAgIEQgLS0%2BfE8nMScgTGF0ZW5jeXwgQQogICAgQyAtLT584pqg77iPIENhY2hlIE1pc3N8IEVbRGVlcCBMZWFybmluZyBQaXBlbGluZV0KICAgIEUgLS0%2BIEYoKFRlbnNvckZsb3cgLyBLZXJhcyBDTk4pKQogICAgRiAtLT58T3V0cHV0IFByZWRpY3Rpb258IEdbU2F2ZSBSZXN1bHQgdG8gUmVkaXNdCiAgICBHIC0tPiBB" 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%2Fmermaid.ink%2Fimg%2FZ3JhcGggVEQKICAgIEFbQ2xpZW50IFVJIC8gQnJvd3Nlcl0gLS0%2BfFVwbG9hZCBQYWxtIEltYWdlfCBCKEZsYXNrIEFQSSBFbmRwb2ludCkKICAgIEIgLS0%2BfDEuIEdlbmVyYXRlIE1ENSBIYXNofCBDe1JlZGlzIENhY2hlfQogICAgQyAtLT588J%2BOiSBDYWNoZSBIaXR8IERbU2VydmUgQ2FjaGVkIERpYWdub3Npc10KICAgIEQgLS0%2BfE8nMScgTGF0ZW5jeXwgQQogICAgQyAtLT584pqg77iPIENhY2hlIE1pc3N8IEVbRGVlcCBMZWFybmluZyBQaXBlbGluZV0KICAgIEUgLS0%2BIEYoKFRlbnNvckZsb3cgLyBLZXJhcyBDTk4pKQogICAgRiAtLT58T3V0cHV0IFByZWRpY3Rpb258IEdbU2F2ZSBSZXN1bHQgdG8gUmVkaXNdCiAgICBHIC0tPiBB" alt="AnemiaSense system architecture flow displaying web browser upload, MD5 hash fingerprint calculation, Redis Cache lookup, and the TensorFlow/Keras inference fallback pathway." width="632" height="915"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Figure 1: High-level system flow depicting the API request interception and distributed cache validation loop.
  &lt;p&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation Deep Dive: The Code
&lt;/h2&gt;

&lt;p&gt;The implementation is written in Python using Flask and the Redis client library. It is designed to be resilient, performant, and self-cleaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Graceful Connection Resilience
&lt;/h3&gt;

&lt;p&gt;In system design, fallback options are vital. If the Redis server goes offline, the web application must not crash. The code below attempts a connection and, if it fails, falls back gracefully to running the application without cache support:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# Initialize external Redis Cache connection
&lt;/span&gt;&lt;span class="n"&gt;redis_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REDIS_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;redis_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;redis_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Test if the database is responding
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Distributed Cache initialized: Connected to Redis successfully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Warning: Failed to connect to Redis. Running without cache. Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;redis_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; No REDIS_URL environment variable found. Cache layer disabled by default.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Intercept, Hashing, and Lookup Loop
&lt;/h3&gt;

&lt;p&gt;Inside our Flask &lt;code&gt;/predict&lt;/code&gt; endpoint, we read the raw incoming bytes of the file, generate the MD5 hash key, query Redis, and return immediately if there is a match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Read bytes and generate the cryptographic hash key
&lt;/span&gt;&lt;span class="n"&gt;file_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;file_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Crucial: Reset the file stream pointer so it can still be saved to disk on a miss
&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&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="c1"&gt;# 2. Check the Cache (Redis RAM)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cached_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached_result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; CACHE HIT! Bypassing inference model for hash: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_hash&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prediction&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prediction&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;probability&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;probability&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# Informs the frontend UI to display the cache badge
&lt;/span&gt;        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note on the Stream Pointer:&lt;/em&gt; Calling &lt;code&gt;file.read()&lt;/code&gt; consumes the image data. We must run &lt;code&gt;file.seek(0)&lt;/code&gt; to reset the file's internal pointer back to the beginning. If we forget this, any attempt to save the image for the model on a cache miss will result in a corrupted, empty file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Caching Write Loop (Cache Miss)
&lt;/h3&gt;

&lt;p&gt;If the fingerprint is not found in Redis, the system saves the image, runs the Keras model, deletes the temporary file to preserve disk space, and saves the new result to Redis with a &lt;strong&gt;24-hour expiration time&lt;/strong&gt; (Time-To-Live, or TTL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 3. Cache Miss: Execute Keras Model Inference
&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;predicted_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;probability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;predict_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Remove the temporary image from disk
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Save the result to Redis (Expired automatically after 24 hours)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prediction&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;predicted_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;probability&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 86,400 seconds = 24 hours
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quantifying the Impact
&lt;/h2&gt;

&lt;p&gt;By shifting the computational load from CPU/GPU-intensive model calculations to lightweight memory lookups, we achieved substantial optimization:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Performance Metric&lt;/th&gt;
&lt;th&gt;Without Redis (Cache Miss / First Run)&lt;/th&gt;
&lt;th&gt;With Redis (Cache Hit)&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Response Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~300 ms&lt;/code&gt; to &lt;code&gt;~320 ms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~2 ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.3% Latency Reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$O(N)$ (CNN layer-by-layer forward pass)&lt;/td&gt;
&lt;td&gt;$O(1)$ (Constant-time hash lookup)&lt;/td&gt;
&lt;td&gt;Infinite scalability benefit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource Overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High CPU utilization spike&lt;/td&gt;
&lt;td&gt;Negligible (microsecond RAM fetch)&lt;/td&gt;
&lt;td&gt;Lower server hosting costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User Experience&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slight load delay&lt;/td&gt;
&lt;td&gt;Instantaneous display&lt;/td&gt;
&lt;td&gt;Lightning-fast interface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the frontend, when the JavaScript UI receives &lt;code&gt;'cached': true&lt;/code&gt; from the backend, it dynamically displays a &lt;strong&gt;&lt;code&gt;(Cache Hit)&lt;/code&gt;&lt;/strong&gt; badge next to the diagnosis. This visually demonstrates the performance optimization directly to the user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enterprise Parallels: How Tech Giants Scale
&lt;/h2&gt;

&lt;p&gt;While caching a custom CNN is a valuable project-level optimization, this exact system design pattern is what keeps the internet's most popular platforms online:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Autocomplete:&lt;/strong&gt; When you type into Google's search bar, it suggests terms instantly. Google cannot search its entire web index database for every keystroke. Instead, they cache popular autocomplete results in high-speed, in-memory databases like Redis. Common query results are served directly from RAM in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter / X Timelines:&lt;/strong&gt; Twitter doesn't run a complex query to search millions of accounts and find recent posts every time you refresh your feed. They compile and cache active user timelines directly in Redis, delivering your home feed instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netflix Session Tracking:&lt;/strong&gt; When you pause a show on your TV and open it on your phone, Netflix knows the exact second you stopped. They use Redis to track and sync live player coordinates and session data in real-time.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Advanced Considerations: Edge Cases &amp;amp; Trade-offs
&lt;/h2&gt;

&lt;p&gt;A professional system design requires understanding the trade-offs of your choices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Storage Management (RAM limits):&lt;/strong&gt; Redis RAM is expensive. Storing raw images in Redis would fill up the memory and crash the server. We solved this by generating an MD5 hash, caching only the lightweight prediction string (a few bytes) rather than the original image data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Collision Risk:&lt;/strong&gt; While MD5 is fast, it has a tiny risk of "collisions" (two different images generating the same hash). In a mission-critical clinical setting, swapping MD5 for &lt;strong&gt;SHA-256&lt;/strong&gt; offers a much larger keyspace to eliminate collision risk with minimal CPU overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Drift &amp;amp; Eviction:&lt;/strong&gt; AI models are updated over time. If we train a new version of our CNN, the old cached predictions in Redis will become inaccurate. Setting a &lt;strong&gt;24-hour TTL&lt;/strong&gt; ensures that stale results naturally expire, while updating the cache key prefix on model redeployment guarantees that old data is never served.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;System design is the art of choosing the right tool for the job. By placing an in-memory Redis cache in front of a deep learning model, we decoupled our application's response time from the complexity of the neural network. Caching is a simple, high-impact pattern that saves hosting costs, scales backend infrastructure, and provides a snappy, seamless user experience.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How do you manage latency in your machine learning deployments? Let’s connect and discuss in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
