<?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: CodersAcademy006</title>
    <description>The latest articles on DEV Community by CodersAcademy006 (@codersacademy006).</description>
    <link>https://dev.to/codersacademy006</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%2F1255300%2Fa1d1ad8f-1435-40e8-913e-a93aa5a15055.jpeg</url>
      <title>DEV Community: CodersAcademy006</title>
      <link>https://dev.to/codersacademy006</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codersacademy006"/>
    <language>en</language>
    <item>
      <title>Butterflies don't take weekends off: 40,000 Pune sightings and a forecast that fell apart when I removed one column</title>
      <dc:creator>CodersAcademy006</dc:creator>
      <pubDate>Sat, 10 Oct 2026 06:27:30 +0000</pubDate>
      <link>https://dev.to/codersacademy006/butterflies-dont-take-weekends-off-40000-pune-sightings-and-a-forecast-that-fell-apart-when-i-1g4</link>
      <guid>https://dev.to/codersacademy006/butterflies-dont-take-weekends-off-40000-pune-sightings-and-a-forecast-that-fell-apart-when-i-1g4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On an average Sunday, Pune's naturalists log &lt;strong&gt;12.2&lt;/strong&gt; butterfly species. On an average Monday they log &lt;strong&gt;4.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I did not believe the butterflies had a schedule, so I built a forecaster, backtested it on a year it had never seen, and then took away the one feature I suspected. The forecast went from useful to useless.&lt;/p&gt;

&lt;p&gt;This post is the build, the number, and the afternoon a worker process got &lt;code&gt;kill -9&lt;/code&gt; in the middle of a 13-minute download and the pipeline finished anyway.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Fieldcast&lt;/strong&gt; answers the question that comes before "what is this bird?". It answers &lt;em&gt;which day should I go?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the next seven days near Pune it forecasts how many butterfly species and how many bird species local observers will log, drops the wet days, and prints the best one on a single page with the species most likely to turn up. You read the card, pocket the phone, and go.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Place&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pune, 30 km radius (18.52, 73.86)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;40,186 research-grade iNaturalist observations (2019 to 4 Oct 2026) and daily Open-Meteo weather&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TabPFN v2, open weights, CPU only, no training loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemma 3 4B running locally through Ollama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plumbing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Temporal workflow with retries, heartbeats and checkpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One printable card, light, dark and print layouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;365 held-out days against a seasonal average and gradient boosting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;&lt;strong&gt;Live card: &lt;a href="https://codersacademy006.github.io/fieldcast/card.html" rel="noopener noreferrer"&gt;codersacademy006.github.io/fieldcast/card.html&lt;/a&gt;&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjltf961bi7nxz0hbymro.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjltf961bi7nxz0hbymro.png" alt="Fieldcast card on desktop, light theme" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On a phone, which is where you will read it before you leave:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet3igyd6hru16vc29t9c.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet3igyd6hru16vc29t9c.png" alt="Fieldcast card on a phone, dark theme" width="800" height="1965"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And on paper, so the phone can stay in your pocket:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8662e2ffk74g3dsnyw4g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8662e2ffk74g3dsnyw4g.png" alt="Fieldcast card, print layout" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The forecast it generated on 6 October 2026:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwb9zqr3lirqm67hwpfr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwb9zqr3lirqm67hwpfr.png" alt="Bar chart of forecast butterfly and bird species per day for 6 to 12 October, with the weekend highlighted" width="799" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It picks &lt;strong&gt;Saturday 10 October&lt;/strong&gt;: dry, 34.8 C, about 27 butterfly species and 20 bird species expected. The likeliest sightings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Species&lt;/th&gt;
&lt;th&gt;Model-estimated chance it is logged that day&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lemon Migrant&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lemon Pansy&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common Mormon Swallowtail&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Red Pierrot&lt;/td&gt;
&lt;td&gt;66%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at that chart again. Five weekdays sit near 19, then the weekend jumps to 27. I will come back to that jump, because it turned out to be the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the forecast work?
&lt;/h2&gt;

