<?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: Amit</title>
    <description>The latest articles on DEV Community by Amit (@iamamitkumarsahoo).</description>
    <link>https://dev.to/iamamitkumarsahoo</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%2F950027%2F193225a7-7f6f-492d-863a-7bc8d32183a0.jpg</url>
      <title>DEV Community: Amit</title>
      <link>https://dev.to/iamamitkumarsahoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamamitkumarsahoo"/>
    <language>en</language>
    <item>
      <title>SlothBox: a local file butler that files your downloads, screenshots and receipts while you do nothing</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Mon, 26 Oct 2026 04:55:38 +0000</pubDate>
      <link>https://dev.to/iamamitkumarsahoo/slothbox-a-local-file-butler-that-files-your-downloads-screenshots-and-receipts-while-you-do-3m4j</link>
      <guid>https://dev.to/iamamitkumarsahoo/slothbox-a-local-file-butler-that-files-your-downloads-screenshots-and-receipts-while-you-do-3m4j</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&lt;/a&gt;: Week 3, Slow and Steady.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;SlothBox watches the folders you choose on a Windows PC, such as Downloads and Screenshots. When a new file lands, a local Gemma model reads it and says what it is: a receipt, a screenshot, a document, or something else. SlothBox then gives the file a clear name, moves it into a sorted folder, and, if it is a receipt, writes a row in a monthly ledger. A page you open on your phone over home Wi-Fi sends photos into the same pipeline. Every move can be undone from the dashboard or the tray.&lt;/p&gt;

&lt;p&gt;Nothing leaves the PC. Gemma runs in Docker on your own GPU, the database is a local Postgres, and there is no account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who gets lazier
&lt;/h3&gt;

&lt;p&gt;Anyone with a Downloads folder they have stopped looking at, a screenshot pile named &lt;code&gt;Screenshot 2026-10-14 213055.png&lt;/code&gt;, or a wallet of paper receipts they mean to type into a spreadsheet one day.&lt;/p&gt;

&lt;p&gt;Gemma never writes a file name. It returns fields, and plain code builds the name from a template:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Receipt&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{date}_{Vendor}_{amount}{CUR}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-10-20_Jio_599INR.jpg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{file date}_{title}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-10-20_reddit-thread-on-dotnet-10.png&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{title}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dotnet-10-release-notes.pdf&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other&lt;/td&gt;
&lt;td&gt;original name, unchanged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ChromeSetup.exe&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An installer keeps its name because renaming it would help nobody.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rules that make it safe to leave running
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Files that were already in a folder when you added it are never touched. "Tidy them now" is a separate click, and "Undo since…" puts everything back.&lt;/li&gt;
&lt;li&gt;A file goes to Needs review instead of being guessed when Gemma's answer is not valid, when the type is unknown, when a receipt has no date or amount, or when the file could not be read (HEIC, scanned PDF, password-protected). Review is keyboard-only if you want it: a digit picks the category, Enter files it.&lt;/li&gt;
&lt;li&gt;Every move is logged before it starts and finished through a temp file with a hash check, so a crash or a power cut leaves either the original or the copy, never neither.&lt;/li&gt;
&lt;li&gt;Cloud-synced folders are copy-only. SlothBox never deletes from a folder that something else also owns.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;SlothBox runs on your own PC, so there is no public deployment. Three screens from the build:&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%2Fb2e5j3zm71neayr5ogtl.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%2Fb2e5j3zm71neayr5ogtl.png" alt="Today: the meter and the activity feed" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Today. The meter counts what was filed this week and turns it into minutes you did not spend. The two rows are test files: a synthetic text receipt filed under Receipts / Shopping, and a HEIC file that went to review and was filed by hand.&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/PASTE-REVIEW-URL" 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/PASTE-REVIEW-URL" alt="Needs review: a HEIC file SlothBox could not read" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Needs review. SlothBox cannot read HEIC yet, so it asks instead of guessing. 1 to 9 picks a category, Enter files it, S skips, O opens the file.&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/PASTE-RECEIPTS-URL" 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/PASTE-RECEIPTS-URL" alt="Receipts: the October ledger" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Receipts. The ledger for October, with the total per currency and per category, and a CSV export. The one row is the synthetic receipt from the test above; Gemma read the date, amount and payment method and missed the vendor.&lt;/em&gt;&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/AmitSahoo45" rel="noopener noreferrer"&gt;
        AmitSahoo45
      &lt;/a&gt; / &lt;a href="https://github.com/AmitSahoo45/slothbox" rel="noopener noreferrer"&gt;
        slothbox
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SlothBox&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A local file butler for Windows. SlothBox watches the folders you choose, such as Downloads and Screenshots. A local
AI model (Gemma, running in Docker on your own PC) reads each new file. SlothBox then gives the file a clear name and
files it in a sorted folder. Receipts go into a monthly ledger, every move can be undone, and you can search your files
by words or by meaning. A small upload page lets your phone send photos over your home Wi-Fi. Files never leave the PC.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/AmitSahoo45/slothbox/docs/specs/2026-10-20-slothbox-design.md" rel="noopener noreferrer"&gt;Design spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AmitSahoo45/slothbox/docs/plans/2026-10-20-slothbox-implementation-plan.md" rel="noopener noreferrer"&gt;Implementation plan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AmitSahoo45/slothbox/AGENTS.md" rel="noopener noreferrer"&gt;Repository guidance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What you need&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10 or 11, 64-bit.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/products/docker-desktop/" rel="nofollow noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; with Linux containers.&lt;/li&gt;
&lt;li&gt;An NVIDIA GPU with at least 8 GB of video memory is recommended. Without one, Gemma runs on the CPU and is much
slower.&lt;/li&gt;
&lt;li&gt;About 17 GB of free disk space for Docker: about 10 GB of images and 7 GB of…&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/AmitSahoo45/slothbox" 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;h3&gt;
  
  
  The open AI part
&lt;/h3&gt;

&lt;p&gt;Two Gemma models through &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, both running in a Docker container on the PC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4 E4B&lt;/strong&gt; (&lt;code&gt;gemma4:e4b&lt;/code&gt;, 6.6 GB) reads each file. Images are sent as base64, PDFs and Office files as extracted text, and the model answers in a fixed JSON schema: type, category, title, and for receipts the vendor, date, amount, currency and payment method. Temperature 0, thinking off, one retry on a timeout or on malformed output. It fits entirely in 8 GB of VRAM, so the GPU runs one file at a time with nothing offloaded to RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EmbeddingGemma&lt;/strong&gt; (&lt;code&gt;embeddinggemma&lt;/code&gt;, 768 dimensions) turns the text Gemma wrote about each file into a vector, on the CPU, so Gemma keeps the GPU to itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model proposes; code decides. The category must be one of the user's own list, or it becomes Other. A currency that is not a three-letter code becomes null. An amount of ten billion or more becomes null, because the ledger column is &lt;code&gt;numeric(12,2)&lt;/code&gt; and a misread would otherwise fail the insert. Control characters are stripped before anything reaches Postgres.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage and search: Postgres + pgvector
&lt;/h3&gt;

&lt;p&gt;Everything that is not a file lives in one Postgres database: the queue, the action log, the receipts ledger, the categories and the search index. Search is one SQL query that merges the top 50 keyword matches (&lt;code&gt;tsvector&lt;/code&gt;, &lt;code&gt;websearch_to_tsquery&lt;/code&gt;) with the top 50 meaning matches (cosine distance on the EmbeddingGemma vector) using reciprocal rank fusion with k = 60. Each hit reports whether it matched by words, by meaning, or both, and the UI shows that label.&lt;/p&gt;

