<?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: Bhushan Vidhate</title>
    <description>The latest articles on DEV Community by Bhushan Vidhate (@bhushan_vidhate).</description>
    <link>https://dev.to/bhushan_vidhate</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%2F4168958%2Fa7a30c59-bfda-42c8-87ef-b28ec83154df.png</url>
      <title>DEV Community: Bhushan Vidhate</title>
      <link>https://dev.to/bhushan_vidhate</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bhushan_vidhate"/>
    <language>en</language>
    <item>
      <title>Eventually</title>
      <dc:creator>Bhushan Vidhate</dc:creator>
      <pubDate>Sun, 25 Oct 2026 18:13:29 +0000</pubDate>
      <link>https://dev.to/bhushan_vidhate/eventually-4cfb</link>
      <guid>https://dev.to/bhushan_vidhate/eventually-4cfb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&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%2Fqyjdb85d7mipace7yyom.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%2Fqyjdb85d7mipace7yyom.png" alt="Eventually cover" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Eventually is a small Python CLI that tidies a folder without opening a single file. You point it at &lt;code&gt;~/Downloads&lt;/code&gt;; it reads the names of the loose files, asks a local open-weight model where each one belongs, prints a plan, and waits. &lt;code&gt;--apply&lt;/code&gt; moves them. &lt;code&gt;--undo&lt;/code&gt; puts every file back.&lt;/p&gt;

&lt;p&gt;It is not a file manager. It has one job: close the open loop that is my Downloads folder.&lt;/p&gt;

&lt;p&gt;The model is Llama 3.2 1B, running through Ollama on my laptop. It chooses among 11 folders: Documents, Spreadsheets, Images, Screenshots, Installers, Archives, Audio, Video, Code, Receipts, and To Sort. The filenames never leave the machine. Neither do the files.&lt;/p&gt;

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

&lt;p&gt;The 60–90 second demo script is in &lt;a href="https://github.com/BhushanVidhate-27/eventually/blob/main/DEMO.md" rel="noopener noreferrer"&gt;DEMO.md&lt;/a&gt;. No hosted server: local Ollama plus 20 generated filenames.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 eventually.py &lt;span class="nt"&gt;--demo&lt;/span&gt;
python3 eventually.py demo-downloads &lt;span class="nt"&gt;--apply&lt;/span&gt;
python3 eventually.py &lt;span class="nt"&gt;--undo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three beats: plan (20 files, 11 folders, 9.7s, zero bytes uploaded), apply (a boring tree), undo (all 20 files back). The last beat matters most: a lazy tool should be easy to reverse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/BhushanVidhate-27/eventually" rel="noopener noreferrer"&gt;github.com/BhushanVidhate-27/eventually&lt;/a&gt;. One Python file, standard library only, plus 21 tests and an MIT license.&lt;/p&gt;

&lt;p&gt;No pip install. No API key. No telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I started with the chore, not the model. The chore is opening Downloads, seeing &lt;code&gt;lease-agreement-signed.pdf&lt;/code&gt; next to &lt;code&gt;Figma.dmg&lt;/code&gt; and &lt;code&gt;voice-note-mom.m4a&lt;/code&gt;, making twenty tiny decisions, then closing the window. I wanted a tool that would make the decision once and then get out of the way.&lt;/p&gt;

&lt;p&gt;The implementation is plain Python: &lt;code&gt;pathlib&lt;/code&gt; for the folder, &lt;code&gt;urllib&lt;/code&gt; for Ollama's &lt;code&gt;/api/chat&lt;/code&gt; endpoint, and a JSON schema to constrain the model to the 11 folder names. Each file gets one request. The model returns a folder, a confidence, and a one-line reason; the plan groups the results and prints them.&lt;/p&gt;

&lt;p&gt;The first version sent all 20 filenames in a single prompt. The 1B model looped, repeated names, and invented folders. I switched to one filename per request, four in flight, temperature 0, and a fixed seed. It was slower per request and far more reliable: twenty small decisions, about half a second each. Slow and steady was also the better engineering choice.&lt;/p&gt;

&lt;p&gt;A 1B model still makes obvious mistakes. It once put &lt;code&gt;main.py&lt;/code&gt; in Spreadsheets. A thin guardrail layer catches those: source-code extensions are Code, installers are Installers, a receipt photo is a Receipt, a text note is a Document. The model still decides the ambiguous cases — is this PDF a lease or an invoice? — but it is never trusted with an obvious extension. If the model errors, the file goes to To Sort, not a guessed folder.&lt;/p&gt;

&lt;p&gt;Safety is the rest of the product. The default is a dry run. It only looks at top-level files, skips hidden files, symlinks, and in-progress downloads, and never overwrites; a collision becomes &lt;code&gt;report (2).pdf&lt;/code&gt;. &lt;code&gt;--apply&lt;/code&gt; writes a manifest to &lt;code&gt;~/.eventually/last-run.json&lt;/code&gt;, and &lt;code&gt;--undo&lt;/code&gt; replays it in reverse. The tests use a fake classifier, so they run without a model: parsing, guardrails, discovery, collisions, apply, undo, and model failure. All 21 pass.&lt;/p&gt;

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