&lt;p&gt;I trained on everything before 5 October 2025 (2,469 days) and tested on the next 365 days. Two baselines: a &lt;strong&gt;seasonal average&lt;/strong&gt; (the mean for that time of year) and &lt;strong&gt;gradient boosting&lt;/strong&gt; on the same features.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Rank correlation (Spearman)&lt;/th&gt;
&lt;th&gt;Error (species)&lt;/th&gt;
&lt;th&gt;Best-day lift&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Butterflies&lt;/td&gt;
&lt;td&gt;Seasonal average&lt;/td&gt;
&lt;td&gt;0.36&lt;/td&gt;
&lt;td&gt;6.03&lt;/td&gt;
&lt;td&gt;0.65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Butterflies&lt;/td&gt;
&lt;td&gt;Gradient boosting&lt;/td&gt;
&lt;td&gt;0.48&lt;/td&gt;
&lt;td&gt;5.34&lt;/td&gt;
&lt;td&gt;1.45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Butterflies&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TabPFN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.52&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5.14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Birds&lt;/td&gt;
&lt;td&gt;Seasonal average&lt;/td&gt;
&lt;td&gt;-0.04&lt;/td&gt;
&lt;td&gt;11.40&lt;/td&gt;
&lt;td&gt;0.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Birds&lt;/td&gt;
&lt;td&gt;Gradient boosting&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.38&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.27&lt;/td&gt;
&lt;td&gt;1.54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Birds&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TabPFN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.37&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.36&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.69&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Best-day lift:&lt;/em&gt; each week I took the day the model ranked highest and divided what was logged that day by the week's average day. 1.0 means the pick was no better than that week's average day. TabPFN's picks averaged 1.4 times the butterfly species and 1.7 times the bird species of a normal day.&lt;/p&gt;

&lt;p&gt;TabPFN wins on butterflies and ties gradient boosting on birds, with the lower error. On individual species it beats the seasonal average for 11 of 14 (ROC AUC), and loses on three: Lemon Migrant, Red Pierrot and Rose-ringed Parakeet, which scores 0.476, below a coin flip. AUC measures ranking only; I did not validate calibration, so read the percentages as model estimates, not calibrated probabilities. The full per-species table is in the repo.&lt;/p&gt;

&lt;p&gt;That is a decent result. It is also where most weekend projects stop, with a table and a pat on the back. I kept going because of the chart above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ablation that ate my result
&lt;/h2&gt;

&lt;p&gt;Why would a model that knows the season and the monsoon predict a weekend jump of 8 species? Because it learned something I handed it. One of my features is a &lt;code&gt;weekend&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;So I ran the backtest twice, with and without it. Same data, same split, same model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Weekend flag&lt;/th&gt;
&lt;th&gt;Rank correlation&lt;/th&gt;
&lt;th&gt;Best-day lift&lt;/th&gt;
&lt;th&gt;Weeks the pick was a weekend day&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Butterflies&lt;/td&gt;
&lt;td&gt;with&lt;/td&gt;
&lt;td&gt;0.52&lt;/td&gt;
&lt;td&gt;1.40&lt;/td&gt;
&lt;td&gt;52 of 52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Butterflies&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;without&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.33&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.95&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;13 of 52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Birds&lt;/td&gt;
&lt;td&gt;with&lt;/td&gt;
&lt;td&gt;0.37&lt;/td&gt;
&lt;td&gt;1.69&lt;/td&gt;
&lt;td&gt;52 of 52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Birds&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;without&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-0.07&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.06&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21 of 52&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without the flag, the butterfly forecast drops to a lift of 0.95, which is slightly worse than picking a day at random. The bird forecast falls to a rank correlation below zero. With the flag, &lt;strong&gt;every single week's best day is a weekend day&lt;/strong&gt;, 52 out of 52.&lt;/p&gt;

&lt;p&gt;The forecast is mostly a forecast of people.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same butterflies, three times the logging
&lt;/h2&gt;

&lt;p&gt;Here are all 2,834 days in the dataset, averaged by weekday. Each number is the count of distinct species logged that day.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mon&lt;/th&gt;
&lt;th&gt;Tue&lt;/th&gt;
&lt;th&gt;Wed&lt;/th&gt;
&lt;th&gt;Thu&lt;/th&gt;
&lt;th&gt;Fri&lt;/th&gt;
&lt;th&gt;Sat&lt;/th&gt;
&lt;th&gt;Sun&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Butterfly species&lt;/td&gt;
&lt;td&gt;3.98&lt;/td&gt;
&lt;td&gt;4.18&lt;/td&gt;
&lt;td&gt;4.29&lt;/td&gt;
&lt;td&gt;4.37&lt;/td&gt;
&lt;td&gt;4.40&lt;/td&gt;
&lt;td&gt;7.82&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.22&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bird species&lt;/td&gt;
&lt;td&gt;2.96&lt;/td&gt;
&lt;td&gt;3.09&lt;/td&gt;
&lt;td&gt;3.22&lt;/td&gt;
&lt;td&gt;3.39&lt;/td&gt;
&lt;td&gt;3.83&lt;/td&gt;
&lt;td&gt;5.52&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.13&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F289gojguae9xykexyhbb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F289gojguae9xykexyhbb.png" alt="Average butterfly and bird species logged per weekday in Pune, Monday 4.0 to Sunday 12.2" width="799" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Weekdays average 4.24 butterfly species and weekends 10.02, a factor of 2.4. For birds it is 3.30 against 6.32, a factor of 1.9. Now the number that explains it: observers log &lt;strong&gt;9.7 observations on a weekday and 25.3 on a weekend day&lt;/strong&gt;, a factor of 2.6. Divide species by observations and the two kinds of day come out close, about 0.44 species per observation on weekdays and 0.40 on weekends.&lt;/p&gt;