&lt;p&gt;Two things in that query were not in any tutorial I read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pgvector's default &lt;code&gt;hnsw.ef_search&lt;/code&gt; is 40, which is below &lt;code&gt;LIMIT 50&lt;/code&gt;, so without &lt;code&gt;SET LOCAL hnsw.ef_search = 100&lt;/code&gt; the meaning branch silently returned at most 40 rows.&lt;/li&gt;
&lt;li&gt;With a filter such as "receipts only", the HNSW index picks its nearest rows first and applies the filter after, so a narrow filter could return no meaning matches at all. &lt;code&gt;SET LOCAL hnsw.iterative_scan = strict_order&lt;/code&gt; fixes that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test for the second point is the most measured line in the repo. The index graph is built with Postgres's internal random generator, which no SQL setting can seed, so the same 20 seeded receipts come back as 20 matches about four runs in five and as few as three in the worst of 600 runs. The test now asserts what the spec can justify, at least one, and the spec records the numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The stack
&lt;/h3&gt;

&lt;p&gt;A .NET 10 tray app hosts an ASP.NET Core server on &lt;code&gt;127.0.0.1:5080&lt;/code&gt;, with the React dashboard built into the exe. A second listener on port 5081 serves only the phone page and its upload. Postgres and Ollama run from one compose file. The release is a single self-contained &lt;code&gt;SlothBox.exe&lt;/code&gt;, so the PC that runs it needs no .NET install.&lt;/p&gt;

&lt;p&gt;The engine has 768 tests and the UI has 139, all on synthetic files and a throwaway Postgres container, so no real file of mine is in the repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug I am glad a review found
&lt;/h3&gt;

&lt;p&gt;Undo looked simple: the action log says the file went from A to B, so move it back. A review of the first safety batch found that a stale undo, one requested after the file had already been refiled somewhere else, could build a "delete the copy" step whose copy and original were the same path, which would have deleted the only copy of the file. Three guards went in: undo refuses unless the action is still the file's newest live one, the action log re-checks that under a row lock, and the mover refuses any delete whose copy and original are the same path. Each guard has a test that failed before it existed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the work was split
&lt;/h3&gt;

&lt;p&gt;I used AI for most of the code. Claude (Opus 5.5, through Claude Code, on the laptop and in cloud sessions) wrote the engine and most of the UI from a design spec and a task plan, test first, one batch at a time, each batch ending with a report and a review. OpenAI's Codex (GPT-6 Astra) built the first four UI batches. My part was choosing the problem, writing the rules into the spec, deciding every open question the agents raised, merging the branches, running the checks, and running the app on my own machine against a real phone and real folders.&lt;/p&gt;

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

&lt;p&gt;Downloads folders are full of things nobody would upload on purpose: bank statements, ID scans, chat screenshots, receipts with a home address on them. A closed vision API would mean sending every one of those files to someone else's server, paying per file, and needing internet before a screenshot could be filed.&lt;/p&gt;

&lt;p&gt;With open-weight models, the files never leave the PC. Once the two models are downloaded, a file costs nothing to process, and the whole thing works with Wi-Fi off, apart from the phone page, which needs the local network and nothing else.&lt;/p&gt;

&lt;p&gt;Open also means the model is a setting. The spec has a day-one rule: if &lt;code&gt;gemma4:e4b&lt;/code&gt; does not fit the GPU or takes more than ten seconds per file, switch to the quantised build or to &lt;code&gt;gemma4:e2b&lt;/code&gt;. That is one line in &lt;code&gt;settings.json&lt;/code&gt; and a pull in Ollama, not a vendor negotiation. The same goes for the embedding model, with a re-embed of the index.&lt;/p&gt;

&lt;p&gt;And it means the limits can be measured and published. The receipt fields Gemma gets wrong, the search matches the index misses, the seconds each file takes: all of that is visible, repeatable, and free to re-run.&lt;/p&gt;

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

&lt;p&gt;The dashboard has a meter. The formula is shown in the UI under "How we count" and it is an estimate, stated here plainly: a receipt counts as 30 seconds you did not spend typing it into a spreadsheet, a screenshot or a document as 15 seconds you did not spend renaming and moving it, anything else as 5 seconds. The per-type seconds can be changed in Settings.&lt;/p&gt;

&lt;p&gt;I did not get SlothBox onto my real Downloads folder before the deadline. The week went into the build, and the plan's "run it on real folders from day one" step is the one that slipped. So the numbers below come from a test folder and the phone page, and they are small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files filed: 3. One photo uploaded from the phone, one synthetic text receipt, one HEIC filed from review.&lt;/li&gt;
&lt;li&gt;Receipts logged: 1, ₹120.00. Gemma read the date, the amount and "paid in cash" correctly and missed the vendor, so the file is &lt;code&gt;2026-10-26_120INR.txt&lt;/code&gt; instead of &lt;code&gt;2026-10-26_SampleStore_120INR.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Duplicates caught: 0.&lt;/li&gt;
&lt;li&gt;Minutes saved by the formula: under one.&lt;/li&gt;
&lt;li&gt;Seconds per file: 28 s for the first file after a start, while Gemma loads into the GPU; about 2 s per call once it is warm. The spec's target of under ten seconds per file holds only once the model is loaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is one receipt, not an accuracy figure. The honest version is: the pipeline works end to end, and the measurement of how often Gemma gets a receipt right is the first thing to do after the deadline.&lt;/p&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 4 E4B reads every file; EmbeddingGemma indexes it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Models: &lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4&lt;/a&gt; and &lt;a href="https://ai.google.dev/gemma/docs/embeddinggemma" rel="noopener noreferrer"&gt;EmbeddingGemma&lt;/a&gt; by Google, under the Gemma Terms of Use, served by &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Database: &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; with &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;.NET libraries: Npgsql and EF Core, &lt;a href="https://github.com/UglyToad/PdfPig" rel="noopener noreferrer"&gt;PdfPig&lt;/a&gt;, &lt;a href="https://github.com/mono/SkiaSharp" rel="noopener noreferrer"&gt;SkiaSharp&lt;/a&gt;, &lt;a href="https://github.com/awaescher/OllamaSharp" rel="noopener noreferrer"&gt;OllamaSharp&lt;/a&gt;, &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;Testcontainers&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;UI: React, Vite, Mock Service Worker.&lt;/li&gt;
&lt;li&gt;The hybrid search is the same idea I used in my earlier &lt;code&gt;rag-semantic-search&lt;/code&gt; project; the SlothBox code was written fresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a Downloads folder you have given up on, point SlothBox at a copy of it and tell me what it got wrong. Open an issue on the repo.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>BibLocal: find every photo of a runner by bib number, offline, on a laptop CPU</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:46:15 +0000</pubDate>
      <link>https://dev.to/iamamitkumarsahoo/biblocal-find-every-photo-of-a-runner-by-bib-number-offline-on-a-laptop-cpu-c9</link>
      <guid>https://dev.to/iamamitkumarsahoo/biblocal-find-every-photo-of-a-runner-by-bib-number-offline-on-a-laptop-cpu-c9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge&lt;/a&gt;: Week 2, Side Hustle.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;BibLocal reads the bib numbers in a folder of race photos and writes them into each file as keywords. After that, a photographer can click a bib number in their photo manager and see every photo of that runner.&lt;/p&gt;

&lt;p&gt;It runs on a laptop CPU with Wi-Fi off. Nothing is uploaded. There is no account, no API key and no GPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who it's for
&lt;/h3&gt;

&lt;p&gt;Race photographers. One 5K or trail race can mean thousands of photos, and every runner wants to find theirs. The usual way to make that possible is to open each photo, zoom in on the bib and type the number.&lt;/p&gt;

&lt;p&gt;Photographers have complained about this for years. On the dgrin forum, a ski-race photographer shooting 2,000 to 3,000 images per race put it at &lt;a href="https://dgrin.com/discussion/194321/automatic-number-plate-recognition-for-photographers" rel="noopener noreferrer"&gt;"maybe 90 minutes to keyword a race."&lt;/a&gt; In 2024, someone on the Adobe Community asked whether any Lightroom Classic plugin could recognise race bib numbers. The Community Expert's &lt;a href="https://community.adobe.com/questions-675/identify-particpant-s-numbers-jersey-975061" rel="noopener noreferrer"&gt;answer was "No."&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't start with this idea. I started with filters. While researching ideas with Claude, I threw out the first batch: one needed a GPU the user wouldn't have, and another was something a free chatbot already does. The next round had to run on an ordinary CPU, beat a free chatbot, depend on open AI, and help people anywhere, not just in my city. When I read about BibLocal, I knew it was solving something real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who would pay for it
&lt;/h3&gt;