&lt;p&gt;Look at the filenames a tool like this sees: &lt;code&gt;lease-agreement-signed.pdf&lt;/code&gt;, &lt;code&gt;invoice-october-acme.pdf&lt;/code&gt;, &lt;code&gt;tax-2025-final-v2.pdf&lt;/code&gt;, &lt;code&gt;voice-note-mom.m4a&lt;/code&gt;. That is not just file metadata. It is a sketch of my finances, my home, and my family. Sending it to a closed API to avoid opening Finder is a bad trade.&lt;/p&gt;

&lt;p&gt;Local open weights make the private version the default. The brain is a 1.3 GB file on my disk. It works on a plane, needs no key or credit card, and cannot be deprecated by a pricing page. The harness is open source too, so anyone can read every line and confirm that the only network call is to &lt;code&gt;localhost:11434&lt;/code&gt;. That is a different kind of trust than a privacy policy.&lt;/p&gt;

&lt;p&gt;Open innovation also makes small problems legible. I do not need a frontier model to know that a &lt;code&gt;.dmg&lt;/code&gt; is an installer. A 1B model plus a careful harness is a complete product for a chore that would never justify a startup, a subscription, or a data pipeline. Open weights gave me permission to build something small and local, and open source gives the next person permission to change the folder names and make it theirs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;I measured the demo on this machine: an Apple M2 Mac running macOS 27.0.1, with &lt;code&gt;llama3.2:1b&lt;/code&gt; already loaded. Eventually planned 20 files in &lt;strong&gt;9.7 seconds&lt;/strong&gt;. My honest manual baseline is about &lt;strong&gt;4 minutes&lt;/strong&gt; for the same folder, because the time is mostly deciding, not dragging. That is roughly &lt;strong&gt;3.8 minutes back every week&lt;/strong&gt;, or &lt;strong&gt;3.3 hours a year&lt;/strong&gt;, plus one less open loop in Finder.&lt;/p&gt;

&lt;p&gt;The number I care about is smaller and harder to measure: I no longer feel a small tax every time I open Downloads. The folder is either tidy or it is waiting for one command. Either way, it is not asking me a question.&lt;/p&gt;

&lt;p&gt;Laziness should be reversible. Eventually plans before it moves, never overwrites, and keeps an undo manifest. That is the difference between automating a chore and creating a new one.&lt;/p&gt;

&lt;p&gt;One command, one job, finished.&lt;/p&gt;

&lt;h1&gt;
  
  
  devchallenge #hf26challenge
&lt;/h1&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>StockRadar</title>
      <dc:creator>Bhushan Vidhate</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:48:29 +0000</pubDate>
      <link>https://dev.to/bhushan_vidhate/stockradar-55bj</link>
      <guid>https://dev.to/bhushan_vidhate/stockradar-55bj</guid>
      <description>&lt;h1&gt;
  
  
  Hacktoberfest DEV Challenge Submission - StockRadar
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Project&lt;/strong&gt;: StockRadar - Open-weight stockout early warning for tiny neighbourhood shops&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/BhushanVidhate-27/side-hustle" rel="noopener noreferrer"&gt;https://github.com/BhushanVidhate-27/side-hustle&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Challenge&lt;/strong&gt;: DEV Challenge - Week 2 (Side Hustle)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tags&lt;/strong&gt;: &lt;code&gt;#devchallenge&lt;/code&gt; #hf26challenge&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Deadline&lt;/strong&gt;: Sunday 18 October 2026, 11:59 PM PDT&lt;/p&gt;




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

&lt;p&gt;StockRadar predicts which SKUs will run out at tiny kirana shops before customers notice. It reads a shop's sales ledger, forecasts stockouts, and outputs ready-to-send reorder messages in the language the shopkeeper actually uses with their distributor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Value&lt;/strong&gt;: Tells a shop owner what to reorder (and what NOT to reorder) before the shelf goes empty - with complete transparency about which engine/AI produced each number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-config by default&lt;/strong&gt;: Runs entirely offline with arithmetic baseline (no API keys required)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live TabPFN integration&lt;/strong&gt;: Uses Prior Labs TabPFN-3.5 tabular foundation model when configured&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-weight Gemma&lt;/strong&gt;: Local LLM (via Ollama) or Google AI Studio for natural language reorder advice in Hinglish/English&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Khata photo ingestion&lt;/strong&gt;: Upload a photo of a handwritten ledger - vision model extracts structured rows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart urgency logic&lt;/strong&gt;: Bins by days-of-cover relative to delivery cycle (not just probability) to avoid alert fatigue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shelf-life aware reordering&lt;/strong&gt;: Caps quantities to prevent spoilage (critical for eggs/milk)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable workflows&lt;/strong&gt;: Temporal-compatible with local fallback that checkpoints after every step and resumes on failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete degradation&lt;/strong&gt;: Every optional component has a working fallback; the output always names what actually ran&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest self-reporting&lt;/strong&gt;: &lt;code&gt;/api/health&lt;/code&gt; and CLI output explicitly state which models are configured vs actually running&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stack&lt;/strong&gt;: Python 3.11+, FastAPI, TabPFN, Gemma (Ollama/Google), Temporal (optional), Sentry (optional)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture highlights&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protocol-based design (&lt;code&gt;ForecastEngine&lt;/code&gt;, &lt;code&gt;Advisor&lt;/code&gt;) for swappable components&lt;/li&gt;
&lt;li&gt;Defensive parsing: TabPFN outputs are reconciled, hallucinated SKUs are dropped, all numbers copied from forecasts (never from LLM)&lt;/li&gt;
&lt;li&gt;Tolerant CSV ingestion with 9 date formats, BOM handling, missing column resilience&lt;/li&gt;
&lt;li&gt;Recency-weighted demand (65% last 28 days, 35% long-run) to catch Diwali/festival ramps&lt;/li&gt;
&lt;li&gt;Atomic checkpointing with per-activity retries and exponential backoff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test Coverage&lt;/strong&gt;: 158 passed, 1 skipped, 2 warnings (no network, fully deterministic)&lt;/p&gt;




