<?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: RyannB01</title>
    <description>The latest articles on DEV Community by RyannB01 (@ryannb01).</description>
    <link>https://dev.to/ryannb01</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%2F3955652%2F22b1a4f4-bf2b-45ad-90d5-f6496e550e9d.png</url>
      <title>DEV Community: RyannB01</title>
      <link>https://dev.to/ryannb01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ryannb01"/>
    <language>en</language>
    <item>
      <title>Best Delivery App Near You — GitHub Finish-Up-A-Thon Submission</title>
      <dc:creator>RyannB01</dc:creator>
      <pubDate>Thu, 28 May 2026 04:30:40 +0000</pubDate>
      <link>https://dev.to/ryannb01/best-delivery-app-near-you-github-finish-up-a-thon-submission-4p64</link>
      <guid>https://dev.to/ryannb01/best-delivery-app-near-you-github-finish-up-a-thon-submission-4p64</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Delivery App Near You&lt;/strong&gt; is a web app that tells you which food delivery app — Swiggy or Zomato — is actually the best option for your specific area in India, right now.&lt;/p&gt;

&lt;p&gt;You type in your area name or pincode (or hit "Use My Location"), and the app returns a ranked list showing which platform is fastest, highest rated, and has the most active offers in your neighborhood. Rankings are explained transparently, and the underlying ML model improves over time as users submit feedback.&lt;/p&gt;