&lt;p&gt;Photographers already pay for this. Manual tagging services charge about &lt;a href="https://www.bibnumberentry.com/race-photo-tagging/" rel="noopener noreferrer"&gt;2 cents&lt;/a&gt; to &lt;a href="https://www.capcitysportsmedia.com/Bib-Tagging-Smugmug" rel="noopener noreferrer"&gt;5 cents&lt;/a&gt; per photo. For a 3,000-photo race, that is $60 to $150, every race. Cloud tools charge credits or a subscription, and the photos have to be uploaded first.&lt;/p&gt;

&lt;p&gt;BibLocal is free and MIT licensed. [CHECK: the paid part. Suggestion: a signed one-click Windows build and setup help, sold per photographer instead of per photo.]&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;biblocal scan&lt;/code&gt; reads every JPEG in a folder and proposes bib numbers. It changes no photo.&lt;/li&gt;
&lt;li&gt;Photos it isn't sure about go to a local review page. You confirm or type the bib, press Enter, and download &lt;code&gt;corrections.csv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;biblocal apply&lt;/code&gt; with &lt;code&gt;--write embed&lt;/code&gt; writes the confirmed bibs into the JPEGs as IPTC and XMP keywords, using exiftool. Every original is kept as a backup.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/B49Fq6c-Gsw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;About a minute, no audio. It runs on 29 real race photos from friends who run marathons; they agreed to me using them. The scan is shown at 5x speed. The real scan time was 51.42 seconds. I didn't get a clean recording of the apply step, so the video shows a text card with the result of the real run instead: 25 of 26 photos written.&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/AmitSahoo45" rel="noopener noreferrer"&gt;
        AmitSahoo45
      &lt;/a&gt; / &lt;a href="https://github.com/AmitSahoo45/biblocal" rel="noopener noreferrer"&gt;
        biblocal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;BibLocal&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;BibLocal reads runners' bib numbers in JPEG race photos on a CPU, offline
Default scans preserve photos and write a CSV and local review page. Opt-in writes add bib keywords.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Tested on Windows 11 with Python 3.13.7. Install Python 3.13 and this checkout on D:.
Run in PowerShell from the checkout:&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;PIP_CACHE_DIR&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;D:\.cache\pip&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c1"&gt;New-Item&lt;/span&gt; &lt;span class="pl-k"&gt;-&lt;/span&gt;ItemType Directory &lt;span class="pl-k"&gt;-&lt;/span&gt;Force .tmp &lt;span class="pl-k"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;Out-Null&lt;/span&gt;
&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;TEMP&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; (&lt;span class="pl-c1"&gt;Resolve-Path&lt;/span&gt; .tmp).Path
&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;TMP&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;TEMP&lt;/span&gt;
py &lt;span class="pl-c1"&gt;-3.13&lt;/span&gt; &lt;span class="pl-k"&gt;-&lt;/span&gt;m venv .venv
.venv&lt;span class="pl-k"&gt;/&lt;/span&gt;Scripts&lt;span class="pl-k"&gt;/&lt;/span&gt;&lt;span class="pl-c1"&gt;python.exe&lt;/span&gt; &lt;span class="pl-k"&gt;-&lt;/span&gt;m pip install .
.venv&lt;span class="pl-k"&gt;/&lt;/span&gt;Scripts&lt;span class="pl-k"&gt;/&lt;/span&gt;&lt;span class="pl-c1"&gt;biblocal.exe&lt;/span&gt; scan &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;D:\Photos\Race&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;&lt;span class="pl-c1"&gt;start-list&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;D:\Photos\start.csv&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Setup needs package access or prepared local wheels. It installs CPU ONNX Runtime and RapidOCR's bundled models
No CUDA, GPU runtime, account or API key is needed. This is a CLI, with no…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AmitSahoo45/biblocal" 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;h3&gt;
  
  
  The open AI part
&lt;/h3&gt;

&lt;p&gt;The OCR is PaddleOCR's PP-OCRv6 small detection and recognition models (Apache-2.0), run through &lt;a href="https://github.com/RapidAI/RapidOCR" rel="noopener noreferrer"&gt;RapidOCR&lt;/a&gt; 3.10.0 on the CPU build of ONNX Runtime. The two model files are about 31 MB together and ship inside the RapidOCR package. BibLocal checks their SHA-256 hashes before using them and never downloads a model.&lt;/p&gt;

&lt;p&gt;Each photo is decoded at reduced size with Pillow's JPEG draft mode, turned upright from its EXIF orientation, resized to a long side of 2,048 px and passed to the OCR.&lt;/p&gt;

&lt;h3&gt;
  
  
  The OCR proposes, plain code decides
&lt;/h3&gt;

&lt;p&gt;I didn't want a model deciding what gets written into someone's files. So the OCR only produces candidates, and ordinary Python checks each one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Digits only, with a sensible length.&lt;/li&gt;
&lt;li&gt;Confidence of at least 0.90 to tag. Between 0.50 and 0.90, the photo goes to review.&lt;/li&gt;
&lt;li&gt;On the race's start list, when one is supplied.&lt;/li&gt;
&lt;li&gt;A text box at least 2% of the photo's height, so tiny numbers in the background don't count.&lt;/li&gt;
&lt;li&gt;Not a number that shows up across too many photos. Those are usually banners or signs, not bibs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A photo that fails these checks goes to review or gets no tag. A suggestion is never written as a tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the work was split
&lt;/h3&gt;

&lt;p&gt;I used AI for most of the code, so here is exactly who did what. Claude (Opus 5.5) did the research, the design spec and the task plan, and later edited the demo video. OpenAI Codex, running GPT-6 Astra, wrote most of the code from that plan, test first, one task per commit. My part was choosing the problem, setting the rules, making the call when the numbers disagreed, checking labels and results, running it on real photos, verifying the tags in digiKam and recording the demo.&lt;/p&gt;

&lt;p&gt;The rules were written down before any code, in the repo's &lt;code&gt;CLAUDE.md&lt;/code&gt;. CPU only, even though my laptop has a GPU. No network calls at runtime. Never write to a photo unless asked. When in doubt, send it to review. Permissive licences only, and every model file recorded with its source, size, checksum and licence. The final suite has 134 tests, all on synthetic images, so no real person's photo is in the repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hardest bug: IPTC
&lt;/h3&gt;

&lt;p&gt;Writing a keyword into a JPEG sounds trivial. It wasn't.&lt;/p&gt;

&lt;p&gt;Older photos often carry IPTC metadata encoded as Latin-1. Telling exiftool to treat it as UTF-8 reinterpreted those bytes and corrupted the text; a test with "café" and "El niño" reproduced it. Copying the old IPTC and editing keywords in one command duplicated keywords. Worst of all, exiftool can cut an IPTC keyword longer than 64 bytes and still exit with status 0, leaving only a warning.&lt;/p&gt;

&lt;p&gt;The fix was two separate exiftool commands per photo, run on a staged copy, with any warning treated as a failure. If the write isn't clean, the original stays exactly as it was.&lt;/p&gt;

&lt;p&gt;That rule fired on the real apply run. One photo triggered an exiftool warning about its HDR gain-map data [CHECK wording], so BibLocal skipped it and wrote the other 25.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 10-out-of-30 moment
&lt;/h3&gt;

&lt;p&gt;I tuned the rules on 100 photos from RBNR, a public research dataset of race photos with labelled bibs. Then I asked friends who run marathons for their race photos, and they sent me 30.&lt;/p&gt;

&lt;p&gt;Before the scan, I asked myself: if it fails on real photos, has the project failed?&lt;/p&gt;

&lt;p&gt;The first scan tagged 10 of them.&lt;/p&gt;

&lt;p&gt;The cause was a rule tuned on the dataset. The minimum bib height was 4% of the photo, because that worked on RBNR. In my friends' photos, many bibs are smaller in the frame, and the rule threw them away. At 2%, the scan in the demo gave 23 tagged, 3 sent to review, 3 with no readable bib, and 0 errors. (That run uses 29 photos. I left one out of the video.)&lt;/p&gt;