&lt;p&gt;Pune does not get more butterflies on Sunday. It gets more people holding phones. The model learned that correctly, and that is the problem with calling it a butterfly forecast.&lt;/p&gt;

&lt;p&gt;I kept the flag in the shipped model on purpose. If you are choosing a day for a trip, "a day when a lot will be logged" is a fair proxy for "a day when someone nearby is looking". But the card now says what it is, and I would not put it in front of anyone as a map of where the animals are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&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/CodersAcademy006" rel="noopener noreferrer"&gt;
        CodersAcademy006
      &lt;/a&gt; / &lt;a href="https://github.com/CodersAcademy006/fieldcast" rel="noopener noreferrer"&gt;
        fieldcast
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      TabPFN + local Gemma forecast of which day to go outside near Pune
    &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;Fieldcast&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Which day this week is best to go outside near Pune, and what will you probably see. TabPFN forecasts daily butterfly and bird species counts from weather and calendar. Local Gemma writes a one-page printable card.&lt;/p&gt;
&lt;p&gt;Finding that shaped the project: observers log 2.6 times more observations on weekends, so a forecast of logged species is largely a forecast of people. &lt;code&gt;ablate.py&lt;/code&gt; removes the weekend flag and shows how much of the skill disappears.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/CodersAcademy006/fieldcast/assets/weekday-effect.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FCodersAcademy006%2Ffieldcast%2FHEAD%2Fassets%2Fweekday-effect.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;python3.11 -m venv .venv &amp;amp;&amp;amp; .venv/bin/pip install -r requirements.txt
brew install ollama &amp;amp;&amp;amp; ollama serve &amp;amp; ollama pull gemma3:4b
.venv/bin/python fetch.py                                  # iNaturalist + Open-Meteo, about 20 min
TABPFN_ALLOW_CPU_LARGE_DATASET=1 .venv/bin/python model.py backtest   # about 10 min, writes metrics.json
TABPFN_ALLOW_CPU_LARGE_DATASET=1 .venv/bin/python model.py forecast   # about 9 min, writes forecast.json
.venv/bin/python card.py                                   # writes card.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Durable run (survives a killed worker, resumes the fetch from its page checkpoint), with the optional Sentry tracing of the Gemma step:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&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/CodersAcademy006/fieldcast" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hyxu5rv8u55nvip2jt9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hyxu5rv8u55nvip2jt9.png" alt="Pipeline: data, features, TabPFN, plain code, Gemma with a checker, card, all inside one Temporal workflow" width="798" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code owns the numbers, Gemma writes the words, Temporal keeps it all alive.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Features and model
&lt;/h3&gt;

&lt;p&gt;Day of year as sine and cosine, the weekend flag, a slow year trend, and rain and temperature windows over 7 and 30 days. The 30-day rain window is how the model learns the monsoon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/PriorLabs/TabPFN" rel="noopener noreferrer"&gt;TabPFN&lt;/a&gt; is a tabular foundation model, so there is no training loop to tune. One regressor predicts species per day and one classifier per common species predicts "will it be logged today". On a MacBook Air M5 CPU the backtest takes about 10 minutes and the forecast about 6.&lt;/p&gt;

&lt;p&gt;Ordinary Python then ranks the seven days, drops the wet ones and builds a data structure that holds every number that will appear on the card.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temporal: the afternoon I killed the worker
&lt;/h3&gt;

