<?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: Vitaliy Hrytsenko</title>
    <description>The latest articles on DEV Community by Vitaliy Hrytsenko (@italik).</description>
    <link>https://dev.to/italik</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%2F4155871%2F42dd309d-beb2-459e-a826-60f32cfcd0e2.jpg</url>
      <title>DEV Community: Vitaliy Hrytsenko</title>
      <link>https://dev.to/italik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/italik"/>
    <language>en</language>
    <item>
      <title>The model never computes a number</title>
      <dc:creator>Vitaliy Hrytsenko</dc:creator>
      <pubDate>Fri, 02 Oct 2026 15:00:31 +0000</pubDate>
      <link>https://dev.to/italik/the-model-never-computes-a-number-4p9e</link>
      <guid>https://dev.to/italik/the-model-never-computes-a-number-4p9e</guid>
      <description>&lt;p&gt;My finance app's AI told me things I knew were wrong. Spending far higher than it really was. A charge counted in this month's numbers that hadn't happened in months.&lt;/p&gt;

&lt;p&gt;Money Track is my personal finance app. It syncs bank statements, sorts spending into categories and has an AI advisor you can ask things like "can I afford this?" On sample data it looked fine. On my real data it started making numbers up.&lt;/p&gt;

&lt;p&gt;In a finance app, a confidently wrong number is worse than no answer. You act on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telling the model didn't work
&lt;/h2&gt;

&lt;p&gt;My first reaction was the usual one: tell the model more clearly. The part of the app that writes notifications already had that instruction. It was told to only repeat figures the app had already calculated, never to work out its own.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What didn't work:&lt;/strong&gt; on real data, one notification quoted two different totals for the same thing. Neither of them was in the data the model had been given.&lt;/p&gt;

&lt;p&gt;The instruction was clear and the model still broke it. A prompt is a request. However well you write it, some answers will ignore it, and you won't know which ones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That changed how I thought about the problem. The goal was never a model that is careful. It was making sure a wrong number can't reach the screen quietly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An instruction to the model is a request. A check in code is a guarantee.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Every number comes from SQL
&lt;/h2&gt;

&lt;p&gt;So I took the arithmetic away from the model completely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every figure is computed in SQL, by the same queries the app's screens use. The chat and the dashboard can't disagree, because they read one source.&lt;/li&gt;
&lt;li&gt;The model gets that snapshot plus read-only tools to look things up. It explains and phrases. It never adds anything up.&lt;/li&gt;
&lt;li&gt;Before an answer is shown, a check compares every sum in it with the numbers the model was given. A figure that isn't there gets dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The check is small. It finds every number in the text and looks for it in the data, with a 1% tolerance for rounding. Anything under 100 is skipped, because counts and percentages are not sums of money.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// worker/lib/ai/grounding.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;numbersAreGrounded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;known&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// spaces inside a number are thousands separators ("3 354")&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d[\d\s]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(?:[&lt;/span&gt;&lt;span class="sr"&gt;.,&lt;/span&gt;&lt;span class="se"&gt;]\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;/g&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;known&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;The same kind of check covers dates and day counts. If the database didn't produce a number, you don't see it.&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%2Fcw804qa9ms7i1x4p9wf9.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%2Fcw804qa9ms7i1x4p9wf9.png" alt="The AI chat in Money Track. Every figure in the answer comes from the snapshot." width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every figure in the chat answer comes from SQL; the model only explains it.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The check can't fix bad data
&lt;/h2&gt;

&lt;p&gt;The check has a limit, and I hit it a few weeks later.&lt;/p&gt;

&lt;p&gt;The app told me that English lessons were shortening my runway, at about $31 a month. I had cancelled that subscription in June. The number was real: the data showed each merchant's spending over the last 90 days divided by three, and the last charges were in June. The model was warned in the prompt not to rely on that figure, and relied on it anyway. The check couldn't object, because the number was genuinely in the data.&lt;/p&gt;

&lt;p&gt;I fixed it by removing the number, not by adding more warnings. A merchant with no charges for 45 days now comes with "stopped" and the days since the last payment, not a monthly figure. The model can't misuse a number it never gets.&lt;/p&gt;

&lt;p&gt;So the check guards what the model writes. You still have to check what you give it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rules first, a model only where needed
&lt;/h2&gt;

&lt;p&gt;The same idea works for sorting transactions into categories, which is where most of the AI cost goes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Plain rules run first: merchants you've already filed, known subscriptions, what most users filed a merchant under, the bank's merchant category codes.&lt;/li&gt;
&lt;li&gt;Only the rows the rules can't settle go to a model.&lt;/li&gt;
&lt;li&gt;Recently I added &lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;Jev&lt;/a&gt;, a small judgment model from TypeSafe, in front of Claude Haiku. Jev files a row itself only when it's at least 80% sure. Everything else goes to Haiku.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 80% line wasn't a guess. In the eval, every answer Jev gave at 0.8 or above was right, and every miss sat below 0.75. That covers 85% of the rows that reach a model.&lt;/p&gt;

&lt;p&gt;The cascade gets about 97% of categories right, the same as Haiku alone, at about a fifth of the cost. It's also faster: on the same eval run Jev took 8 seconds where Haiku took 42.&lt;/p&gt;
&lt;h2&gt;
  
  
  Measure on cases you never tuned on
&lt;/h2&gt;