&lt;p&gt;That change has a cost, and the dataset shows it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Minimum bib height&lt;/th&gt;
&lt;th&gt;Photos with a correct tag&lt;/th&gt;
&lt;th&gt;Wrong tags, realistic start list&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;7.76% (9 of 116)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;19.08% (29 of 152)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Realistic start list" here means every number between the lowest and highest bib at each event, which is close to what a real race list looks like. So at 2%, about one in five tags it accepts on that dataset is wrong. My target was under 3%. It misses that by a long way.&lt;/p&gt;

&lt;p&gt;I kept 2% as the default because a tool that misses most bibs on real photos is no use to anyone, and a wrong tag is visible in the photo manager and can be removed. [CHECK: your reason] If you would rather miss bibs than tag wrong ones, &lt;code&gt;--min-bib-height 0.04&lt;/code&gt; brings back the old behaviour.&lt;/p&gt;

&lt;p&gt;One more caveat about the demo: its start list contains only the 30 bibs that appear in those photos. That is the best case. A real race list can have thousands of numbers, which gives a misread more chances to match one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it doesn't do yet
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The wrong-tag rate above is the first thing to fix. The dataset numbers are also in-sample: 100 photos, no held-out set.&lt;/li&gt;
&lt;li&gt;I checked the keywords in digiKam 9.1.0. Lightroom Classic is untested.&lt;/li&gt;
&lt;li&gt;Speed comes from one laptop with an Intel Core i7-13650HX. On mains power, a 24 MP test image took about 1.5 seconds, roughly 25 minutes per 1,000 photos (an estimate, not a measured batch). On battery saver, it took 12 to 14 seconds per photo.&lt;/li&gt;
&lt;li&gt;It's a command-line tool, tested on Windows 11 only, and it reads JPEGs only.&lt;/li&gt;
&lt;li&gt;Blurred, covered or angled bibs get missed. Someone still has to review.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Race photos are photos of the public, often including children. With a closed vision API, every one of them would be uploaded to someone else's server and paid for per image, and the tool would need internet at the finish line.&lt;/p&gt;

&lt;p&gt;With an open model, the photos never leave the laptop. You can check that yourself by running the scan with Wi-Fi off; I did. Once it's installed, a photo costs nothing to process.&lt;/p&gt;

&lt;p&gt;Open also let me measure it properly. The model files are fixed and checksummed, so the OCR gives the same output every time. The calibration script caches the OCR output for the 100 dataset photos and replays the rules under 18 combinations of settings, which is how the thresholds were chosen. With a closed API, the model can change under you, and every re-run is a bill.&lt;/p&gt;

&lt;p&gt;If a better PP-OCR comes out, or someone fine-tunes one on bib fonts, swapping it in means changing a file and re-running the calibration.&lt;/p&gt;

&lt;p&gt;Open did not make the model good at bibs. It gave me a model I could run as often as I liked and actually measure. That's how I know about the 19%, and I would rather publish that number than hide it.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>Learning Dad's Garden: From 4 Minutes of Phone Video to a Printed Watering Sheet, Offline</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sun, 11 Oct 2026 19:08:54 +0000</pubDate>
      <link>https://dev.to/iamamitkumarsahoo/learning-dads-garden-from-4-minutes-of-phone-video-to-a-printed-watering-sheet-offline-52ic</link>
      <guid>https://dev.to/iamamitkumarsahoo/learning-dads-garden-from-4-minutes-of-phone-video-to-a-printed-watering-sheet-offline-52ic</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;My dad has looked after our pot plants for years. On Sunday morning he filmed them on his phone and explained each pot as he went.&lt;/p&gt;

&lt;p&gt;At the bush with tag 12, he said: "Give regular amount of water, the usual amount."&lt;/p&gt;

&lt;p&gt;He knows exactly how much that is. I didn't. So I built a tool that finds every line like that and asks him.&lt;/p&gt;

&lt;p&gt;It's called Learning Dad's Garden. It turns his walk-through into a printed checklist I can take outside. Each pot gets a row, with a tick box for each day it needs something. Gemma 4 does the reading, on my laptop. Before anything prints, Dad answers the questions a beginner would ask.&lt;/p&gt;

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

&lt;p&gt;I'm taking up Dad's hobby, and he knows what every pot needs. Some of his instructions are exact. Plant with tag 4 gets "200 to 250 ml of water daily". For the pot with tag 9 he said "one cup of water or one and a half cup of water to be accurate, the green cup we have", which is exact, as long as nobody moves the green cup. Others only make sense if you already know the garden: "the usual amount", "water it regularly", "this plantation". To him, those are complete instructions.&lt;/p&gt;

&lt;p&gt;Learning Dad's Garden turns a walk-through like that into something I can follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dad films each part of the garden and talks, the way he would explain it to me in person. &lt;/li&gt;
&lt;li&gt;"Learning Dad's Garden" turns his speech into text and splits it into instructions. Then it checks that every amount and time came from his words.&lt;/li&gt;
&lt;li&gt;A gap check lists everything I couldn't act on. Dad answers on a local web page that shows the video frame and plays his voice from that moment. &lt;/li&gt;
&lt;li&gt;The sheet prints. Each row has the tags, what to do, how much and when, a box for each day it's due, and Dad's exact words underneath.&lt;/li&gt;
&lt;li&gt;I take the sheet outside, water, and tick. Each evening Dad checks the pots and marks every row: done right, missed or done wrong. My dad is grading my homework again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The screen time comes first but it stays small. The routine lives on paper. This week I walk from pot to pot with the sheet and a pencil, and the laptop stays shut.&lt;/p&gt;

&lt;p&gt;It works for anyone learning a garden from someone who knows it by feel. The command line calls them the owner and the helper. In our house, that's Papa and me.&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%2Fmp4pztsbojjdjj5selrw.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%2Fmp4pztsbojjdjj5selrw.png" alt="The top of the printed sheet: a Do NOT box, then tag 12's row with the amount " width="800" height="495"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The top of our sheet. Tag 12's "usual amount" is now half a glass of water, in Dad's typed words. What he said on Sunday sits under the row.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pmRC0_B7b8o" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/AmitSahoo45/learning-dads-garden" rel="noopener noreferrer"&gt;GitHub URL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The licence is MIT.&lt;/p&gt;

&lt;p&gt;It's built on &lt;a href="https://github.com/SYSTRAN/faster-whisper" rel="noopener noreferrer"&gt;faster-whisper&lt;/a&gt;, &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, Gemma 4, &lt;a href="https://github.com/PyAV-Org/PyAV" rel="noopener noreferrer"&gt;PyAV&lt;/a&gt;, &lt;a href="https://python-pillow.org" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt;, &lt;a href="https://jinja.palletsprojects.com" rel="noopener noreferrer"&gt;Jinja2&lt;/a&gt; and headless Chromium for the PDF.&lt;/p&gt;

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

&lt;p&gt;The model has one narrow job, and plain Python checks its work at every step. Pipeline :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Done by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speech to text, with the time of every word&lt;/td&gt;
&lt;td&gt;faster-whisper &lt;code&gt;small.en&lt;/code&gt;, on the CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split into instructions&lt;/td&gt;
&lt;td&gt;Gemma 4 E4B through Ollama, held to a JSON schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grounding check&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Days on the sheet&lt;/td&gt;
&lt;td&gt;Python rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gap check&lt;/td&gt;
&lt;td&gt;Gemma's flags, a phrase list, structure rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;A local page: standard-library server, plain JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print&lt;/td&gt;
&lt;td&gt;Jinja template, then headless Chromium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What Gemma is allowed to do
&lt;/h3&gt;

