<?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: Teo Furlan</title>
    <description>The latest articles on DEV Community by Teo Furlan (@teo_furlan).</description>
    <link>https://dev.to/teo_furlan</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%2F3955206%2Fcda045a1-a49d-4ac3-b96f-bf75a12bfdad.png</url>
      <title>DEV Community: Teo Furlan</title>
      <link>https://dev.to/teo_furlan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/teo_furlan"/>
    <language>en</language>
    <item>
      <title>How I Built a Learning System Inside My AI Coding Workflow (Using Obsidian)</title>
      <dc:creator>Teo Furlan</dc:creator>
      <pubDate>Fri, 30 Oct 2026 21:55:25 +0000</pubDate>
      <link>https://dev.to/teo_furlan/how-i-built-a-learning-system-inside-my-ai-coding-workflow-using-obsidian-fbp</link>
      <guid>https://dev.to/teo_furlan/how-i-built-a-learning-system-inside-my-ai-coding-workflow-using-obsidian-fbp</guid>
      <description>&lt;p&gt;Coding with AI agents carries a quiet trap: &lt;strong&gt;the illusion of competence&lt;/strong&gt;.&lt;br&gt;
   Tasks close in record time, architecture moves forward, and everything builds cleanly. But at the end of the day, an uncomfortable question remain: &lt;em&gt;Did I actually learn any of this, or was I just acting as a prompt supervisor?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In an intend to prevent AI from turning into a mindless "code vending machine," I designed an integrated learning system where the agent doesn't just write software—it documents my learning journey, maps concepts, and actively tests my comprehension.&lt;/p&gt;

&lt;p&gt;Here is how the system works:&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Rule: The Vault Stores Comprehension, Not Code
&lt;/h2&gt;

&lt;p&gt;I maintain an Obsidian vault (&lt;code&gt;Engineering/&lt;/code&gt;) directly connected to my AI agent sessions via automated hooks and skills.&lt;/p&gt;