&lt;p&gt;The most useful thing I built for the AI wasn't a prompt. It was an eval: &lt;code&gt;npm run eval&lt;/code&gt; runs the real categorisation code over 168 raw bank descriptions and reports accuracy and cost together. Before it, every prompt change was judged by reading a few rows and forming an impression.&lt;/p&gt;

&lt;p&gt;It paid off twice.&lt;/p&gt;
&lt;h3&gt;
  
  
  A score of 98.9% that meant nothing
&lt;/h3&gt;

&lt;p&gt;I tuned Jev on that dataset and it scored 98.9%. Then I wrote 36 new merchants it had never seen and ran it again: 72%.&lt;/p&gt;

&lt;p&gt;The cause was mine. Jev's instructions were a shorter copy of the category guide I'd written by hand, and the copy had lost the lists of brands. Two definitions of the same thing had drifted apart. Now both models read one guide, and those 36 merchants stay as a test I never tune on. Haiku gets 35 of them right, and the whole run costs $0.03.&lt;/p&gt;
&lt;h3&gt;
  
  
  A cache that was never on
&lt;/h3&gt;

&lt;p&gt;The eval also showed that prompt caching had never worked for categorisation. The cached guide was 18 tokens short of the minimum Haiku will cache. The API doesn't fail or warn when that happens. It just bills the full prompt every time, and a cache that never engages looks exactly like one that does.&lt;/p&gt;

&lt;p&gt;I padded the guide with real guidance, not filler, so accuracy went up too:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Billed input tokens per run&lt;/td&gt;
&lt;td&gt;187,532&lt;/td&gt;
&lt;td&gt;4,498&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per 1,000 transactions&lt;/td&gt;
&lt;td&gt;$2.86&lt;/td&gt;
&lt;td&gt;$0.68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Category accuracy&lt;/td&gt;
&lt;td&gt;96.6%&lt;/td&gt;
&lt;td&gt;98.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A test now fails the build if the guide gets too short again.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it means for your product
&lt;/h2&gt;

&lt;p&gt;If your AI feature gives answers about money, stock, bookings or anything else people act on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let code compute the facts and let the model talk about them. Then check every answer against the data before it reaches the user.&lt;/li&gt;
&lt;li&gt;Check what you send the model too. A misleading number in the input will pass any check on the output.&lt;/li&gt;
&lt;li&gt;Send the model only the work rules can't do. It's cheaper and more accurate.&lt;/li&gt;
&lt;li&gt;Write test cases the model hasn't seen before you tune the prompt. Otherwise you're measuring your own prompt, and you won't notice a cache that costs you four times more.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://money.italik.dev/demo" rel="noopener noreferrer"&gt;Money Track demo&lt;/a&gt; creates a private sandbox with six months of sample data that resets after 24 hours. The &lt;a href="https://italik.dev/work/money-track" rel="noopener noreferrer"&gt;case study&lt;/a&gt; goes deeper into the architecture, and the code is open source:&lt;/p&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/ITalik-gr" rel="noopener noreferrer"&gt;
        ITalik-gr
      &lt;/a&gt; / &lt;a href="https://github.com/ITalik-gr/money-track" rel="noopener noreferrer"&gt;
        money-track
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI-powered personal finance tracker on Cloudflare Workers. React + TS. Deterministic-first pipeline that keeps the AI from ever inventing the numbers
    &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;Money Track&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A personal finance tracker with an AI advisor that is not allowed to invent a figure.&lt;/p&gt;

&lt;p&gt;It connects to a Ukrainian bank (Monobank) or reads a statement file, categorizes every transaction
with a deterministic-first pipeline, and answers questions about your money from the &lt;strong&gt;same
canonical numbers&lt;/strong&gt; the screens show — never its own. Every figure an AI answer contains is checked
against your data before it renders.&lt;/p&gt;
&lt;p&gt;It also exposes your own ledger to &lt;strong&gt;Claude as an MCP server&lt;/strong&gt; — the app is both the MCP resource
server and its own OAuth 2.1 authorization server, so connecting Claude Desktop, claude.ai or the
mobile app is a URL and a consent screen, with nothing to paste and nothing stored on your disk.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://money.italik.dev/demo" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;/demo&lt;/code&gt;&lt;/a&gt; — spins up a private
throwaway sandbox seeded with ~6 months of realistic data. No sign-up, resets in 24 hours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/dashboard.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Fdashboard.jpg" alt="Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width="50%"&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/statistics-1.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Fstatistics-1.jpg" alt="Statistics — overview"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td width="50%"&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/statistics-2.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Fstatistics-2.jpg" alt="Statistics — trends"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/chat.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Fchat.jpg" alt="AI chat answering from the ledger"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/report.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Freport.jpg" alt="Generated monthly report"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/transactions.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Ftransactions.jpg" alt="Transactions with search and filters"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ITalik-gr/money-track/docs/screenshots/subscriptions.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FITalik-gr%2Fmoney-track%2FHEAD%2Fdocs%2Fscreenshots%2Fsubscriptions.jpg" alt="Subscriptions and recurring payments"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;sup&gt;Screenshots are from the&lt;/sup&gt;…&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/ITalik-gr/money-track" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Does your model invent numbers too? Tell me what you're building in the comments, on &lt;a href="https://t.me/ITalik_gr" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; or on X &lt;a href="https://x.com/italikdev" rel="noopener noreferrer"&gt;@italikdev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://italik.dev/blog/the-model-never-computes-a-number" rel="noopener noreferrer"&gt;italik.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>llm</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