&lt;h2&gt;
  
  
  Evidence of Real Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Working Demo (Zero Config)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python data/generate_sample_shop.py
python &lt;span class="nt"&gt;-m&lt;/span&gt; stockradar.cli run &lt;span class="nt"&gt;--limit&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output shows: engine name, days of cover, probabilities, rupee amounts at risk, and WhatsApp-ready messages like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Namaste bhaiya, 123 trays Farm Eggs (dozen) bhej dijiye, aaj hi chahiye. Stock khatam hone wala hai. Dhanyavaad. - Sharma General Store"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Live TabPFN Path
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;PRIORLABS_API_KEY&lt;/code&gt; is configured, the system runs with &lt;code&gt;engine: tabpfn-3.5 (Prior Labs)&lt;/code&gt;. The model learns from the shop's own history (fit on (SKU, day) features with forward-looking stockout labels) and provides calibrated probabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durability Demo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;STOCKRADAR_NOTIFY_FLAKY_ATTEMPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 python &lt;span class="nt"&gt;-m&lt;/span&gt; stockradar.cli workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows injected failures, exponential backoff retries (2s, 4s...), and successful completion of all three steps (forecast → notify → persist). Checkpointing ensures interrupted runs resume without redoing completed work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measured Performance (Backtest)
&lt;/h2&gt;