&lt;p&gt;Gemma gets the transcript of one part of the garden and a short list of rules. The first rule sets the boundary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are not a gardening expert. Never add advice, amounts, times or conditions that the speaker did not say. &lt;br&gt;
(Well, neither am I, which is the whole reason this project exists.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ollama's structured output holds Gemma to a JSON schema. For each instruction, Gemma returns the exact quote, the tag numbers, the kind (do, don't, do only if, or a note), a short command, the amount, the timing and the condition. It also lists any words a helper couldn't act on. An example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"water this daily in the morning or in the evening anytime when you are free give regular amount of water, the usual amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pots_said"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"do"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"what"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Water"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regular amount of water, the usual amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"daily in the morning or in the evening anytime when you are free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vague"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"which_pot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"phrase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"this"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"how_much"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"phrase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regular amount of water, the usual amount"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemma flagged both gaps on its own. It couldn't tell which pot "this" was, or how much "the usual amount" is. Both became questions for Dad.&lt;/p&gt;

&lt;p&gt;I kept Gemma's thinking on, which is its default, with temperature 0 and an 8,192-token context. Gemma 4 E4B can take audio as input, but Whisper still does the listening. The tool needs two things Whisper gives for every word. The time lets the review page play the right clip. The confidence lets the sheet mark words Whisper wasn't sure of. I also give Whisper a list of words from Indian gardens, like tulsi, money plant, curry leaves, lota, etc. so it expects them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every value has to be in Dad's words
&lt;/h3&gt;

&lt;p&gt;A model that restructures text will sometimes try to help. So nothing Gemma returns goes straight onto the sheet. First, the code finds each quote in the transcript, word for word or close to it, with a similarity of at least 0.8. Then it checks every amount, time and condition. A value must be copied from the instruction's own words, or from elsewhere in the same video. Every number in it must appear there too. Anything else is deleted.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ground_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Where a field value came from.

    &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;said&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: copied from the instruction&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s own words.
    &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inferred&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: copied from elsewhere in the same note (for example &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;do the
    same for 26&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;); the owner is asked to confirm it.
    None: not found; the value must be dropped.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_extractive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;numbers_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;said&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_extractive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;numbers_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inferred&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Inferred" covers lines like "do the same for 26". The value is real but comes from another sentence, so the tool asks Dad to confirm it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Days come from rules
&lt;/h3&gt;

&lt;p&gt;The model never decides days. Plain rules turn the timing words into days on the sheet. "Daily" means every day. "Every alternate day", "one day gap" and "every 2 days" all mean days 1, 3, 5 and 7 on a 7-day sheet. That makes 11 rows on odd days and 5 on even days. A striped box means nothing to do that day.&lt;/p&gt;

&lt;h3&gt;
  
  
  The questions
&lt;/h3&gt;

&lt;p&gt;The gap check rebuilds its questions from the instructions whenever anything changes, and it never stores a question as the truth. An answer changes the instruction. "It's fine as is" dismisses that one question. The questions come from five places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemma's own flags, like the two above.&lt;/li&gt;
&lt;li&gt;A phrase list of things people say instead of numbers. Three of its lines:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;how_much&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:as usual|the usual(?: amount| way| quantity)?|like (?:usual|always|i do|before)|the same amount)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;how_often&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:regularly|sometimes|occasionally|now and then|from time to time|often|frequently&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|once in a while|on and off)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when_to_act&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bdepend(?:s|ing)?(?: on| upon)?(?: how| what| whether| if| the)(?: \w+){0,4}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Structure rules: a watering row with no amount, two amounts joined by "or" (tag 9's "100 to 200 ml or 250 ml"), a command that is only a verb and an amount (tag 11's "Keep around 200 ml"), and a Do NOT line that also has a timing.&lt;/li&gt;
&lt;li&gt;Coverage: tags that no instruction names. &lt;/li&gt;
&lt;li&gt;Whisper: a word it heard with less than 5% confidence gets a question if it lands in a field on the sheet. In Dad's quotes it gets a "(?)", as in "amount brown (?) should be not more than 200 ml".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every question carries its moment in the video. The page shows the frame and plays Dad's voice from the first word of the instruction to the last. He doesn't have to remember what "this" meant, because he sees what the camera saw. For pot with tag 12 he typed "This is tag number 12", then "that is usually 1/2 glass of water".&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%2Fjy29k013s0c0wh5okjzb.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%2Fjy29k013s0c0wh5okjzb.png" alt="The questions page. It quotes Dad's words for tag 6 and asks what Amit should do, and how often. The answer box says " width="799" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The tag 6 question, on a copy of the project without our videos, so the frame and audio player are missing. The green line repeats the previous answer back.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the record, Plant (Tag #12) usual amount is half a glass of water. It took a 19-second video, a local AI model and one question to find that out. I could also have just asked him.&lt;/p&gt;

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

&lt;p&gt;The videos show our front door, our pots and my dad's voice. With Gemma and Whisper running on my laptop, none of it leaves the house, and after the first model download a full run works with the Wi-Fi off. Each run costs nothing, so I could re-run it as often as I liked. Anyone taking over a garden can run it too. It needs Docker and, for speed, an NVIDIA GPU, with Gemma 4 E2B as the smaller fallback.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 4 E4B does all the reading. It splits Dad's speech into instructions, flags the words a beginner can't act on and, as an option, reads tag numbers in video frames. It runs locally through Ollama.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>BreakMyQuery: find the row that breaks your SQL</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sun, 04 Oct 2026 14:55:47 +0000</pubDate>
      <link>https://dev.to/iamamitkumarsahoo/breakmyquery-find-the-row-that-breaks-your-sql-31ak</link>
      <guid>https://dev.to/iamamitkumarsahoo/breakmyquery-find-the-row-that-breaks-your-sql-31ak</guid>
      <description>&lt;h1&gt;
  
  
  BreakMyQuery: find the row that breaks your SQL
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built BreakMyQuery for a colleague(also a friend🫠) who is a new graduate hire and is struggling with SQL joins or let's say he's scared of SQL joins.&lt;/p&gt;

&lt;p&gt;A query can pass the sample data while quietly dropping a customer who has no orders. For someone learning joins, seeing the missing customer gives them a concrete place to start investigating.&lt;/p&gt;

&lt;p&gt;BreakMyQuery offers eight SQLite exercises covering joins, NULLs, aggregation, ties, arithmetic, dates, and anti-joins. The learner writes a query, runs it, inspects any counterexample, and tries a repair. Each counterexample shows the breaking dataset, the learner's result, and the expected result. &lt;strong&gt;My traps&lt;/strong&gt; saves their mistakes so they can revisit a query through &lt;strong&gt;Retry&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I have demonstrated the app to my friend(or colleague). The goal is to help them connect the query they wrote to the rows it keeps, loses, or duplicates.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://breakmyquery.streamlit.app" rel="noopener noreferrer"&gt;Try BreakMyQuery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first exercise asks for every customer's name and their number of paid orders, including customers with zero paid orders.&lt;/p&gt;

&lt;p&gt;That one row exposes the problem. The learner can trace why Alice disappears and change their query. The app keeps the reference SQL out of the interface.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/AmitSahoo45/breakmyquery/blob/main/docs/hosted-verification.md" rel="noopener noreferrer"&gt;hosted verification record&lt;/a&gt; documents the tested counterexample, accepted-query, error, history, and Retry paths.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/AmitSahoo45/breakmyquery" rel="noopener noreferrer"&gt;Source code, exercises, tests, and setup instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The application code uses the MIT license. The model weights have their own licenses.&lt;/p&gt;

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

&lt;p&gt;The stack is Python, Streamlit, SQLite, and Pydantic. The hosted demo uses the open-weight &lt;strong&gt;&lt;a href="https://ai.google.dev/gemma/docs/core/model_card_4" rel="noopener noreferrer"&gt;Gemma 4 26B A4B&lt;/a&gt;&lt;/strong&gt; model through Google's Gemini API.&lt;/p&gt;

&lt;p&gt;The app first checks the sample. If the results match, it searches for a breaking case:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Propose.&lt;/strong&gt; The model receives the schema, exercise question, learner query, and reference query. It proposes small datasets intended to make the queries disagree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify.&lt;/strong&gt; The app validates the proposed data and executes both queries in SQLite. It compares results while preserving duplicate-row counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shrink.&lt;/strong&gt; It removes rows while preserving the mismatch and database constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice.&lt;/strong&gt; The learner sees the remaining data and differing results, then edits their own query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Seeded random fuzzing generates additional datasets and provides a fallback when the model returns no usable proposal or the API is unavailable. The hunter does not use the exercise's authored trap fixtures.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the evaluations showed
&lt;/h3&gt;