&lt;p&gt;The fetch alone takes about 13 minutes, because iNaturalist pages are slow and polite. A laptop sleeping, a network drop or one bad page should cost a retry, not the whole run. So the pipeline is a &lt;a href="https://temporal.io" rel="noopener noreferrer"&gt;Temporal&lt;/a&gt; workflow, and each step is an activity:&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;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_to_close_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&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;heartbeat_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&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;retry_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RetryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                     &lt;span class="n"&gt;backoff_coefficient&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;maximum_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&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;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetch_obs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;GROUPS&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                     &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetch_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merge_obs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_forecast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;make_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Butterflies, birds and weather download in parallel. Each long activity runs its blocking code in a thread and heartbeats every 10 seconds, so Temporal can tell a slow step from a dead one. The fetch also writes a checkpoint file after every page.&lt;/p&gt;

&lt;p&gt;Then I tested it properly. Mid-fetch, I ran &lt;code&gt;kill -9&lt;/code&gt; on the worker. Temporal got no goodbye. Two minutes later the heartbeat timeout fired, Temporal scheduled attempt 2, I started a fresh worker, and the fetch &lt;strong&gt;resumed from the last saved page&lt;/strong&gt; instead of starting over.&lt;/p&gt;

&lt;p&gt;Then it got more interesting, because the run failed for a real reason I had not planned. The forecast step died six times in a row with TabPFN's guard: &lt;em&gt;"Running on CPU with more than 1000 samples is not allowed by default."&lt;/em&gt; I had set the override as an environment variable in my shell, and the worker did not have it. I fixed it in code with one line in &lt;code&gt;model.py&lt;/code&gt;, restarted the worker, and the pending retry succeeded on attempt 7 without anyone re-running anything.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb37qq23kf7r7aphwtm91.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb37qq23kf7r7aphwtm91.png" alt="Temporal timeline: fetch_obs twice with a retry after the kill, run_forecast after repeated failures, finishing in 24 minutes 42 seconds" width="799" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Total wall time for the whole run, kill, bug and fix included: &lt;strong&gt;24 minutes 42 seconds&lt;/strong&gt;, status Completed. I checked that the resumed run matches a clean one. The weather table is identical. The resumed data has 40,191 rows against 40,186, and the extra 5 are observations people uploaded in between. The 7-day forecast agrees within 0.4 species on every day.&lt;/p&gt;
&lt;h3&gt;
  
  
  Gemma: let it talk, never let it count
&lt;/h3&gt;

&lt;p&gt;Gemma 3 4B writes three sentences on the card: why this day, what to look for and in what order, and a reminder to leave the phone alone. It gets the facts in its prompt. Every number on the card comes from code.&lt;/p&gt;

&lt;p&gt;A small model told to follow a format does not always follow it, so I measured. Over two batches of 30 cards, &lt;strong&gt;17 and 18 of them broke the three-sentence rule&lt;/strong&gt;, which is about three in five. Across the 60 cards, &lt;strong&gt;zero invented a number&lt;/strong&gt;. So the checker rejects any draft with the wrong length or any digit that is not in the facts, retries up to five times, and falls back to a plain template if all five fail. The model decides the wording. The code decides what counts as acceptable.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this does not show
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The backtest feeds the model the &lt;strong&gt;actual&lt;/strong&gt; weather for the test year, not archived forecasts, so real use will score lower.&lt;/li&gt;
&lt;li&gt;Gradient boosting is close behind TabPFN here. I will not claim a big win over it.&lt;/li&gt;
&lt;li&gt;Without the weekend flag, the forecast has little left. That is the finding, and it is also a limit.&lt;/li&gt;
&lt;li&gt;I have not yet carried the card outside and checked it against what I saw.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Run it
&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/CodersAcademy006" rel="noopener noreferrer"&gt;
        CodersAcademy006
      &lt;/a&gt; / &lt;a href="https://github.com/CodersAcademy006/fieldcast" rel="noopener noreferrer"&gt;
        fieldcast
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      TabPFN + local Gemma forecast of which day to go outside near Pune
    &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;Fieldcast&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Which day this week is best to go outside near Pune, and what will you probably see. TabPFN forecasts daily butterfly and bird species counts from weather and calendar. Local Gemma writes a one-page printable card.&lt;/p&gt;