&lt;p&gt;The foundational rule is strict: &lt;strong&gt;the vault never duplicates repository docs or generic boilerplate&lt;/strong&gt;. It only captures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mental models and architectural analogies.&lt;/li&gt;
&lt;li&gt;Real trade-offs and discarded alternatives.&lt;/li&gt;
&lt;li&gt;Current Architecture Decision Records (ADRs) and their change history.&lt;/li&gt;
&lt;li&gt;Especific markers distinguishing verified facts &lt;code&gt;[V]&lt;/code&gt; from assumptions &lt;code&gt;[A]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  System Architecture: 3 Key Layers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx9oaq4egaohi7bfaeoi2.jpg" 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%2Fx9oaq4egaohi7bfaeoi2.jpg" alt="Learning Workflow with AI Agents and Obsidian" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Architecture of the learning pipeline: from session classification to milestone exams.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Contextual Project Modes (&lt;code&gt;learn&lt;/code&gt;, &lt;code&gt;log&lt;/code&gt;, &lt;code&gt;off&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Whenever an agent session starts inside any repository, it checks the configured mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;learn&lt;/code&gt;&lt;/strong&gt;: Full pedagogical depth. Generates architecture notes, links concepts, and triggers milestone exams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;log&lt;/code&gt;&lt;/strong&gt;: Lightweight mode. Only tracks architectural decisions (ADRs) and concept
links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;off&lt;/code&gt;&lt;/strong&gt;: Zero overhead. No notes written to the vault.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Background Concept Distillation
&lt;/h3&gt;

&lt;p&gt;Whenever I ask a conceptual question during a session—such as &lt;em&gt;"What is backpressure?"&lt;/em&gt; or &lt;em&gt;"Why prefer this concurrency pattern here?"&lt;/em&gt;—the agent answers in chat and quietly reports:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Saved "Backpressure" to Concepts.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concepts start small: a 3–5 line &lt;strong&gt;Term&lt;/strong&gt; (definition, quick example, and the context where it surfaced). As the same pattern appears across different projects, it matures in place into a deeper &lt;strong&gt;Concept&lt;/strong&gt;. Links are embedded organically in the body text, weaving a knowledge web over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Milestone Exams (Active Recall)
&lt;/h3&gt;

&lt;p&gt;Passive note-taking doesn't build long-term retention. To counter this, whenever a milestone is reached in a &lt;code&gt;learn&lt;/code&gt; project, the agent offers an exam:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10 to 12 open-ended questions&lt;/strong&gt; (no multiple choice—multiple choice tests recognition, not recall).&lt;/li&gt;
&lt;li&gt;Grounded strictly in the actual repository code, decisions made, and real bugs encountered during development.&lt;/li&gt;
&lt;li&gt;Covers real code reading, failure scenarios, and change propagation (&lt;em&gt;"If we swap dependency X tomorrow, what could break and why?"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Supports an oral mode where questions are evaluated through speech and voice transcription.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I cannot defend and explain &lt;em&gt;why&lt;/em&gt; an architectural path was chosen, the task is not considered truly learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed in My Daily Routine
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;True Retention:&lt;/strong&gt; The codebase doesn't feel foreign when returning to it two weeks later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Project Synergy:&lt;/strong&gt; A concept learned while fixing a bug in Rust or Go naturally connects to an architectural decision made months later in a TypeScript API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI as a Socratic Partner:&lt;/strong&gt; The agent transitions from an autocomplete utility into a technical partner that demands conceptual clarity before and after shipping code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are using AI agents to write software, consider designing your workflow to amplify your own understanding, not replace it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Do you have a system for retaining what you learn while coding with AI? Let's discuss your setup in the comments below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>obsidian</category>
      <category>learning</category>
    </item>
    <item>
      <title>I highlight paper books. A small open model types my quotes while I sleep</title>
      <dc:creator>Teo Furlan</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:33:04 +0000</pubDate>
      <link>https://dev.to/teo_furlan/i-highlight-paper-books-a-small-open-model-types-my-quotes-while-i-sleep-4197</link>
      <guid>https://dev.to/teo_furlan/i-highlight-paper-books-a-small-open-model-types-my-quotes-while-i-sleep-4197</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I read on paper, with a highlighter in hand. Every quote that stops me ends up in a Notion database: knowing I'll keep what I mark makes me read more carefully, writing it down helps me remember it, and later I can find the right line when it comes up in a conversation or in something I'm writing.&lt;/p&gt;

&lt;p&gt;The chore is in between: sitting down with the book, finding each highlighted passage again and typing it word by word. By the end of a book I have dozens of highlighted lines and no will to copy them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dogear&lt;/strong&gt; removes that chore. While reading, the only extra thing I do is take a photo of each page I highlighted. Overnight, a small open-weight model running on a plain CPU in the cloud reads the photos and pulls out exactly the highlighted passages. In the morning I review them and stay in full control: I can keep, edit or delete any quote, and add one the night run missed. What I keep stays in my library, and a Notion export is next.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dogear works end to end today: I photograph a page from the app, the night run reads it, and the quotes wait for me in the morning. What's still ahead lists what comes next.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://dogear-web.onrender.com" rel="noopener noreferrer"&gt;dogear-web.onrender.com&lt;/a&gt;. Anyone can sign in and start their own library. Page photos are read by the next nightly run (06:00 UTC) and deleted afterwards.&lt;/p&gt;

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

&lt;p&gt;The video walks through the whole loop with public-domain books. The screens are the real app with demo data, and the quotes and crops come from Dogear's own pipeline (Tesseract and Gemma 4 on a CPU) run on a page of &lt;em&gt;Pride and Prejudice&lt;/em&gt;. The night-run numbers are a real line from the Render log.&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/teofurlan" rel="noopener noreferrer"&gt;
        teofurlan
      &lt;/a&gt; / &lt;a href="https://github.com/teofurlan/dogear" rel="noopener noreferrer"&gt;
        dogear
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Photograph highlighted pages of paper books; a small open model extracts the quotes overnight.
    &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;Dogear&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Photograph the pages you highlighted in a paper book. Overnight, a small open-weight model on a plain CPU types out exactly the highlighted passages. In the morning you review them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dogear-web.onrender.com" rel="nofollow noopener noreferrer"&gt;Live app&lt;/a&gt; · &lt;a href="https://youtu.be/X18GtpoFXyM" rel="nofollow noopener noreferrer"&gt;Demo video&lt;/a&gt; · &lt;a href="https://github.com/teofurlan/dogear/docs/api.md" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;DEV Hacktoberfest Open-Source AI Challenge, Week 3: Slow and Steady&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Reading on paper with a highlighter is great; copying the highlights out afterwards is not. Dogear moves that chore to a night job and keeps you in charge of the result:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add a book&lt;/strong&gt; by typing it or photographing its cover. OCR suggests the title and author; nothing is saved until you confirm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photograph each page you highlighted&lt;/strong&gt; while the book is still open. The photo goes to a private bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overnight&lt;/strong&gt;, a cron job reads every pending page with Tesseract and Gemma 4 on a CPU, stores each highlighted passage as a…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/teofurlan/dogear" 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;
  
  
  My highlighter is almost invisible to a computer
&lt;/h3&gt;

&lt;p&gt;Before writing any app code, I tested the riskiest assumption with three real photos taken on my lap, one-handed. In a phone photo, a highlighter barely changes the paper: its color saturation measured about 7 out of 255, which is basically gray.&lt;/p&gt;

&lt;p&gt;What worked was the Lab color space, which separates brightness from color. Instead of looking for a particular color, the code measures how far each pixel drifts from the paper's own tint, so it doesn't care which pen I picked. Light was the next trap: with half a page in shadow, one paper tint no longer fit the whole photo. The fix was to fit the tint as a smooth surface across the page and compare each pixel with the tint at its own position. That found every highlighted block on my test pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model is a good reader and a bad judge
&lt;/h3&gt;

&lt;p&gt;My first plan was to hand the whole photo to Gemma 4 (&lt;code&gt;e4b&lt;/code&gt;, running in Ollama) and ask for the highlighted text. On one page it found 1 of 3 passages. On the other, it confidently returned the chapter title, which isn't highlighted at all. Given only a crop, it read well, but once it silently dropped a whole clause. A quote app that drops words is worse than no app.&lt;/p&gt;

&lt;p&gt;So the design flipped: &lt;strong&gt;the model reads, the code decides.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Color decides what is highlighted.&lt;/strong&gt; It's deterministic and cheap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCR finds where every word is.&lt;/strong&gt; Tesseract returns a box for each word, and a word belongs to a quote when enough of the paper under it carries highlighter. That found the exact first and last word of every passage, even when a highlight starts mid-line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two readers, compared.&lt;/strong&gt; OCR knows &lt;em&gt;where&lt;/em&gt; but misreads letters. Gemma writes clean text but can skip words. Each block is read by both, and Gemma's version is kept where they agree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disagreement is flagged for me&lt;/strong&gt;, with the photo crop next to the quote. Flags only point my attention: every quote stays mine to keep, edit or delete.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On my two test pages, the pipeline found 3 of 3 passages on each, with no false quotes. Watching Gemma fail taught me two fixes: send crops shaped like a text selection instead of scattered word boxes, and compare readings in a way that forgives OCR's glued words and the page's line-break hyphens. In the end, 4 of the 6 quotes came out with both readers agreeing, and the other 2 were flagged, exactly where OCR scrambled the text near the book's gutter. No wrong quote was presented as confident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow on purpose
&lt;/h3&gt;

&lt;p&gt;None of this has to happen when I take the photo, so it doesn't. A nightly cron job on Render processes the day's photos on a plain CPU, with no GPU. The model weights are baked into the Docker image, and a page takes about a minute and a half on four CPU cores, so even a heavy reading day of 30 pages is done in under an hour, long before I wake up. The job first ran on an 8 GB instance for about 2.5 cents, but Gemma peaked near 7.4 GiB there, so it moved to 16 GB for about a cent more per night.&lt;/p&gt;

&lt;h3&gt;
  
  
  From a script to an app
&lt;/h3&gt;

&lt;p&gt;Around the pipeline, I built a FastAPI server and a React PWA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My books, only for me.&lt;/strong&gt; Sign-in goes through Auth0, the server checks every token, and books live in MongoDB Atlas tied to the verified account. Tests make sure one reader never sees another reader's books.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A library that looks like a book catalog&lt;/strong&gt;, with covers from Open Library. When there's no reliable match, a book gets a plain placeholder instead of a guessed edition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adding a book, the lazy way.&lt;/strong&gt; I type it or photograph the cover. OCR suggests the title and author, nothing is saved until I confirm, and the cover photo is never stored. Same rule as the quotes: the machine suggests, I decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photos go to the night run, privately.&lt;/strong&gt; From a book's page I photograph what I highlighted; the phone shrinks the photo and the server stores it in a private Cloudflare R2 bucket. The nightly job claims each pending page, writes its quotes with a small crop of the passage, and deletes the photo. If Gemma can't read a page, the photo waits for another night instead of settling for OCR alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The morning review.&lt;/strong&gt; Each quote sits next to the crop it came from. I keep, edit or delete it, or add a passage the run missed; where OCR and Gemma disagreed, the app says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A careful PWA.&lt;/strong&gt; Its service worker keeps only a public "you're offline" page. Sign-in data, books and photos never touch its cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's still ahead
&lt;/h3&gt;

&lt;p&gt;The full loop works: library, page capture, night run and morning review. Next come search by meaning across my quotes, the Notion export, deleting a book together with its quotes, and a dedicated in-app camera.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My reading stays mine.&lt;/strong&gt; My photos and highlights are read by a model running in my own night job, not sent to a third-party AI API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs a few cents a night.&lt;/strong&gt; A small open model on a plain CPU is cheap enough to run every night for years, with no per-request bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model doesn't change under me.&lt;/strong&gt; The whole design rests on knowing how Gemma fails. With the weights pinned in my image, tomorrow's model fails the same way today's does, so the checks I tuned against it keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable parts.&lt;/strong&gt; The model and the OCR sit behind small interfaces, so a better open model can replace Gemma without touching the rest.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; in a dense book, I highlight 2 or 3 passages per page. Copying them is a 30 to 60 minute typing session, so I keep postponing it, and the quotes pile up in books I've already shelved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; one photo per highlighted page, taken while the book is still open, and a short review over breakfast. The review screen now exists, so I'll measure the real review time over the next books I read.&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 &lt;code&gt;e4b&lt;/code&gt; reads every highlighted block, cross-checked against OCR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; the nightly CPU cron job that runs the extraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of MongoDB Atlas:&lt;/strong&gt; each reader's books, stored per verified account.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Pricor: local AI reads the supplier spreadsheet, an exact engine does the math</title>
      <dc:creator>Teo Furlan</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:01:02 +0000</pubDate>
      <link>https://dev.to/teo_furlan/pricor-local-ai-reads-the-supplier-spreadsheet-an-exact-engine-does-the-math-3915</link>
      <guid>https://dev.to/teo_furlan/pricor-local-ai-reads-the-supplier-spreadsheet-an-exact-engine-does-the-math-3915</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 Week 2: Side Hustle&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Two suppliers send you their price lists. Supplier A's spreadsheet says &lt;code&gt;12.100,00&lt;/code&gt; under &lt;em&gt;Precio Bulto c/IVA&lt;/em&gt; (price per box, VAT included), &lt;code&gt;10+5&lt;/code&gt; under &lt;em&gt;Bonificación&lt;/em&gt;, and &lt;code&gt;100&lt;/code&gt; under &lt;em&gt;Unid x Bulto&lt;/em&gt;. Supplier B's says &lt;code&gt;92,00&lt;/code&gt; under &lt;em&gt;Costo Unit. s/IVA&lt;/em&gt; (unit cost, before VAT) and &lt;code&gt;8%&lt;/code&gt; under &lt;em&gt;Dto&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Both are selling the same screw. Which one is cheaper?&lt;/p&gt;

&lt;p&gt;You cannot tell until both lists speak the same language. Supplier A's screw costs &lt;strong&gt;85.50&lt;/strong&gt; per unit: two chained discounts (not 15%), VAT removed, the box split into 100 units. Supplier B's costs &lt;strong&gt;84.64&lt;/strong&gt;. In many small Argentine shops, and at least one distributor I have talked to, someone does this translation by hand, for every supplier, in every supplier's own format, every time a list arrives. And next week, one of the columns will have moved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricor&lt;/strong&gt; (&lt;em&gt;price&lt;/em&gt; + &lt;em&gt;order&lt;/em&gt;) is a local app that turns those files into consistent, importable product records: supplier code, description, net unit cost and currency, with row-level traceability. It reads Excel or CSV, proposes what each column means, asks about whatever the file does not say, and shows every number from the original cell to the final cost before you approve it. Rejected rows stay visible and exportable; nothing is dropped silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricor prepares the file; it does not replace the shop's management system.&lt;/strong&gt; Those systems already import price updates, but only in their own fixed formats.&lt;/p&gt;

&lt;p&gt;Who would pay? The side hustle I have in mind is setting up and supporting a shop's recurring supplier imports, built on this tool. That is a hypothesis, not validated demand: I have not had a paying customer or measured time saved yet.&lt;/p&gt;

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

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

&lt;p&gt;The video was built from screens recorded in the running app with &lt;strong&gt;Laya and Gemma 4 E4B running locally&lt;/strong&gt;, using two small fictional lists ("Ferretería Norte" and "Distribuidora Sur") written for the recording. The narration is a local Kokoro voice.&lt;/p&gt;

&lt;p&gt;A longer browser rehearsal on two lists from a real public supplier price page covered the full loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First import.&lt;/strong&gt; Gemma's suggestions arrive with no answer preselected. I confirmed the columns and assumptions, inspected the calculation trace, approved, and downloaded the CSV/XLSX exports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next week's list.&lt;/strong&gt; The same supplier's saved plan was reused. Pricor noticed that the price column had moved and followed the saved mapping instead of reading the wrong column.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price report.&lt;/strong&gt; Comparing the two approved lists: &lt;strong&gt;514 increases, 118 decreases, 420 unchanged, 8 new products, 5 discontinued, and one +200% anomaly&lt;/strong&gt;, across 1,057 and 1,060 accepted products.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To be clear about that run: the second list was &lt;strong&gt;simulated&lt;/strong&gt; by a tool that perturbs a real list and records every change in a manifest. The exports were checked against every changed price and every added or removed code in that manifest, not just the totals. It demonstrates the behavior; it is not evidence of a real supplier's price update.&lt;/p&gt;

&lt;p&gt;The arithmetic behind the example at the top, as the receipts in the demo show it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12,100.00   per box, 21% VAT included
 × 0.90 × 0.95   chained discounts of 10% and 5%
 ÷ 1.21          remove VAT
 ÷ 100           units per box
=    85.50 net per unit

92.00       per unit, before VAT
 × 0.92          8% discount
=    84.64 net per unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No model calculates those results.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Repository: &lt;a href="https://github.com/teofurlan/pricor" rel="noopener noreferrer"&gt;github.com/teofurlan/pricor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application is Apache-2.0: backend, frontend, tests, the corpus download tooling (supplier files are linked from their public pages, not redistributed), the Laya fine-tuning tooling and the benchmark. The fine-tuned Laya weights are not published yet; the training script reproduces them.&lt;/p&gt;

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

&lt;p&gt;The whole design is one rule: &lt;strong&gt;models interpret, the engine executes, the user decides.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw52uc66bioy7afvti8t9.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%2Fw52uc66bioy7afvti8t9.png" alt="Pricor as a local filter: supplier files go through a deterministic profile, Laya, Gemma 4 and the user, then an exact engine produces one common format for the system the shop already uses" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pricor sits &lt;strong&gt;in front of&lt;/strong&gt; the system a shop already uses (or the one it is moving to), as a local filter with three layers: Laya, then Gemma, then you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;deterministic profiler&lt;/strong&gt; finds the header row, row types, apparent column types and number formats (&lt;code&gt;12.100,00&lt;/code&gt; is not &lt;code&gt;12.1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A fine-tuned multilingual &lt;strong&gt;Laya&lt;/strong&gt; model classifies each column into a closed set of roles. Columns it is unsure about go to &lt;strong&gt;Gemma 4 E4B&lt;/strong&gt;, running locally through Ollama. Whatever is still uncertain goes to the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Questions are generated deterministically&lt;/strong&gt; from what the plan is missing: does the price include VAT? Is it per pack or per unit? Which currency? Gemma may suggest an answer and show its (uncalibrated) confidence, but nothing is accepted on the user's behalf.&lt;/li&gt;
&lt;li&gt;The executor only understands a &lt;strong&gt;closed catalog of operations&lt;/strong&gt;: parse numbers, apply chained discounts, remove VAT, split packs into units, map values, normalize text and, optionally, convert currency. It never runs model-generated code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; serves a &lt;strong&gt;React&lt;/strong&gt; frontend. &lt;strong&gt;SQLite&lt;/strong&gt; stores approved plan versions and price history. Exports come with a separate rejected-rows report.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plans are saved per supplier. Before reusing one, a fingerprint checks each column's position, normalized header and apparent type. If a column the plan needs is missing, the affected rule is blocked instead of quietly producing a cost. Currency conversion keeps the original value and currency, and snapshots the exchange rate used for that run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory is explicit, local and cited
&lt;/h3&gt;

&lt;p&gt;Pricor can remember things like "this supplier's prices always include VAT". Ollama produces &lt;strong&gt;bge-m3&lt;/strong&gt; embeddings, SQLite stores them, and Python ranks matches by cosine similarity.&lt;/p&gt;

&lt;p&gt;A memory is created only when the user writes one, or accepts a Gemma suggestion and clicks &lt;strong&gt;Remember this&lt;/strong&gt;. Never automatically. The most relevant memories are passed to Gemma as numbered, untrusted data; Gemma must cite any memory a suggestion relies on, and the UI shows the citation. A reply citing a memory that was not supplied is discarded entirely. If memory is unavailable, the import continues with a notice. A Memory panel lets the user view, edit and delete everything.&lt;/p&gt;

&lt;p&gt;In one manual check (not a benchmark), with one note about one supplier and one about another, Gemma's VAT and per-pack suggestions for the first supplier cited the right note and not the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  The benchmark did not justify automating everything
&lt;/h3&gt;

&lt;p&gt;The benchmark in the repo has only &lt;strong&gt;29 held-out columns from two distributors&lt;/strong&gt;, so read it as a sanity check, not a leaderboard:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Classifier&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Keyword/type heuristics&lt;/td&gt;
&lt;td&gt;24/29&lt;/td&gt;
&lt;td&gt;82.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laya zero-shot&lt;/td&gt;
&lt;td&gt;18/29&lt;/td&gt;
&lt;td&gt;62.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laya fine-tuned&lt;/td&gt;
&lt;td&gt;22/29&lt;/td&gt;
&lt;td&gt;75.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma prompt-only&lt;/td&gt;
&lt;td&gt;25/29&lt;/td&gt;
&lt;td&gt;86.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the whole-sheet cascade, 11 columns were resolved automatically (10 correctly) and &lt;strong&gt;18 of 29 still went to the user&lt;/strong&gt;. That is the honest result: on this data, the app should ask. Laya took about 35 ms per column on GPU; Gemma averaged about 11.4 s per sheet (RTX 4060 Laptop GPU). Those are different units, not a speedup claim.&lt;/p&gt;

&lt;p&gt;The caveats are documented next to the numbers: thresholds were chosen on partly synthetic validation data, the test set lacks some roles, the heuristics were written after their held-out headers had been seen, and two labels were corrected after the first run. A heuristics-plus-Laya agreement stage I tried afterwards showed no advantage, so the shipped cascade does not use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs worth telling
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Percentages.&lt;/strong&gt; Excel stores a cell displayed as &lt;code&gt;21%&lt;/code&gt; as &lt;code&gt;0.21&lt;/code&gt;. Lose the number format and 21% VAT becomes 0.21% VAT, which still produces a plausible-looking cost. The reader now keeps that meaning at the spreadsheet boundary, without touching the engine's percentage-point contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A second discount column.&lt;/strong&gt; While recording the demo, I found that when a list has two separate discount columns, the plan builder uses only the first one. Chained discounts in one cell (&lt;code&gt;10+5&lt;/code&gt;) are handled correctly, which is what the demo uses, but a second column is currently ignored without a question, which breaks the app's own "nothing silent" rule. Fixing that is next on the list.&lt;/p&gt;

&lt;p&gt;The backend suite passes &lt;strong&gt;1,219 tests&lt;/strong&gt; (three live-model tests deselected) and the frontend suite &lt;strong&gt;194&lt;/strong&gt;. None of that proves compatibility with every Excel file a supplier can produce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm Starting From
&lt;/h2&gt;

&lt;p&gt;I did not start from a model looking for a problem. I started from what is publicly visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supplier price lists change often in Argentina.&lt;/strong&gt; Press reports describe kiosks receiving price-change notices from two or three suppliers a week. Inflation has slowed in 2026, so this is less urgent than in 2023–24, but lists still change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Management systems already import prices, but only in their own format.&lt;/strong&gt; The documentation of several local systems (Dux, ADM Global, Flexxus) describes re-importing a file whose columns match their fields, or filling a fixed template. None documents per-supplier saved mappings, pack-to-unit conversion, or chained discounts with VAT included. That gap is my inference from their docs, not a survey.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What people told me so far
&lt;/h3&gt;

&lt;p&gt;After publishing the first version of this post, I talked to two people. Two conversations are not validation, but they changed how I describe the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An IT colleague at a distributor&lt;/strong&gt; confirmed that the product data suppliers send is loaded into the distributor's system &lt;strong&gt;by hand&lt;/strong&gt;, and said a tool like this would help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A business owner&lt;/strong&gt; told me Pricor was not very useful for their specific case, but could be very useful for &lt;strong&gt;automating data entry into new or existing systems&lt;/strong&gt;: shops and companies moving from paper records to a digital system. They offered to ask colleagues whether it would help them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second idea is broader than supplier price lists, and I have not tested it. The next step is still the same: a supervised trial with a real list someone already handles by hand.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The open models are in the import path, not bolted on as a chatbot.&lt;/strong&gt; Laya and Gemma read the supplier's data, and their limits directly decide when the app has to ask the user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; A shop's supplier lists and costs never leave its own computer to reach a hosted model provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; A shop imports the same suppliers every week. With local inference, reusing a plan has no per-call fee. Electricity, hardware and support are not free, but there is no meter running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; Imports work without internet. Only the optional online exchange-rate refresh needs a connection; a cached or manually entered rate works offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning and measurement.&lt;/strong&gt; Open weights let me fine-tune Laya for this narrow task and publish exactly how I measured it. It improved over its zero-shot baseline but did not beat Gemma or the heuristics on this small set, and I can say so because everything is inspectable and replaceable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licensing.&lt;/strong&gt; Gemma 4 is Apache-2.0, so it can ship inside something a shop pays for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is not more features: it is a supervised trial with a shop or distributor, using a list they already load by hand. Compare Pricor's output against their current import, watch where the confirmation steps confuse them, and ask what recurring support would be worth paying for. Until then, the business case stays provisional.&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 runs locally through Ollama. It classifies the columns Laya is unsure about, suggests answers to the plan's questions, and uses explicitly saved memories as cited context. It is not fine-tuned, and it never does price arithmetic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am also entering for the overall prize. I do not claim any other partner integration.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Loci: A Walk You Have to Remember</title>
      <dc:creator>Teo Furlan</dc:creator>
      <pubDate>Mon, 12 Oct 2026 03:16:10 +0000</pubDate>
      <link>https://dev.to/teo_furlan/loci-a-walk-you-have-to-remember-14lf</link>
      <guid>https://dev.to/teo_furlan/loci-a-walk-you-have-to-remember-14lf</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;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I often walk my dog, and too many walks become the same loop: I hesitate over where to go, then choose the familiar route again. I do the same when I head somewhere nearby, like the supermarket. I wanted a reason to explore on foot, and a little more purpose than scrolling through a walk.&lt;/p&gt;

&lt;p&gt;Recently I enabled my phone's warning for using it while walking. I was surprised by how often I was reading social media or chatting as I moved. That made me want to try using technology against the pull of technology: a memory game that gives me something to think about, then asks me to put the phone away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loci&lt;/strong&gt; is an Android app that plans a walking loop through real places near you, turns them into one short mnemonic story, and then hides the map. You look at the route once, put the phone in your pocket, and walk it from memory. The screen is the shortest part of the experience: a minute to memorize, then the rest of the walk is outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meet Loci
&lt;/h3&gt;

&lt;p&gt;Loci is also the name of the character you talk to: a small pixel-art companion on a black field. It waits on the home screen, listens while it plans, gives a hint when you ask, and celebrates when you finish a route. Instead of filling in a form, you ask it for a walk, by typing or by voice, and it answers with a route made of places around you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ask Loci&lt;/th&gt;
&lt;th&gt;Loci listens while planning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fw323ggnl2wcr8p5cx884.png" alt="Home screen: Loci waits and asks where to walk today, with example requests and a text and microphone input." width="720" height="1612"&gt;&lt;/td&gt;
&lt;td&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%2Fhxhvegnirmrfhl4s00zr.png" alt="Loci listens while the app finds places near you." width="720" height="1612"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every landmark is an original 32×32 pixel sprite on a night map, so the places become memory hooks rather than interchangeable pins.&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%2F1zzb52q0a8rpbb5p4q80.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%2F1zzb52q0a8rpbb5p4q80.png" alt="Memorize screen: a night map with numbered pixel-art landmarks and a story fragment in a dialogue box beside Loci." width="720" height="1612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to play
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Ask Loci for a walk or run ("20 min walk, green areas"), then review the planned course and its story.&lt;/li&gt;
&lt;li&gt;Study the landmarks while the map is visible. The story is the cue to remember.&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Hide map &amp;amp; start&lt;/strong&gt;, put the phone in your pocket and follow the story from memory. GPS keeps checking with the screen off, and a vibration marks each checkpoint. Order doesn't matter in the current mode.&lt;/li&gt;
&lt;li&gt;Ask for a hint if you need one: it replays that place's story fragment and gives a direction, for half credit. Giving up is always available and reveals the map.&lt;/li&gt;
&lt;li&gt;At the end, Loci shows what you remembered, with a badge for each place.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Walk from memory&lt;/th&gt;
&lt;th&gt;Results&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fmojup40hu007iro8ktaw.png" alt="Pocket screen: Loci on a black field, a place counter, a Hint button and a Give up button." width="720" height="1600"&gt;&lt;/td&gt;
&lt;td&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%2Fu80eqnt8hmpttftzctow.png" alt="Results screen: Loci, the score, a badge for each checkpoint and the route map." width="720" height="1612"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The phone is never locked by the app, so calls and emergency help remain available. Planning requires connectivity; a prepared run works offline. Loci is not turn-by-turn navigation and makes no route-safety guarantee.&lt;/p&gt;

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

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

&lt;p&gt;Every screen in the video is the real release build (v1.0.0), recorded on the Android emulator. The only simulated thing is the GPS position (San Telmo, Buenos Aires), moved in small steps to walk the route. The narration is synthetic, generated locally with the open-weight Kokoro voice model.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try it:&lt;/strong&gt; install the Android APK from the &lt;a href="https://github.com/teofurlan/loci/releases/tag/v1.0.0" rel="noopener noreferrer"&gt;v1.0.0 release&lt;/a&gt; (a sideloaded build, not a Play Store one).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://lociwalk.vercel.app" rel="noopener noreferrer"&gt;lociwalk.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/teofurlan" rel="noopener noreferrer"&gt;
        teofurlan
      &lt;/a&gt; / &lt;a href="https://github.com/teofurlan/loci" rel="noopener noreferrer"&gt;
        loci
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Loci: a memory walk. Gemma turns a random loop of real OpenStreetMap landmarks into a method-of-loci story; you look once, pocket the phone, and walk it from memory.
    &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;Loci&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Loci is an Android app that plans a walking loop through real landmarks, turns it into a mnemonic story, and asks you to walk it from memory with the phone in your pocket. It is built for the &lt;a href="https://dev.to/challenges/hf26" rel="nofollow"&gt;DEV Hacktoberfest 2026 challenge&lt;/a&gt; (&lt;code&gt;hf26&lt;/code&gt;, Gemma category).&lt;/p&gt;
&lt;p&gt;You ask Loci, a small pixel-art companion, for a route ("20 min walk, green areas") by typing or by voice. Loci finds named places near you in OpenStreetMap and links them into a short story using the method of loci: each place becomes a vivid image, and the images follow one another. You read (or listen to) the story once with the map visible, then hide the map and walk. GPS confirms each checkpoint with a vibration, and at the end Loci shows how many you remembered.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;See it&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://lociwalk.vercel.app" rel="nofollow noopener noreferrer"&gt;lociwalk.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo video&lt;/strong&gt; (1:29, the release build on the Android emulator with…&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/teofurlan/loci" 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;
  
  
  Gemma is the mechanic
&lt;/h3&gt;

&lt;p&gt;Gemma isn't a chat feature added for decoration. Without a story that connects places to memorable images, there is no memory challenge to carry away from the screen.&lt;/p&gt;

&lt;p&gt;Gemma has two jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse the request.&lt;/strong&gt; It turns free text ("30 min run, I remember places better than street names") into bounded route parameters: distance or time, pace, preferences, story style. The app validates and clamps every value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the story.&lt;/strong&gt; It writes one short mnemonic fragment per landmark, using the method of loci: each place becomes a vivid image, and each image leads to the next. The input is only landmark IDs, names and kinds. No coordinates go to the model, and the IDs that come back are checked against the fetched set.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gemma never gets to invent landmarks or coordinates. The places come from OpenStreetMap; the model only supplies the words. Voice input uses Android speech recognition and feeds the same text path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;The route and memory-game logic is plain TypeScript, with external services behind small adapters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Free-text request ──&amp;gt; intent parser ──&amp;gt; bounded route intent
                                           │
GPS origin ──&amp;gt; loop candidates ──&amp;gt; Overpass landmarks ──&amp;gt; snapped checkpoints
                                           │
                                           └──&amp;gt; story generator ──&amp;gt; Gemma via Vercel proxy
                                                                      │
                         Expo / React Native screens &amp;lt;── route + story┘
                         GPS fixes ──&amp;gt; hit detection ──&amp;gt; score + vibration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domain handles distance calculations, randomized loop candidates, landmark selection, GPS hit detection, session state, hints and scoring. Application code composes those steps into route planning. Adapters connect the ports to Overpass mirrors, LLM clients, story templates and cache, and local visited-landmark history. Expo and React Native provide the Android app; MapLibre draws the map with OpenFreeMap tiles. I wrote the domain logic, the adapters and the pure view models behind the screens test-first; the suite has 586 Jest tests.&lt;/p&gt;

&lt;p&gt;In production, the app calls the open-weight Gemma 4 model through the Gemini API behind a small Vercel proxy. The proxy fixes the model, caps input and output sizes, applies best-effort per-IP rate limiting and keeps the API key server-side instead of inside the APK. If generation fails, Loci falls back to a cached or template story.&lt;/p&gt;

&lt;p&gt;The screen-off run is deliberately simple: it checks proximity to the prepared checkpoints, not whether I followed a particular street between them. Hints and giving up are always available, so the score is never the point of the walk. That balance matters to me: I want a small reason to pay attention to where I am, while still being free to keep walking or stop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leave room for model thinking.&lt;/strong&gt; Gemma's reasoning could use up a small token budget and leave an empty visible answer. More headroom fixed the case I saw.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A failed request can hide the next one.&lt;/strong&gt; A transient proxy 502 plus an over-eager, client-wide cooldown meant the story request could silently fall back to a template. Retrying transient proxy failures and limiting the cooldown to Ollama fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public data services need alternatives.&lt;/strong&gt; Overpass returned 504s, so the app tries mirrors instead of depending on a single endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen-off GPS needs Android foreground work.&lt;/strong&gt; A foreground service keeps the run's location tracking alive while the screen is off.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Loci is built from open pieces end to end, and each one does a job a closed service would have made harder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open map data is what keeps the model honest.&lt;/strong&gt; The places come from OpenStreetMap through Overpass, with names and kinds for parks, monuments, churches, artworks and more (the app draws 18 kinds). Because the real place data is open and queryable, the model never has to "know" a neighborhood: it only writes words about places the app already fetched and verified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An open-weight model can move.&lt;/strong&gt; The same prompts and validation run against Gemma on my laptop through Ollama during development, at no cost and with no account, and against hosted Gemma in production. Swapping is configuration, not a rewrite, and running Gemma on the phone itself is a realistic next step rather than a dependency on one vendor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No map API keys or quotas.&lt;/strong&gt; MapLibre with OpenFreeMap tiles means the map costs nothing to show and can't be switched off by a billing change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small and inspectable.&lt;/strong&gt; The proxy is a single open function with fixed model, size caps and rate limiting, so anyone can see exactly what leaves the phone: landmark names and kinds, never your coordinates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where it worked better than a closed approach: I could develop the whole story pipeline offline against a local model, test failure paths for free, and keep the app's promise ("the model only writes the words") enforceable in code, because both the data and the model are open.&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 parses every request and writes every story; the route and the game are built around what it produces.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;Take Loci on a longer outdoor walk and share where it went, the distance and the score.&lt;/li&gt;
&lt;li&gt;Let Loci ask a clarifying question when a request is vague.&lt;/li&gt;
&lt;li&gt;An ordered quest mode, where remembering each place unlocks the next clue.&lt;/li&gt;
&lt;li&gt;On-device Gemma, so planning can work without a connection too.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>android</category>
      <category>gemma</category>
    </item>
  </channel>
</rss>