&lt;p&gt;I evaluated model proposals and fuzzing separately against 14 deliberately wrong queries and eight equivalent alternatives.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Wrong queries exposed&lt;/th&gt;
&lt;th&gt;False positives on alternatives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 via API&lt;/td&gt;
&lt;td&gt;14/14&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Using Local Llama 3.2&lt;/td&gt;
&lt;td&gt;9/14&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seeded random fuzzing&lt;/td&gt;
&lt;td&gt;14/14&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Gemma catalog evaluation used live API calls from the local checkout; the deployed app was checked separately.&lt;/p&gt;

&lt;p&gt;Gemma's figures combine the primary run with one explicit follow-up after an unusable response. Covering the 22 cases took 23 model attempts. The &lt;a href="https://github.com/AmitSahoo45/breakmyquery/blob/main/eval_results_gemma_api.md" rel="noopener noreferrer"&gt;Gemma report&lt;/a&gt; preserves that failed call; the &lt;a href="https://github.com/AmitSahoo45/breakmyquery/blob/main/eval_results_llama32.md" rel="noopener noreferrer"&gt;Llama report&lt;/a&gt; records the separate local trial.&lt;/p&gt;

&lt;p&gt;On this catalog, random testing caught five mistakes Llama missed. Keeping both methods gives the app another chance to find a breaking case.&lt;/p&gt;

&lt;p&gt;I also found a teaching problem: Llama could produce a valid counterexample and still explain its cause incorrectly. I disabled generated hints and mistake labels in the demo. Gemma's teaching quality remains unevaluated. The tables and query results give the learner evidence they can inspect.&lt;/p&gt;

&lt;p&gt;These measurements cover a small authored catalog. &lt;strong&gt;A passing run means no counterexample was found within the search budget.&lt;/strong&gt; It does not prove the query correct.&lt;/p&gt;

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

&lt;p&gt;Open weights give this project a local deployment option. Once the dependencies, runtime, and model are installed, the Ollama workflow runs inference and SQLite on the learner's computer without a hosted inference API.&lt;/p&gt;

&lt;p&gt;The public demo offers an easier starting point: my friend can open a link without installing a model. That convenience has a different data path. Submitted SQL goes to Google, and SQLite verification runs on the app server. The interface discloses this.&lt;/p&gt;

&lt;p&gt;I could evaluate local Llama and Gemma through the API using the same SQLite checker. Another developer can inspect that checker, swap the model, and repeat the evaluation. The model's role is explicit: it proposes cases, and SQLite checks whether they demonstrate a mistake.&lt;/p&gt;

&lt;p&gt;The counterexample approach was inspired by &lt;a href="https://www.cse.iitb.ac.in/infolab/XData/XData.html" rel="noopener noreferrer"&gt;XData at IIT Bombay&lt;/a&gt;. BreakMyQuery applies that idea to a small practice tool for my friend: find a concrete case, inspect the rows, and work out the repair.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4 proposes the breaking datasets used by the hosted demo. SQLite verifies each accepted counterexample before the learner sees it.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>How AI text watermarking works</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sun, 27 Sep 2026 16:58:01 +0000</pubDate>
      <link>https://dev.to/iamamitkumarsahoo/how-ai-text-watermarking-works-4d03</link>
      <guid>https://dev.to/iamamitkumarsahoo/how-ai-text-watermarking-works-4d03</guid>
      <description>&lt;p&gt;An AI model can leave a watermark in the words it chooses. The text looks ordinary, and the mark can survive being copied into Notepad because it is carried by the token sequence. A detector with the matching key can check for that pattern later.&lt;/p&gt;

&lt;p&gt;Anthropic explained its approach in August 2026 in &lt;a href="https://www.anthropic.com/news/claude-text-watermark" rel="noopener noreferrer"&gt;"How Claude's text watermark works"&lt;/a&gt;. It uses a version of SynthID-Text, which Google DeepMind published in &lt;a href="https://www.nature.com/articles/s41586-024-08025-4" rel="noopener noreferrer"&gt;&lt;em&gt;Nature&lt;/em&gt; in 2024&lt;/a&gt;. Anthropic says it is introducing watermarking to meet the EU AI Act's transparency requirements.&lt;/p&gt;

&lt;p&gt;I wanted to understand how ordinary word choices could carry a signature, and what a detector finding that signature would actually tell us. I followed the choice of one token through the process, then tested a small implementation with GPT-2 in Google Colab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with one word
&lt;/h2&gt;

&lt;p&gt;Let's say you upload a photo of a cat to Claude and ask, "What is the cat in this image doing?" Claude starts its answer with "The cat" and now has to pick the next token.&lt;/p&gt;

&lt;p&gt;A language model writes one token at a time. A token can be a word, part of a word or punctuation. GPT-2's tokenizer for example, has roughly 50,000 tokens. The model assigns probabilities to possible next tokens. For a simplified example, assume only these three options have non-zero probabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sits&lt;/code&gt;: 50%&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sat&lt;/code&gt;: 30%&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lies&lt;/code&gt;: 20%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are invented probabilities and not actual measurements. We will treat each word as one token throughout the example. Without a watermark, sampling from these probabilities would pick "sits" about half of the time. The chosen token joins the sentence, and model calculates probabilities for the next token. The watermark changes how the next token is selected. Model's weights remain unchanged. For the same input, it still produces same initial probabilities. Watermarking just influences the selection.&lt;/p&gt;

&lt;p&gt;Without the watermark, a model might write "The cat sits on the mat." With the watermark, it might write "The cat sat on the rug" instead. These are just illustrative outcomes. The choices happen as the text is generated.&lt;/p&gt;

&lt;p&gt;A secret key, combined with the preceding tokens, determines the watermark scores for each possible next token. Changing the preceding tokens can change the scores. With same key, settings and token context, calculation gives the same scores every time. That repeatability makes detection possible later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tournament
&lt;/h2&gt;

&lt;p&gt;SynthID-Text uses a method called &lt;strong&gt;tournament sampling&lt;/strong&gt; to choose the next token. We can follow it through an example with four candidates and two rounds.&lt;br&gt;
Suppose a model has written "The cat" and assigns these probabilities to the next token:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Probability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sits&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sat&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lies&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;First, we draw four candidates using those probabilities. Each draw can pick any of three tokens, including one already picked. One possible result is:&lt;br&gt;
&lt;code&gt;sits, sat, sits, lies&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;"sits" appears twice in this draw. It's 50% probability makes it more likely to appear, but &lt;strong&gt;that does not guarantee two places&lt;/strong&gt;. &lt;br&gt;
Next, watermarking algorithm uses key and preceding tokens to calculate a score of 0 or 1 for each candidate. Each round has its own scoring function. Within a round, repeated copies of the same token receive the same score. Suppose scores produce these matches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Round&lt;/th&gt;
&lt;th&gt;Match&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;sits (0) versus sat (1)&lt;/td&gt;
&lt;td&gt;sat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;sits (0) versus lies (1)&lt;/td&gt;
&lt;td&gt;lies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;sat (1) versus lies (0)&lt;/td&gt;
&lt;td&gt;sat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The higher score wins each match. Ties are broken randomly. A token's score can differ between rounds, which is why "lies" scores 1 in the first round and 0 in the second. The winning token is "sat", so text now reads "The cat sat". Generation continues from there.&lt;/p&gt;

&lt;p&gt;Although "sits" had the highest original probability, both of its copies lost in this tournament. Another draw could produce a different winner. The model's probabilities determine how likely candidates are to enter, and watermark scores determine which candidates advance. This is method described in &lt;a href="https://www.nature.com/articles/s41586-024-08025-4" rel="noopener noreferrer"&gt;SynthID-Text paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Picking "sat" once provides little evidence of a watermark. An unwatermarked model could pick it too. Across many tokens watermarked text tends to have higher watermark scores than expected by chance. The detector measures that tendency. The bracket helps explain the method. The implementation discussed later calculates the equivalent winning probabilities directly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Does watermarking affect writing quality??? 🤔
&lt;/h3&gt;