&lt;p&gt;Finding that shaped the project: observers log 2.6 times more observations on weekends, so a forecast of logged species is largely a forecast of people. &lt;code&gt;ablate.py&lt;/code&gt; removes the weekend flag and shows how much of the skill disappears.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/CodersAcademy006/fieldcast/assets/weekday-effect.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FCodersAcademy006%2Ffieldcast%2FHEAD%2Fassets%2Fweekday-effect.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;python3.11 -m venv .venv &amp;amp;&amp;amp; .venv/bin/pip install -r requirements.txt
brew install ollama &amp;amp;&amp;amp; ollama serve &amp;amp; ollama pull gemma3:4b
.venv/bin/python fetch.py                                  # iNaturalist + Open-Meteo, about 20 min
TABPFN_ALLOW_CPU_LARGE_DATASET=1 .venv/bin/python model.py backtest   # about 10 min, writes metrics.json
TABPFN_ALLOW_CPU_LARGE_DATASET=1 .venv/bin/python model.py forecast   # about 9 min, writes forecast.json
.venv/bin/python card.py                                   # writes card.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Durable run (survives a killed worker, resumes the fetch from its page checkpoint), with the optional Sentry tracing of the Gemma step:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&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/CodersAcademy006/fieldcast" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3.11 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; .venv/bin/pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
brew &lt;span class="nb"&gt;install &lt;/span&gt;ollama &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ollama serve &amp;amp; ollama pull gemma3:4b
temporal server start-dev                      &lt;span class="c"&gt;# UI at localhost:8233&lt;/span&gt;
.venv/bin/python workflow.py worker            &lt;span class="c"&gt;# second terminal&lt;/span&gt;
.venv/bin/python workflow.py run               &lt;span class="c"&gt;# third terminal, writes card.html&lt;/span&gt;
.venv/bin/python ablate.py                     &lt;span class="c"&gt;# the weekend ablation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change &lt;code&gt;LAT&lt;/code&gt; and &lt;code&gt;LNG&lt;/code&gt; in &lt;code&gt;fetch.py&lt;/code&gt; and &lt;code&gt;model.py&lt;/code&gt; to point it at your own city.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run the ablation you are afraid of.&lt;/strong&gt; One column decided whether this project was a result or an artifact. The tables looked good right up until I removed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A forecast of observations is a forecast of observers.&lt;/strong&gt; Citizen-science counts carry the habits of the people who log them. Rank correlation will not tell you that. A weekday table will.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability is a design choice you make before the crash.&lt;/strong&gt; The heartbeat and the checkpoint cost about ten lines. The &lt;code&gt;kill -9&lt;/code&gt; cost me two minutes of waiting and nothing else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the language model describe and never decide.&lt;/strong&gt; Gemma is wrong about format three times in five and has never been wrong about a number, because it never makes one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replace actual weather with archived forecasts so the backtest matches real use.&lt;/li&gt;
&lt;li&gt;Model observer effort directly, so the forecast separates animals from people.&lt;/li&gt;
&lt;li&gt;Carry the card outside and compare it with what I actually see.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Anyone can check the weights.&lt;/strong&gt; TabPFN's v2 checkpoints are public on Hugging Face and the README pins them, so anyone can rerun the backtest and the ablation and get my numbers. The newer v3.5 weights sit behind a licence gate, so I used the open ones and stayed reproducible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Almost nothing leaves the machine.&lt;/strong&gt; The only network calls are two public APIs. Gemma runs locally, so the card writer needs no key and no account, and it sees a weather summary and nothing else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data is a commons.&lt;/strong&gt; Every row is a person who photographed a butterfly and let strangers identify it. A forecast built on that should be shareable the same way, so the repo has the fetch script, the model, the metrics and the ablation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 4B runs locally through Ollama and writes the card prose from code-supplied facts, behind a checker that rejects wrong length and invented numbers, measured over 60 runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of TabPFN:&lt;/strong&gt; the whole forecast is TabPFN, a regressor for daily counts and a classifier per species, backtested on a held-out year against gradient boosting and a seasonal baseline, then ablated to show how much of its skill is observer behaviour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Temporal:&lt;/strong&gt; the pipeline is a Temporal workflow with parallel activities, heartbeats, retries and checkpointed resume, demonstrated by killing the worker mid-fetch and by recovering from a real configuration failure on retry.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>I Built a Local DuckDB Engine to Mathematically Settle Cricket Debates</title>
      <dc:creator>CodersAcademy006</dc:creator>
      <pubDate>Fri, 29 May 2026 12:02:39 +0000</pubDate>
      <link>https://dev.to/codersacademy006/i-built-a-local-duckdb-engine-to-mathematically-settle-cricket-debates-5b38</link>
      <guid>https://dev.to/codersacademy006/i-built-a-local-duckdb-engine-to-mathematically-settle-cricket-debates-5b38</guid>
      <description>&lt;p&gt;For years, sports commentators have repeated the same clichés about T20 cricket: &lt;em&gt;"You have to win the powerplay,"&lt;/em&gt; &lt;em&gt;"You need an anchor to win,"&lt;/em&gt; and &lt;em&gt;"Team X always chokes."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;I wanted to know if any of that was actually true. &lt;/p&gt;