&lt;p&gt;Walk-forward evaluation on synthetic kirana data (3 seeds × 5 cutoffs × 24 SKUs = 360 predictions per scenario):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;ArithmeticBaseline&lt;/th&gt;
&lt;th&gt;TabPFN-3.5&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Realistic (demo)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Brier&lt;/td&gt;
&lt;td&gt;0.3257&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.2171&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TabPFN better (calibrated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;False-alarm rate&lt;/td&gt;
&lt;td&gt;0.3641&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.2521&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TabPFN better (fewer false alerts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stressed (supplier failure)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Brier&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.1603&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.2280&lt;/td&gt;
&lt;td&gt;Baseline better (generalizes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Precision @ top 25%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.8222&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.4556&lt;/td&gt;
&lt;td&gt;Baseline better (ranking)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway&lt;/strong&gt;: TabPFN wins the realistic scenario (the one matching normal shop behavior) and loses under abrupt regime shift. This is reported honestly with full caveats (only 3 stockout events in demo scenario - direction, not verdict). Methodology documented in &lt;a href="//docs/BACKTEST.md"&gt;&lt;code&gt;docs/BACKTEST.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open-Source AI Usage
&lt;/h2&gt;

&lt;p&gt;This project makes deliberate, honest use of open-weight models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prior Labs TabPFN-3.5&lt;/strong&gt; (Featured Partner) - Open tabular foundation model. Trained on millions of tabular datasets; we use it for stockout probability prediction from engineered features. Called via scikit-learn API (TabPFNClassifier) with defensive parsing and graceful fallback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gemma 3 (open weights)&lt;/strong&gt; - Used for:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Natural language advice generation (Ollama local: &lt;code&gt;gemma3:1b&lt;/code&gt;; Google AI Studio: &lt;code&gt;gemma-3-27b-it&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Khata photo OCR (Ollama: &lt;code&gt;gemma3:4b&lt;/code&gt; vision-capable) to extract ledger rows from handwritten notebooks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;System prompt explicitly forbids forecasting jargon and copies all numeric facts from forecasts (never trusts LLM with arithmetic)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fully offline path&lt;/strong&gt; - Works with zero external calls. The arithmetic baseline (&lt;code&gt;normal approximation&lt;/code&gt; over daily demand) is production-quality for this use case.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Degradation Philosophy
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Every optional component has a working replacement, and the replacement is always named in the output."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;No API key → arithmetic baseline + template advisor (explicitly stated)&lt;/li&gt;
&lt;li&gt;TabPFN errors/timeouts → batch fallback to arithmetic with warnings&lt;/li&gt;
&lt;li&gt;LLM unavailable → deterministic templates (no stack traces to shopkeeper)&lt;/li&gt;
&lt;li&gt;Process dies mid-run → atomic checkpoint resumes from first incomplete step&lt;/li&gt;
&lt;li&gt;Malformed model output → per-SKU fallback, never discards entire batch&lt;/li&gt;
&lt;li&gt;Khata OCR fails → raises with actionable fix (&lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;), never invents rows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Sponsor Integrations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Partner&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Prior Labs (TabPFN)&lt;/strong&gt; - $200&lt;/td&gt;
&lt;td&gt;Integrated, live&lt;/td&gt;
&lt;td&gt;Code in &lt;code&gt;stockradar/forecasting/tabpfn_engine.py&lt;/code&gt;, API key configured, backtest run with real model path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Google (Gemma)&lt;/strong&gt; - $200&lt;/td&gt;
&lt;td&gt;Code integrated, ready&lt;/td&gt;
&lt;td&gt;Providers in &lt;code&gt;explain/providers.py&lt;/code&gt; (Ollama + Google AI Studio), prompts in &lt;code&gt;prompts.md&lt;/code&gt;, vision path for khata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Render&lt;/strong&gt; - $200&lt;/td&gt;
&lt;td&gt;Config ready&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;render.yaml&lt;/code&gt; (free plan), health check at &lt;code&gt;/api/health&lt;/code&gt;, designed to not host inference (data stays local)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Temporal&lt;/strong&gt; - $100&lt;/td&gt;
&lt;td&gt;Built &amp;amp; tested&lt;/td&gt;
&lt;td&gt;Full workflow with retry policies, checkpoint fallback (&lt;code&gt;LocalDurableScheduler&lt;/code&gt;), flakiness demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; - $100&lt;/td&gt;
&lt;td&gt;Built &amp;amp; ready&lt;/td&gt;
&lt;td&gt;Tracing in &lt;code&gt;observability/tracing.py&lt;/code&gt; with local fallback to in-memory step table at &lt;code&gt;/api/steps&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;.tech domain&lt;/strong&gt; - $100&lt;/td&gt;
&lt;td&gt;Available via promo&lt;/td&gt;
&lt;td&gt;Can be claimed at hacktoberfest.com/my/promos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/BhushanVidhate-27/side-hustle.git
&lt;span class="nb"&gt;cd &lt;/span&gt;side-hustle
python &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

&lt;span class="c"&gt;# Zero-config demo (no network, no secrets)&lt;/span&gt;
python data/generate_sample_shop.py
python &lt;span class="nt"&gt;-m&lt;/span&gt; stockradar.cli run &lt;span class="nt"&gt;--limit&lt;/span&gt; 8
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests/  &lt;span class="c"&gt;# 158 passed&lt;/span&gt;

&lt;span class="c"&gt;# Dashboard&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; stockradar.cli serve  &lt;span class="c"&gt;# http://localhost:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full documentation: &lt;a href="//README.md"&gt;README.md&lt;/a&gt; | &lt;a href="//docs/DEV_POST.md"&gt;DEV Post Draft&lt;/a&gt; | &lt;a href="//docs/BACKTEST.md"&gt;Backtest Methodology&lt;/a&gt; | &lt;a href="//docs/WORKFLOW.md"&gt;Workflow Design&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>Graph Hacks - Thestrals</title>
      <dc:creator>Bhushan Vidhate</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:24:42 +0000</pubDate>
      <link>https://dev.to/bhushan_vidhate/graph-hacks-thestrals-3pb</link>
      <guid>https://dev.to/bhushan_vidhate/graph-hacks-thestrals-3pb</guid>
      <description>&lt;h1&gt;
  
  
  We taught a database to say "I don't know" — and then had to prove it wasn't lying
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Helio — a company knowledge graph agent · Graph Hacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask &lt;em&gt;"who knows about the payments service, what breaks if it goes down, and where was that decided?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most enterprise search answers &lt;em&gt;"where is the doc about X."&lt;/em&gt; Helio answers &lt;strong&gt;who knows about X, what breaks if it goes down, where was that decided, and how current is that answer&lt;/strong&gt; — in one traversal, with the path shown.&lt;/p&gt;

&lt;p&gt;This post is about the part nobody puts in the demo: how you know the thing is actually correct when the thing is an LLM.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem is not retrieval. It's that retrieval can't be trusted.
&lt;/h2&gt;

&lt;p&gt;A company's knowledge is a graph. Services depend on services. Teams own services. Decisions supersede other decisions. If you store that as documents, you can find the document. You cannot answer the questions that actually block someone at 2pm.&lt;/p&gt;

&lt;p&gt;So we built the graph. FalkorDB, 7 node types, 14 edge types, a synthetic 120-person fintech mid-migration between payment providers. Then we pointed an LLM at it with a tool-use loop.&lt;/p&gt;

&lt;p&gt;That part worked. Then we asked the obvious follow-up question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do you know it didn't just make something up?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And we hit the wall that shaped this entire project.&lt;/p&gt;

&lt;h3&gt;
  
  
  The circularity trap
&lt;/h3&gt;

&lt;p&gt;The normal move is to ask the model "is this answer correct?" — to use the LLM as its own judge. This is worth stating plainly because it's so tempting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is circular.&lt;/strong&gt; A hallucinated ownership chain and a correct ownership chain are &lt;em&gt;structurally identical&lt;/em&gt;. Both are fluent prose over well-formed edges. Both mention services, teams, dates, and documents. A judge model asked "does this look right?" is being shown the same evidence in both cases. It cannot tell them apart, because &lt;strong&gt;there is no evidence that distinguishes them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The failure is invisible by construction. There is no signal that would have warned us.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: write the answer key before you see the answer
&lt;/h2&gt;

&lt;p&gt;We did something that felt backwards at the time. In &lt;code&gt;GROUND_TRUTH.md&lt;/code&gt;, we hand-wrote &lt;strong&gt;20 facts about the company before generating a single row of data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This inverted the usual order. Normally you build the system, then eyeball whether it works. We'd already committed to what "correct" means, so the generator now has to satisfy us rather than the other way around.&lt;/p&gt;

&lt;p&gt;The uncomfortable part: &lt;strong&gt;5 of those 20 questions have no answer in the graph.&lt;/strong&gt; We designed them that way, on purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Who owns config-service?"
  →  "The graph contains no OWNED_BY edge. No team owns it."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That response is the product. Not an apology, not a hedge, not a guess — because an absent edge is a &lt;em&gt;real, correct answer&lt;/em&gt;, not a gap in our data.&lt;/p&gt;

&lt;p&gt;An agent that invents an owner there has failed, &lt;strong&gt;even if the sentence reads perfectly.&lt;/strong&gt; We made that an explicit scoring category rather than a footnote.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why that framing matters more than it sounds
&lt;/h3&gt;

&lt;p&gt;Building a system that answers correctly is mostly an engineering problem. Building one that &lt;em&gt;fails honestly&lt;/em&gt; is a design problem, and it changes what you optimise for. Once "refuse" is a scored behaviour with the same weight as "answer," the prompt stops being about being helpful and starts being about being &lt;em&gt;legible&lt;/em&gt;. You stop asking "how do I make the model more useful?" and start asking "what would make this answer checkable?"&lt;/p&gt;




&lt;h2&gt;
  
  
  What the graph caught that text search structurally cannot
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The volume/recency trap
&lt;/h3&gt;

&lt;p&gt;Priya Sharma has the most commits on &lt;code&gt;payments-gateway&lt;/code&gt; — 14. Her last touch was 2026-04-15, the day she left the company.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;ORDER BY count DESC&lt;/code&gt; returns Priya. That query is &lt;strong&gt;technically correct on volume and the wrong answer to the question.&lt;/strong&gt; Any system that collapses "who knows this" into a single number produces this confidently, and nothing in the output looks suspicious.&lt;/p&gt;

&lt;p&gt;We return both signals and refuse to blend them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Platform team owns payments. &lt;strong&gt;Ravi Menon&lt;/strong&gt; has worked on it most recently (2026-09-28). &lt;strong&gt;Priya Sharma&lt;/strong&gt; has the most commits (14), but her last touch was 2026-04-15 and is flagged as stale. Ask &lt;strong&gt;Ravi Menon&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fix wasn't a smarter model. It was &lt;strong&gt;not having one number where two facts belong.&lt;/strong&gt; Volume and recency are separate properties of separate edges, and collapsing them is a modelling error, not an intelligence problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The contradiction with a citation
&lt;/h3&gt;

&lt;p&gt;A March document and a March Slack thread both say &lt;em&gt;"single provider, Stripe."&lt;/em&gt; Decision &lt;strong&gt;D-19&lt;/strong&gt; (2026-05-06) says &lt;em&gt;"dual provider, Adyen primary, Stripe fallback."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both sources are stale, and the graph can &lt;strong&gt;prove&lt;/strong&gt; it — because &lt;code&gt;SUPERSEDES&lt;/code&gt; is an edge, not an inference. This is the thing embeddings cannot do: they can tell you these documents are &lt;em&gt;similar&lt;/em&gt;. They cannot tell you one of them has been &lt;em&gt;formally retired by a decision you can name&lt;/em&gt;. Citations make staleness actionable; without the edge, "this doc is out of date" leaves the reader unable to tell which live decision it undercuts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bug that taught us the most: our own gate was lying
&lt;/h2&gt;

&lt;p&gt;Partway through, our verification gate reported that all four must-fail questions were &lt;strong&gt;wrong&lt;/strong&gt;. They were, in fact, correct and complete refusals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q16  "The graph contains no OWNED_BY edge for config-service…"
     → matched 1 of 8
Q17  "The graph contains no decisions whose summary mentions pricing."
     → matched 1 of 10
Q20  "The graph contains no SLA entry for dispute-service."
     → matched 1 of 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one was a semantically perfect refusal. Every one failed.&lt;/p&gt;

&lt;p&gt;The gate required all eight phrasings of "no" to appear in a single sentence. Read as an AND list, it was demanding that one sentence say "no no no no no no no no" — which no correct answer would ever do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We had shipped a gate that was backwards in the exact direction that wastes your time.&lt;/strong&gt; It was optimised to look thorough while penalising exactly the behaviour we said we wanted.&lt;/p&gt;

&lt;p&gt;The instructive part is what we tried first: we widened the vocabulary. And the pass rate went &lt;strong&gt;down&lt;/strong&gt;. Widening an AND list makes it strictly harder — every added term is another conjunct. An earlier attempt at this gate did precisely that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The fix is the matching rule, not the word list.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Refusal has many spellings, and no single answer contains all of them. So must-fail cases carry &lt;code&gt;ANY_OF&lt;/code&gt; while affirmative cases keep AND — because "D-19" &lt;strong&gt;and&lt;/strong&gt; "supersed" both have to be there to prove you understood &lt;em&gt;which&lt;/em&gt; decision is current, but "no owner" only needs one honest phrasing.&lt;/p&gt;

&lt;p&gt;That's the whole lesson, and it generalises past this project: &lt;strong&gt;when a metric moves the wrong way, the metric is usually the bug.&lt;/strong&gt; We were about to "fix" the model. The model was fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other things that broke, and what they taught
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The server sends a full snapshot per SSE frame — not a delta.&lt;/strong&gt; Handlers must &lt;em&gt;replace&lt;/em&gt;, not append. Append looks correct for exactly one frame, which is why it survives casual testing and then duplicates the whole answer trail in production. Protocol assumptions are code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Six graph facts the agent structurally could not reach.&lt;/strong&gt; Not "answered wrong" — &lt;em&gt;could not reach&lt;/em&gt;. The Cypher was correct, the traversal was correct, and the agent still couldn't get to the answer because the shape of the returned rows didn't offer a path the model would take. A correct query behind an unreachable surface is still unreachable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool results blew past a 413.&lt;/strong&gt; A 6.5k-token tool result was large enough to be rejected outright. The fix was to return &lt;em&gt;trails&lt;/em&gt; instead of raw rows — which turned out to be better product design anyway, because the trail is what a human wants to see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent results aren't stable run-to-run.&lt;/strong&gt; The model is sampled, so a single sweep tells you very little. Any conclusion drawn from one run — including "the fix worked" — is nearly worthless. This is why the ground-truth set is fixed and hand-written: it doesn't move when the model does.&lt;/p&gt;




&lt;h2&gt;
  
  
  On the frontend, and deleting things
&lt;/h2&gt;

&lt;p&gt;We started the site from a reverse-engineered capture of another product, which meant inheriting someone else's entire codebase — including 12 third-party scripts it loaded, including one that was &lt;strong&gt;pulled from their domain&lt;/strong&gt;, and including a resource-saver script that deleted DOM nodes mid-parse and caused a hydration mismatch.&lt;/p&gt;

&lt;p&gt;One of them was almost certainly why scroll only advanced two of four steps: their scroll controller driving our DOM. Remove all twelve: &lt;strong&gt;0 console errors.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then the hard lesson. The captured HTML has &lt;strong&gt;unclosed &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s&lt;/strong&gt;, which means the obvious approach — count &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; until depth hits zero — silently runs past an element's real end. That deleted the footer and four entire sections in one cut. The cut &lt;em&gt;looked balanced&lt;/em&gt;. Nothing complained. That's the worst kind of bug: the failure is invisible until you notice a section is gone.&lt;/p&gt;

&lt;p&gt;The fix was to stop trusting the structure and start checking it: derive spans from a real HTML parser, and refuse to write unless the sentinel counts (&lt;code&gt;&amp;lt;/main&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;, every &lt;code&gt;home-*-wrap&lt;/code&gt;) survive. &lt;strong&gt;A delete that can't fail silently is worth more than a delete that's usually right.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a matching lesson in CSS. We tried "fixing" the cascade by layering the captured stylesheet so our utilities would win. It worked perfectly on buttons and &lt;strong&gt;halved every section on the page&lt;/strong&gt;, because the markup used generic class names both stylesheets owned. Load order was the real mechanism all along.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd tell someone starting this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Write the answer key first.&lt;/strong&gt; Everything downstream gets cheaper when correctness is defined before implementation exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat "I don't know" as a feature with a score.&lt;/strong&gt; If refusal isn't measured, your model will be rewarded for guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never let one number stand in for two facts.&lt;/strong&gt; The volume/recency trap wasn't an AI failure. It was a data-modelling choice we made years before the LLM existed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't fix the model when the metric is broken.&lt;/strong&gt; Our gate failed four correct answers for a day. Widening the vocabulary made it worse. The model was right the whole time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make destructive operations fail loudly.&lt;/strong&gt; The unclosed-div bug and the layered-CSS bug had the same root cause: something looked correct while being wrong. Both were found by asking "what would I have to check to &lt;em&gt;know&lt;/em&gt; this is safe?" — not by looking harder.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://helio-brain.onrender.com" rel="noopener noreferrer"&gt;https://helio-brain.onrender.com&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;code&gt;app/&lt;/code&gt; (FastAPI + FalkorDB + LLM tool-use loop) · &lt;code&gt;scripts/&lt;/code&gt; (generate → load → verify) · &lt;code&gt;front/&lt;/code&gt; (static Next.js site)&lt;/p&gt;

&lt;p&gt;All data is synthetic.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>database</category>
      <category>llm</category>
    </item>
    <item>
      <title>Horizon: a local AI sky planner that tells you when to step outside</title>
      <dc:creator>Bhushan Vidhate</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:46:05 +0000</pubDate>
      <link>https://dev.to/bhushan_vidhate/horizon-a-local-ai-sky-planner-that-tells-you-when-to-step-outside-1k70</link>
      <guid>https://dev.to/bhushan_vidhate/horizon-a-local-ai-sky-planner-that-tells-you-when-to-step-outside-1k70</guid>
      <description>&lt;blockquote&gt;
&lt;p&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;/p&gt;

&lt;p&gt;&lt;code&gt;#hf26challenge&lt;/code&gt; &lt;code&gt;#devchallenge&lt;/code&gt; &lt;code&gt;#opensource&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&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%2F8crkj7ypzztoixbchyng.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%2F8crkj7ypzztoixbchyng.png" alt="Screenshot of Horizon’s daily outside recommendation" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/bhushanvidhate-27/horizon" rel="noopener noreferrer"&gt;github.com/bhushanvidhate-27/horizon&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://horizon-bhushanvidhate-27.onrender.com" rel="noopener noreferrer"&gt;horizon-bhushanvidhate-27.onrender.com&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Horizon&lt;/strong&gt; is a daily outdoor planner that answers one simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When is the best time for me to go outside today, and what can I see in the sky?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Enter a location and Horizon combines weather forecasts with astronomy data to recommend the best one-to-three-hour window for being outdoors. It considers temperature, rain probability, wind, cloud cover, UV, sunrise and sunset, Moon phase, visible planets, constellations, and upcoming sky events.&lt;/p&gt;

&lt;p&gt;The result is intentionally short: a useful recommendation, a few things to look for, and a reason to put the phone away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For example (real run for Hyderabad):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your best outside window is 04:00–07:00

Weather: Clear sky, 26°C (feels 27°C), 0% chance of rain, wind 5 km/h, 0% cloud
Sun: up 06:08 / down 17:58
Moon: New Moon (0% lit) — dark skies, ideal for stargazing
Look up for: Aquila, Lyra, Cygnus, Sagittarius, Pegasus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Horizon chose that pre-dawn window because clear weather plus a New Moon creates the darkest, starriest conditions of the day. The scoring deliberately balances human comfort with what the sky actually has worth looking at.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who It’s For
&lt;/h2&gt;

&lt;p&gt;Horizon is for people who want a reason to get outside without another screen-heavy app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Busy people&lt;/strong&gt; who need one clear sentence (“go out at 6 p.m.”) instead of a dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Casual stargazers&lt;/strong&gt; who don’t know what’s overhead tonight but would love a reason to look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walkers, runners, and remote workers&lt;/strong&gt; who benefit from a nudge at the nicest, clearest part of the day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The screen is only the starting point. The real goal is to close the tab and look up.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try it live:&lt;/strong&gt; &lt;a href="https://horizon-bhushanvidhate-27.onrender.com" rel="noopener noreferrer"&gt;horizon-bhushanvidhate-27.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample alert:&lt;/strong&gt; &lt;a href="https://github.com/bhushanvidhate-27/horizon/blob/main/examples/sample_alert.md" rel="noopener noreferrer"&gt;examples/sample_alert.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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/BhushanVidhate-27" rel="noopener noreferrer"&gt;
        BhushanVidhate-27
      &lt;/a&gt; / &lt;a href="https://github.com/BhushanVidhate-27/horizon" rel="noopener noreferrer"&gt;
        horizon
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      hactoberfest dev challenge week 1 submission 
    &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;🌇 Horizon&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/bhushanvidhate-27/horizon/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/bhushanvidhate-27/horizon/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/BhushanVidhate-27/horizon/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://github.com/BhushanVidhate-27/horizon/pyproject.toml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/13466c63b4f964f1bc47e46eabb6ea9ebff2cb7777cba82af790a55719bbbfda/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31302532422d626c75652e737667" alt="Python 3.10+"&gt;&lt;/a&gt;
&lt;a href="https://github.com/BhushanVidhate-27/horizon#-tests" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9f333efe5e9b41c0807d6b5149b1b213ba71f9631a6312ec194dd2d4276e2d9d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d383425323070617373696e672d627269676874677265656e2e737667" alt="Tests"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A daily outside alert &amp;amp; sky event planner.&lt;/strong&gt; Horizon tells you the best time
to step outside today, what interesting things are happening in the sky, and
then nudges you to put the phone away and actually go look.&lt;/p&gt;
&lt;p&gt;It's built with the &lt;strong&gt;"Touch Grass"&lt;/strong&gt; spirit: a short interaction that leads to
a real-world experience. Ask once, then leave your phone in your pocket.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Submission for &lt;strong&gt;Hacktoberfest 2026 — Open-Source AI Challenge, Week 1: Touch Grass&lt;/strong&gt;
Brand-new project, built within the challenge window (Oct 5–11, 2026). Any commits
after the Oct 11, 2026 11:59 PM PDT deadline will be noted here as required.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/BhushanVidhate-27/horizon/docs/screenshot.svg"&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%2FBhushanVidhate-27%2Fhorizon%2FHEAD%2Fdocs%2Fscreenshot.svg" alt="Horizon web UI"&gt;&lt;/a&gt;&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best weather window&lt;/strong&gt; — scores every 1–3 hour block of the day using
&lt;a href="https://open-meteo.com/" rel="nofollow noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; data (temperature, rain chance, wind
cloud cover, UV) and picks the nicest window to be outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sky events&lt;/strong&gt; — uses &lt;a href="https://rhodesmill.org/skyfield/" rel="nofollow noopener noreferrer"&gt;Skyfield&lt;/a&gt; to compute
for your location:…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/BhushanVidhate-27/horizon" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Horizon is an &lt;strong&gt;MIT-licensed&lt;/strong&gt;, self-contained Python project. Its &lt;strong&gt;core works with zero paid APIs&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-Meteo&lt;/strong&gt; → weather + geocoding
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skyfield&lt;/strong&gt; → sunrise, Moon phase, planets, constellations, meteor showers, calendar events
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma&lt;/strong&gt; (open weights) → local narration via Ollama (or hosted via OpenRouter / AI Studio / HF)
&lt;/li&gt;
&lt;li&gt;Deterministic template narrator + local SQLite when no model or database is configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional partner integrations are auto-detected and never required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;horizon/
├── agent.py      # pipeline: location → sky → weather → news → narration → voice → storage
├── sky.py        # Skyfield astronomy
├── weather.py    # Open-Meteo window scoring
├── llm.py        # Gemma via Ollama / OpenRouter / AI Studio
├── narrator.py   # deterministic fallback
├── news.py       # SerpApi headlines
├── voice.py      # ElevenLabs TTS
├── storage.py    # MongoDB Atlas + SQLite fallback
├── main.py       # FastAPI + REST API
└── cli.py        # same pipeline from the terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run it yourself:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/bhushanvidhate-27/horizon &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;horizon
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
uv run uvicorn horizon.main:app   &lt;span class="c"&gt;# → http://localhost:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;A small agent pipeline, one stage per concern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location → sky → weather → news → narration → (voice) → storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What it does&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;Location&lt;/td&gt;
&lt;td&gt;City → lat/long (cached)&lt;/td&gt;
&lt;td&gt;Open-Meteo geocoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sky&lt;/td&gt;
&lt;td&gt;Sunrise/set, Moon, planets, constellations, meteors&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Skyfield&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calendar&lt;/td&gt;
&lt;td&gt;New/full moons, meteor peaks, equinoxes, oppositions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Skyfield&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weather&lt;/td&gt;
&lt;td&gt;Scores every 1–3 h block for comfort + clear skies&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Open-Meteo&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;News&lt;/td&gt;
&lt;td&gt;Rare sky-event headlines&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;SerpApi&lt;/strong&gt; (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narration&lt;/td&gt;
&lt;td&gt;Turns facts into a warm human alert&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Gemma&lt;/strong&gt; (Ollama default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice&lt;/td&gt;
&lt;td&gt;Spoken version of the alert&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ElevenLabs&lt;/strong&gt; (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Preferences + alert history&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;MongoDB Atlas&lt;/strong&gt; / SQLite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracing&lt;/td&gt;
&lt;td&gt;One transaction per alert, span per stage&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Graceful degradation is intentional: missing keys never break the experience. The useful recommendation still appears using only open data and open weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Open data makes a real product possible for free.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Weather and astronomy are public knowledge. Open-Meteo and Skyfield let a weekend builder compute a genuinely useful recommendation without a billing account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open weights keep the intelligence inspectable.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Narration is the part most likely to hide bias or leak data, so it runs locally through Ollama by default. No prompt leaves the device unless you choose a hosted path. The model can be swapped with a one-line change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open source makes small tools durable.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Horizon is MIT-licensed and modular. If a provider changes, only one stage needs updating. The project stays understandable and improvable by anyone.&lt;/p&gt;

&lt;p&gt;The most important output isn’t a model response — it’s &lt;em&gt;you, outside&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking It Outside
&lt;/h2&gt;

&lt;p&gt;[Add your honest outdoor note here. Example:&lt;br&gt;&lt;br&gt;
“I ran Horizon for Hyderabad on a clear New Moon morning. It recommended 04:00–07:00. I stepped outside just before 5 a.m. and could clearly see Lyra and Cygnus rising — exactly the dark-sky window it had chosen. That short interaction was the whole point.”]&lt;/p&gt;

&lt;p&gt;This is the part I care about most: the screen interaction should be brief and the real-world experience longer.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Horizon uses Gemma to turn structured weather + sky data into warm, natural daily alerts. It runs locally through Ollama (open weights, no key) and also supports hosted paths. A deterministic template narrator keeps the app working when no model is reachable. (&lt;code&gt;horizon/llm.py&lt;/code&gt;, &lt;code&gt;horizon/narrator.py&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Render&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The live Horizon demo is deployed on Render:&lt;br&gt;&lt;br&gt;
&lt;a href="https://horizon-bhushanvidhate-27.onrender.com" rel="noopener noreferrer"&gt;https://horizon-bhushanvidhate-27.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Horizon can turn each daily alert into a short spoken narration so you can listen while getting ready to go outside. Audio is cached by content hash. (&lt;code&gt;horizon/voice.py&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of MongoDB Atlas&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When configured with &lt;code&gt;MONGODB_URI&lt;/code&gt;, Horizon stores location preferences and alert history in MongoDB Atlas, with SQLite available as a local fallback. (&lt;code&gt;horizon/storage.py&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of SerpApi&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Horizon can include recent astronomy and rare-sky-event headlines through SerpApi, adding fresh context to a daily recommendation. (&lt;code&gt;horizon/news.py&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Sentry&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Horizon traces its pipeline stages in Sentry, including location lookup, sky calculations, weather scoring, narration, voice generation, and storage. (&lt;code&gt;horizon/agent.py&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of DigitalOcean&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The repo ships a Dockerfile and a DigitalOcean App Platform spec (&lt;code&gt;.do/app.yaml&lt;/code&gt;) with a &lt;code&gt;/api/health&lt;/code&gt; health check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-source + open-weight core&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No paid API is required for the core experience (Open-Meteo + Skyfield + Gemma).&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering Quality
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;84 offline unit tests at ~95% coverage&lt;/strong&gt; (&lt;code&gt;pytest --cov&lt;/code&gt;) — every network partner is monkey-patched so the suite runs in about a second offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI on GitHub Actions&lt;/strong&gt; (Python 3.10–3.12): &lt;code&gt;ruff&lt;/code&gt; + full test suite + packaging smoke test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful degradation everywhere&lt;/strong&gt;: missing keys never break the pipeline.&lt;/li&gt;
&lt;li&gt;Full architecture, API reference and setup notes live in the &lt;a href="https://github.com/bhushanvidhate-27/horizon/blob/main/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Push notifications at the start of the best window&lt;/li&gt;
&lt;li&gt;Telegram / WhatsApp bot (“send me my Horizon”)&lt;/li&gt;
&lt;li&gt;Red-light night mode for dark-adapted eyes&lt;/li&gt;
&lt;li&gt;Aurora (NOAA Kp) and ISS-pass alerts&lt;/li&gt;
&lt;li&gt;Atlas Vector Search over past alerts&lt;/li&gt;
&lt;li&gt;Multi-day planner + iCal export of the sky calendar&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built with open data, open weights, and the sincere hope that you close this tab and go outside. 🌿&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