&lt;p&gt;Drawing candidates from the model's probabilities keeps likely continuations well represented. That alone does not guarantee good writing. SynthID-Text has a quality-preserving configuration and another that produces a stronger watermark at a cost to quality. Google tested the quality-preserving configuration on nearly 20 million Gemini responses. The paper reports no statistically significant difference in thumbs-up or thumbs-down ratings between watermarked and unwatermarked responses. When the model has very little choice about the next token, there is less room to leave a watermark. This makes detection harder in passages where accurate wording leaves few alternatives.&lt;/p&gt;
&lt;h2&gt;
  
  
  How a detector checks without the prompt
&lt;/h2&gt;

&lt;p&gt;The detector uses text, secret key and same watermark settings used during generation. It also needs the matching tokenizer to split the text into tokens. For each token, it takes the preceding tokens and calculates that token's watermark scores again. The calculation is repeatable: the same inputs produce the same scores.&lt;/p&gt;

&lt;p&gt;This works without the prompt because the preceding tokens are already in passage. With a four-token context, detector skips scoring the first four tokens. From the fifth token onward, it has the context it needs. A simple detector averages recovered scores across the eligible tokens. It skips repeated contexts so that a repeated phrase does not count as fresh evidence. SynthID-Text also supports more sophisticated detectors.&lt;/p&gt;

&lt;p&gt;For text written without this watermark, average is expected to be around 0.5 because the scoring functions assign roughly equal numbers of zeros and ones. Watermarked text tends to score higher because tournament favours tokens with higher scores. An average above 0.5 is not enough on its own to establish a watermark. Short passages can score high by chance. Longer passages usually provide more evidence, provided enough of the watermark survives.&lt;/p&gt;

&lt;p&gt;Detectors use thresholds calibrated against unwatermarked text to control false positives. For a simple average-based approach, the threshold also needs to account for text length. Editing can weaken the signal because it changes the inputs to the scoring functions. With a five-token window, replacing one token affects the score inputs for that position and the next four. Replacing a whole word may affect more tokens, depending on how it is tokenized. Small edits can leave much of the original signal intact. Extensive rewriting can make the watermark undetectable.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the code implements the tournament
&lt;/h2&gt;

&lt;p&gt;A tournament with 30 rounds would require 2&lt;sup&gt;30&lt;/sup&gt; candidates for each token, which is over a billion. The implementation calculates the equivalent winning probabilities directly.&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;g_values_at_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g_values&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;g_mass_at_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_values_at_depth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;probs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&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;keepdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;probs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;probs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;g_values_at_depth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;g_mass_at_depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;g_values&lt;/code&gt; contains the watermark scores, and &lt;code&gt;depth&lt;/code&gt; is the number of rounds. Each round adjusts the probabilities to favour tokens that scored 1. The adjustment depends on how much probability those tokens already hold. Using our cat example, the probabilities change as follows. In round 1, "sat" and "lies" score 1. In round 2, only "sat" scores 1; "sits" and "lies" both score 0.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Original probability&lt;/th&gt;
&lt;th&gt;After round 1&lt;/th&gt;
&lt;th&gt;After round 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sits&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;13.75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sat&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;td&gt;69.75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lies&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;16.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 69.75% comes from the chance that at least one finalist is "sat". After round 1, each finalist has a 45% chance of being "sat", so each has a 55% chance of being something else. The chance that neither is "sat" is &lt;code&gt;0.55 × 0.55 = 0.3025&lt;/code&gt;, or 30.25%. Since "sat" beats the other tokens in round 2, its chance of winning is &lt;code&gt;100% - 30.25% = 69.75%&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The implementation calculates these winning probabilities and makes one random choice from them. This gives each token the same chance of selection as running the tournament. "sat" is the most likely choice, but the other tokens still have a combined 30.25% chance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with GPT-2
&lt;/h2&gt;

&lt;p&gt;I used GPT-2 in Google Colab to generate text with and without a watermark. The setup used 30 key values for 30 watermarking rounds, a five-token window, &lt;code&gt;top_k=40&lt;/code&gt; and &lt;code&gt;temperature=0.7&lt;/code&gt;. Each response could contain up to 300 new tokens.&lt;/p&gt;

&lt;p&gt;For each of five prompts, I generated one watermarked response and one plain response. I then took the first 20 words of the watermarked response. Separately, I made an edited version of the full watermarked response by replacing one word in four, starting with the first, with a random choice from "the", "a", "very" and "also". These replacements test the effect of disruptive edits rather than natural paraphrasing. I also scored an F1 text sample.&lt;/p&gt;

&lt;p&gt;The detector averaged the watermark scores after excluding the opening tokens, repeated contexts and end-of-text padding. It produced a raw average score, not a probability that the text was AI-generated.&lt;/p&gt;