&lt;p&gt;The problem is that querying 15+ years of historical ball-by-ball telemetry (over 294,000 deliveries) via commercial sports APIs is painfully slow and brutally rate-limited. &lt;/p&gt;

&lt;p&gt;So, I built &lt;a href="https://github.com/CodersAcademy006/Midwicket" rel="noopener noreferrer"&gt;Midwicket&lt;/a&gt;—an open-source SDK that bypasses APIs entirely. It pulls raw open data into a local DuckDB and PyArrow engine, turning your laptop into a sub-millisecond sports data warehouse. &lt;/p&gt;

&lt;p&gt;To test the architecture, I wrote a logistic regression Win Probability model (AUC 0.87) trained on 1,239 IPL matches. Because the query engine is entirely local, I could calculate the exact &lt;strong&gt;Win Probability Added (WPA)&lt;/strong&gt; for every single ball ever bowled. &lt;/p&gt;

&lt;p&gt;When I ran the numbers, the data completely broke some of the sport's biggest myths.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The "Choke Index" is real, and it's ruthless.
&lt;/h2&gt;

&lt;p&gt;I queried the database for every match where a chasing team reached an &lt;strong&gt;80% Win Probability&lt;/strong&gt; at any point, but still managed to lose the game. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Franchise&lt;/th&gt;
&lt;th&gt;80%+ WP Matches&lt;/th&gt;
&lt;th&gt;Chokes&lt;/th&gt;
&lt;th&gt;Choke %&lt;/th&gt;
&lt;th&gt;Rating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Royal Challengers Bangalore (RCB)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notorious&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mumbai Indians (MI)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chennai Super Kings (CSK)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kolkata Knight Riders (KKR)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Composed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The Finding:&lt;/strong&gt; RCB has a staggering 66.7% choke rate from commanding positions. If they reach an 80% probability of winning, they are mathematically more likely to throw it away than finish the job. KKR, on the other hand, converts almost 78% of these positions.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Anchor vs. The Finisher (Kohli vs Dhoni)
&lt;/h2&gt;

&lt;p&gt;Who is more valuable in a run chase: the guy who bats for 15 overs to build a foundation (Kohli), or the guy who comes in at the end (Dhoni)? &lt;/p&gt;

&lt;p&gt;The WPA math heavily favors the finisher. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Player&lt;/th&gt;
&lt;th&gt;Total Innings Analyzed&lt;/th&gt;
&lt;th&gt;Positive WPA %&lt;/th&gt;
&lt;th&gt;Average WPA per Innings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MS Dhoni&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+46.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Virat Kohli&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+25.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The Finding:&lt;/strong&gt; Kohli is vastly consistent, but Dhoni mathematically contributes ~20% more total win probability per innings. The model reveals that run-chases are highly non-linear; the leverage in the final 3 overs is so massive that a finisher essentially holds the entire win probability of the team in their hands.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. "Winning the Powerplay" is a myth. Over 19 is the cliff.
&lt;/h2&gt;

&lt;p&gt;If you simulate a catastrophic 2-wicket collapse at any point in a chase, the Powerplay (Overs 1-6) is surprisingly forgiving—a collapse there drops your win probability by about 25%. &lt;/p&gt;

&lt;p&gt;However, a collapse in &lt;strong&gt;Over 19&lt;/strong&gt; drops it by &lt;strong&gt;60.9%&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;By the 19th over, the margin of error mathematically compresses to zero. It is the definitive breaking point of the sport.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Open Source Engine
&lt;/h3&gt;

&lt;p&gt;If you’re a sports analytics nerd, a data engineer, or just someone who wants to run SQL queries against 15 years of sports data without paying for an API, I’ve open-sourced the entire engine, the PyArrow schema, and the trained models. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the repo here:&lt;/strong&gt; &lt;a href="https://github.com/CodersAcademy006/Midwicket" rel="noopener noreferrer"&gt;Midwicket on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d love to hear your thoughts on the architecture, the DuckDB implementation, or the data findings! What team should I run the choke index on next?&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>data</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