&lt;p&gt;This project means a lot to me because it solves a genuinely annoying real-world problem. Every time I want to order food, I open both apps, compare delivery times, check for offers, and then decide. That's a 2-minute ritual I wanted to automate — and it turns out the data problem behind it is surprisingly hard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/RyannB01" rel="noopener noreferrer"&gt;
        RyannB01
      &lt;/a&gt; / &lt;a href="https://github.com/RyannB01/best-delivery" rel="noopener noreferrer"&gt;
        best-delivery
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Best Delivery App Near You&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A web application that ranks food delivery apps (Swiggy and Zomato) for any Indian pincode based on real scraped data, scored by a lightweight ML model trained on delivery time, ratings, and offers.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What It Does&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A user enters their area name or pincode. The app returns a ranked list of delivery apps available in that area, showing which is fastest, highest rated, and has the most active offers. Rankings are explained transparently and improve over time as users submit feedback.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vanilla JavaScript, HTML, CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python, FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scraping&lt;/td&gt;
&lt;td&gt;Playwright (Zomato), requests (Swiggy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML Model&lt;/td&gt;
&lt;td&gt;scikit-learn RandomForestRegressor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geocoding&lt;/td&gt;
&lt;td&gt;Nominatim (OpenStreetMap) — free, no API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pincode lookup&lt;/td&gt;
&lt;td&gt;India Post API — free, no API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;CSV files (raw + engineered features)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Vercel (frontend), Render (backend)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;User enters pincode/area
        │
        ▼
Frontend (Vanilla JS)
  - Calls GET /rank?area=kochi&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/RyannB01/best-delivery" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&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%2F1fry3us3nkmc62e48k0s.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%2F1fry3us3nkmc62e48k0s.png" alt="Empty search UI: The app's homepage showing two input fields (area name and pincode), category filter chips (All, Pizza, Burger, Biryani, Chinese, Desserts, Groceries) with " width="800" height="481"&gt;&lt;/a&gt;&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%2Fpbao12cqjllxtkcq9n84.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%2Fpbao12cqjllxtkcq9n84.png" alt="Filled search with Burger selected: The search form filled in with " width="800" height="392"&gt;&lt;/a&gt;&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%2Fw6bhca35h4x14cu2zt1f.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%2Fw6bhca35h4x14cu2zt1f.png" alt=" Ranked results for Kochi burgers: Ranked delivery app results for Kochi (pincode 682003) filtered for burgers. Swiggy ranks #1 with a score of 68.9, showing 29 min delivery, 4.5 stars, and offers available. Zomato ranks #2 with a score of 63.4, showing 30 min delivery and 4 stars. Both cards have Helpful/Not helpful feedback buttons. A " width="800" height="665"&gt;&lt;/a&gt;&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%2Fdsf794scs5111llq5ik8.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%2Fdsf794scs5111llq5ik8.png" alt="How rankings work panel: The " width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where it started
&lt;/h3&gt;

&lt;p&gt;The project existed as a rough idea with a half-working scraper and no real structure. I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Playwright script that sometimes captured Zomato responses and sometimes didn't&lt;/li&gt;
&lt;li&gt;A Swiggy scraper that only worked for hardcoded city-center coordinates&lt;/li&gt;
&lt;li&gt;No backend, no ML model, no frontend&lt;/li&gt;
&lt;li&gt;A pile of JSON files from manual browser sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core problem I kept hitting: &lt;strong&gt;no public APIs exist for Indian food delivery apps&lt;/strong&gt;. Swiggy discontinued their public API years ago. Zomato's is B2B only. Blinkit has none. Everything had to be scraped, and scraping is fragile.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I changed, fixed, and added
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scraper overhaul:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swiggy now uses precise pincode-level coordinates via Nominatim (OpenStreetMap's free geocoding API) instead of hardcoded city centers. This means two pincodes in the same city get different restaurant lists, which is how the apps actually work.&lt;/li&gt;
&lt;li&gt;Zomato scraping was rewritten to use Playwright's response interception (&lt;code&gt;page.on("response", ...)&lt;/code&gt;) to capture the &lt;code&gt;webroutes/search/home&lt;/code&gt; API call instead of trying to parse the DOM — much more reliable.&lt;/li&gt;
&lt;li&gt;Added stealth JS injection to avoid bot detection.&lt;/li&gt;
&lt;li&gt;Added India Post API integration to resolve any Indian pincode to a city name automatically.&lt;/li&gt;
&lt;li&gt;Pre-scraped 28 cities across India.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On-demand scraping:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a user searches for a pincode not in the database, the frontend automatically calls &lt;code&gt;/refresh&lt;/code&gt;, which triggers a live scrape, runs feature engineering, reloads the data, and then returns rankings. New cities are added automatically without any manual intervention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full ML pipeline:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;feature_engineering.py&lt;/code&gt; cleans raw scraped data and computes a hand-crafted score (delivery time 35%, rating 30%, fee 15%, offers 10%, distance 10%).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;train.py&lt;/code&gt; trains a &lt;code&gt;RandomForestRegressor&lt;/code&gt; on those scores. Initially it replicates the formula; over time it learns from real user feedback — a simplified version of the Learn-to-Rank approach used by Google and Amazon.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retrain.py&lt;/code&gt; adjusts training scores based on helpful/not helpful votes and retrains the model.&lt;/li&gt;
&lt;li&gt;Model performance on current data: Train R² ~0.99, Test R² ~0.97, Test MAE ~1.5 points on a 0–100 scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FastAPI backend from scratch:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/rank&lt;/code&gt; — queries engineered features, runs the ML model, returns ranked apps with stats&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/refresh&lt;/code&gt; — on-demand scrape pipeline triggered by the frontend&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/explain&lt;/code&gt; — returns feature importances from the trained model&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/feedback&lt;/code&gt; — stores helpful/not helpful votes, persisted to a JSONL file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/retrain&lt;/code&gt; — retrains the model on collected feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-page app in vanilla JS, HTML, CSS — no framework, no build step.&lt;/li&gt;
&lt;li&gt;Category chips (Pizza, Burger, Biryani, etc.) that auto-re-rank when switched.&lt;/li&gt;
&lt;li&gt;"Use My Location" via browser geolocation + Nominatim reverse geocoding.&lt;/li&gt;
&lt;li&gt;"Why this ranking?" panel with animated feature importance bars.&lt;/li&gt;
&lt;li&gt;Helpful/Not helpful feedback buttons on each result card.&lt;/li&gt;
&lt;li&gt;Graceful handling of the on-demand scrape flow with a loading state that explains what's happening ("New area detected — scraping live data, this takes ~2 minutes...").&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Copilot was genuinely useful throughout this project, but in specific ways — not as a magic wand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it saved the most time:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Swiggy API response parsing was the most tedious part. The response is a deeply nested JSON with inconsistent structure — restaurants are buried inside &lt;code&gt;data.cards[n].card.card.gridElements.infoWithStyle.restaurants&lt;/code&gt;. Copilot autocompleted the entire nested access chain after I typed the first two levels, and got it right. That alone saved 20 minutes of trial-and-error.&lt;/p&gt;

&lt;p&gt;For the FastAPI endpoints, once I wrote the first endpoint (&lt;code&gt;/rank&lt;/code&gt;) with full docstrings and type hints, Copilot generated accurate skeletons for &lt;code&gt;/feedback&lt;/code&gt;, &lt;code&gt;/retrain&lt;/code&gt;, and &lt;code&gt;/health&lt;/code&gt; that needed only minor edits. It understood the pattern from context.&lt;/p&gt;

&lt;p&gt;The feature engineering scoring formula was another win. I described the weights in a comment (&lt;code&gt;# delivery time 35%, rating 30%...&lt;/code&gt;) and Copilot wrote the normalized score calculation correctly on the first try, including the &lt;code&gt;.clip()&lt;/code&gt; calls to handle outliers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it needed guidance:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Playwright scraping logic required more back-and-forth. Copilot's initial suggestions used &lt;code&gt;page.wait_for_selector()&lt;/code&gt; on DOM elements, which doesn't work well for a React app that renders asynchronously. I had to steer it toward the response interception approach (&lt;code&gt;page.on("response", ...)&lt;/code&gt;) which is more reliable. Once I showed it that pattern in a comment, it followed it correctly.&lt;/p&gt;

&lt;p&gt;The on-demand scrape flow in &lt;code&gt;app.js&lt;/code&gt; — where the frontend catches a 404, calls &lt;code&gt;/refresh&lt;/code&gt;, waits, then retries &lt;code&gt;/rank&lt;/code&gt; — was something I had to write manually. Copilot kept suggesting simpler error handling that didn't account for the 2-3 minute scrape time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overall:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Copilot is best described as a very fast typist who has read a lot of code. It handles boilerplate, repetitive patterns, and well-known API structures extremely well. For novel logic — like the fallback chain in the scraper or the feedback-adjusted retraining — you still need to think it through yourself and use Copilot to execute the implementation. That's a fair division of labor.&lt;/p&gt;

&lt;p&gt;The project took about a week of evenings. The code itself probably represents 3-4 days of work; the rest was data validation, testing the scraper against real pincodes, and debugging Zomato's bot detection. Copilot helped with the code. The data problem required human time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with FastAPI, scikit-learn, Playwright, Vanilla JS, Nominatim, and India Post API. No paid APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
    </item>
  </channel>
</rss>