&lt;p&gt;The first cell loads GPT-2 and sets the watermark configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SynthIDTextWatermarkingConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SynthIDTextWatermarkLogitsProcessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DEVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVICE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;CFG&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;ngram_len&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;keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;654&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;836&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;340&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;597&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;590&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;639&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;715&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;468&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;990&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;966&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;324&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;585&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;421&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;521&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;732&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;225&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;sampling_table_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sampling_table_seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;context_history_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;wm_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SynthIDTextWatermarkingConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;CFG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SynthIDTextWatermarkLogitsProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;CFG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEVICE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second generates a response with or without the watermark:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;inp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVICE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_sample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pad_token_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eos_token_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;watermarking_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;wm_config&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;watermark&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:],&lt;/span&gt; &lt;span class="n"&gt;skip_special_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third calculates the average watermark score:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVICE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute_g_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="n"&gt;eos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute_eos_token_mask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eos_token_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eos_token_id&lt;/span&gt;&lt;span class="p"&gt;)[:,&lt;/span&gt; &lt;span class="n"&gt;CFG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ngram_len&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="n"&gt;rep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute_context_repetition_mask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eos&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;unsqueeze&lt;/span&gt;&lt;span class="p"&gt;(&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="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&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="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fourth runs the comparisons:&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;prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write about how coffee is grown and exported.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain how trains work.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Descrieb a day at the beach.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tell the history of the bicycle.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain how rain forms.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;human&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;George Russell won the Azerbaijan Grand Prix in Baku on Saturday, 26 September, converting pole position into his third victory of the season. It was not a quiet afternoon. The race had two safety car periods, six retirements, and a finish so close that Russell crossed the line only a tenth of a second ahead of Max Verstappen. Isack Hadjar completed the podium in third place.

The first safety car came out after Alex Albon made an error and binned his Williams. The second followed by Franco Colapinto torpedo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ing himself in turn 1, which also ended the races of his teammate Pierre Gasly and Lando Norris. Both Aston Martins retired with mechanical problems. Oscar Piastri led for much of the race, but a lock-up cost him badly and he finished fourteenth. Charles Leclerc took fourth and Lewis Hamilton sixth. Kimi Antonelli had one of the best drives of the day, climbing from sixteenth to fifth.

The result extends Russell&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s championship lead and denies Verstappen his first win of the season.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;very&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;also&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Plain long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM short (20 words)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM 25% edited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Plain long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM short (20 words)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM 25% edited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; mean=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  runs=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&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;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Human text (F1)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;human&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;These scores are from one run. Generation and word replacement are random, so reruns can produce different values.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Text&lt;/th&gt;
&lt;th&gt;Average score&lt;/th&gt;
&lt;th&gt;Range across five prompts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Watermarked responses&lt;/td&gt;
&lt;td&gt;0.564&lt;/td&gt;
&lt;td&gt;0.548 to 0.586&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain GPT-2 responses&lt;/td&gt;
&lt;td&gt;0.497&lt;/td&gt;
&lt;td&gt;0.489 to 0.509&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First 20 words of watermarked responses&lt;/td&gt;
&lt;td&gt;0.573&lt;/td&gt;
&lt;td&gt;0.545 to 0.607&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watermarked responses with every fourth word edited&lt;/td&gt;
&lt;td&gt;0.500&lt;/td&gt;
&lt;td&gt;0.495 to 0.504&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F1 text sample&lt;/td&gt;
&lt;td&gt;0.498&lt;/td&gt;
&lt;td&gt;One sample&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The watermarked and plain responses had no overlapping scores in these five runs. The F1 sample scored close to the plain responses. This shows a clear difference in this experiment, though more samples would be needed to establish a reliable detection threshold and measure false positives.&lt;/p&gt;

&lt;p&gt;The short excerpts also scored high, but I did not test short unwatermarked excerpts for comparison. These results cannot establish reliable detection on short passages.&lt;/p&gt;

&lt;p&gt;Replacing every fourth word brought the average down to 0.500, close to the unwatermarked baseline. This editing pattern weakened the signal measured by this detector. It does not establish that changing any 25% of a passage would have the same effect.&lt;/p&gt;

&lt;p&gt;This experiment used GPT-2 and the configuration shown above. It does not measure the performance of Claude's watermark. A detected watermark provides evidence that the matching watermarking system was involved in producing the text. Failing to detect one does not establish that a person wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.anthropic.com/news/claude-text-watermark" rel="noopener noreferrer"&gt;Anthropic, "How Claude's text watermark works" (August 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.claude.com/en/articles/16266773-how-claude-marks-ai-generated-content" rel="noopener noreferrer"&gt;Claude Help Center, "How Claude marks AI-generated content"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1038/s41586-024-08025-4" rel="noopener noreferrer"&gt;Dathathri et al., "Scalable watermarking for identifying large language model outputs," Nature 634, pages 818-823 (2024)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-deepmind/synthid-text" rel="noopener noreferrer"&gt;Google DeepMind, SynthID-Text repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py" rel="noopener noreferrer"&gt;Hugging Face Transformers, SynthIDTextWatermarkLogitsProcessor source&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Our SQL Server loop kept writing the previous row's value</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:08:34 +0000</pubDate>
      <link>https://dev.to/iamamitkumarsahoo/our-sql-server-loop-kept-writing-the-previous-rows-value-1gn4</link>
      <guid>https://dev.to/iamamitkumarsahoo/our-sql-server-loop-kept-writing-the-previous-rows-value-1gn4</guid>
      <description>&lt;p&gt;&lt;em&gt;A loop that looks correct, passes review, and still writes the wrong value, because of two documented T-SQL behaviors.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's a loop that resolves a value for each item: read it from a primary table, fall back to a secondary one when the primary has nothing, write the result. It looks correct. On the wrong data it writes the wrong value, and the wrong value isn't random: each bad row gets the value belonging to the row processed just before it.&lt;/p&gt;

&lt;p&gt;No error, no exception. The loop reads correctly top to bottom. The cause is two T-SQL behaviors that Microsoft documents and that are easy to misread:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A variable declared inside a loop is set to &lt;code&gt;NULL&lt;/code&gt; once for the whole batch, not once per iteration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SELECT @var = column&lt;/code&gt; leaves the variable alone when the query returns no rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either one is survivable alone. Together they make a missing row reuse the last good answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the code looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;WHILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ITEM&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ItemList&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;RowNo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;

    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isnull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_VAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_VAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;Primary&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Fallback&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_SEQ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pmax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_SEQ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;Primary&lt;/span&gt; &lt;span class="n"&gt;pmax&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pmax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;LTRIM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTRIM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
    &lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ITEM_VAL&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Fallback&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;

    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;ITEM_VAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read top to bottom, the intent is plain: resolve from the primary table, fall back to the secondary one, write the result. The &lt;code&gt;DECLARE @Value&lt;/code&gt; inside the loop looks like it hands each pass a clean variable. That assumption is the trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  A DECLARE inside a loop isn't a reset
&lt;/h2&gt;

&lt;p&gt;In C++, Java, or C#, a variable declared inside a loop body is a fresh binding every pass. T-SQL doesn't do that. A local variable's scope is the whole batch, and it's initialized to &lt;code&gt;NULL&lt;/code&gt; when it's declared, not at the point in the code where you wrote &lt;code&gt;DECLARE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So putting &lt;code&gt;DECLARE @Value&lt;/code&gt; inside the &lt;code&gt;WHILE&lt;/code&gt; is just decoration. There's one &lt;code&gt;@Value&lt;/code&gt; for the whole run, set to &lt;code&gt;NULL&lt;/code&gt; once, and every iteration after the first inherits whatever the previous one left in it. That's also why SQL Server doesn't complain about a double declaration even though the &lt;code&gt;DECLARE&lt;/code&gt; line is reached on every pass; it isn't a statement that re-runs each time.&lt;/p&gt;

&lt;p&gt;A quick proof:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;WHILE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ISNULL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;PRINT&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;. If the &lt;code&gt;DECLARE&lt;/code&gt; reset &lt;code&gt;@x&lt;/code&gt; each pass, you'd get &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://learn.microsoft.com/en-us/sql/t-sql/language-elements/declare-local-variable-transact-sql" rel="noopener noreferrer"&gt;DECLARE @local_variable (Transact-SQL)&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SELECT-assignment does nothing when nothing matches
&lt;/h2&gt;

&lt;p&gt;The resolve uses &lt;code&gt;SELECT @Value = ...&lt;/code&gt;. When the item isn't in the primary table, the &lt;code&gt;WHERE&lt;/code&gt; returns no rows. You'd assume that sets &lt;code&gt;@Value&lt;/code&gt; to &lt;code&gt;NULL&lt;/code&gt;. It doesn't. With &lt;code&gt;SELECT&lt;/code&gt;-style assignment, a zero-row result leaves the variable exactly where it was.&lt;/p&gt;

&lt;p&gt;So on a missing item the assignment runs, changes nothing, and &lt;code&gt;@Value&lt;/code&gt; still holds whatever the previous item resolved to.&lt;/p&gt;

&lt;p&gt;Microsoft documents this exact case in Example A on that page: a variable set to a default, a &lt;code&gt;SELECT @var = ... WHERE&lt;/code&gt; that matches nothing, and the variable keeps the default.&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://learn.microsoft.com/en-us/sql/t-sql/language-elements/select-local-variable-transact-sql" rel="noopener noreferrer"&gt;SELECT @local_variable (Transact-SQL)&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the two combine
&lt;/h2&gt;

&lt;p&gt;Now the failure is just mechanics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;@Value&lt;/code&gt; is never cleared between iterations.&lt;/li&gt;
&lt;li&gt;For an item missing from the primary table, the resolve matches nothing and doesn't touch &lt;code&gt;@Value&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The guard &lt;code&gt;IF (@Value IS NULL) OR LTRIM(RTRIM(@Value)) = ''&lt;/code&gt; sees a leftover non-null value, decides resolution already worked, and skips the fallback.&lt;/li&gt;
&lt;li&gt;The item gets written with the previous item's value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fallback that should have rescued the missing item never fires, because the stale value makes it look like there was nothing to rescue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it
&lt;/h2&gt;

&lt;p&gt;Two changes close the gap.&lt;/p&gt;

&lt;p&gt;Clear the variable at the top of every iteration so a missing row can't carry the last value forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;WHILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for a single-value lookup like this, prefer &lt;code&gt;SET&lt;/code&gt; with a scalar subquery over &lt;code&gt;SELECT&lt;/code&gt;-assignment. A scalar subquery that finds no rows gives you &lt;code&gt;NULL&lt;/code&gt;, which is what you want here, because then the &lt;code&gt;IS NULL&lt;/code&gt; guard does its job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;isnull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_VAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_VAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;Primary&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Fallback&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_SEQ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pmax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_SEQ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;Primary&lt;/span&gt; &lt;span class="n"&gt;pmax&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pmax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ITEM_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Item_Code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads fine. Each behavior is harmless on its own, which is why the pair survives review. If a loop variable can outlive its iteration, assume it does.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>sqlserver</category>
      <category>database</category>
    </item>
  </channel>
</rss>
