<?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: DebMondalX</title>
    <description>The latest articles on DEV Community by DebMondalX (@debmondalx).</description>
    <link>https://dev.to/debmondalx</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%2F491603%2Fd7a41599-b22e-4d4e-bc50-9dac4c25521b.png</url>
      <title>DEV Community: DebMondalX</title>
      <link>https://dev.to/debmondalx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/debmondalx"/>
    <language>en</language>
    <item>
      <title>I fine-tuned a 4B model to read my bank statement, and it beat the 397B model that taught it</title>
      <dc:creator>DebMondalX</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:04:16 +0000</pubDate>
      <link>https://dev.to/debmondalx/i-fine-tuned-a-4b-model-to-read-my-bank-statement-and-it-beat-the-397b-model-that-taught-it-3hm8</link>
      <guid>https://dev.to/debmondalx/i-fine-tuned-a-4b-model-to-read-my-bank-statement-and-it-beat-the-397b-model-that-taught-it-3hm8</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;p&gt;My bank statement says this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPI/ZOMATO LIM/&amp;lt;ID&amp;gt;.g/UPI/AXIS BANK/&amp;lt;REF12&amp;gt;/&amp;lt;ID&amp;gt;/
ACH/GUJARAT MINERAL DEVE/&amp;lt;NUM&amp;gt;
&amp;lt;NUM&amp;gt; NET BANKING SI -NBMEPFODKUZUVXCX
TO &amp;lt;CARD&amp;gt;/HIB- &amp;lt;ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I wanted it to say this:&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;merchant&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;₹129.93&lt;/td&gt;
&lt;td&gt;Zomato&lt;/td&gt;
&lt;td&gt;food delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;₹285.00 credit&lt;/td&gt;
&lt;td&gt;Gujarat Mineral Development&lt;/td&gt;
&lt;td&gt;dividend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;₹5,000.00&lt;/td&gt;
&lt;td&gt;Standing instruction&lt;/td&gt;
&lt;td&gt;recurring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;₹2,198.00&lt;/td&gt;
&lt;td&gt;Credit card bill&lt;/td&gt;
&lt;td&gt;card / EMI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So I built &lt;strong&gt;UPI Decoder&lt;/strong&gt;: a 4-billion-parameter open model, fine-tuned on Tinker, that turns cryptic Indian bank narrations into a clean, categorised ledger. It's trained on three years of my own statements across three banks, and no real name or account number ever reached the models that labelled or trained it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every Indian with a bank account has the same problem. UPI made payments effortless and statements unreadable. Half of each line is reference numbers, the payee name is cut to 10 characters, and each bank prints it differently, using some of the entries from my bank statements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ICICI:&lt;/strong&gt; &lt;code&gt;UPI/&amp;lt;name&amp;gt;/&amp;lt;vpa&amp;gt;/&amp;lt;note&amp;gt;/&amp;lt;bank&amp;gt;/&amp;lt;ref&amp;gt;/&amp;lt;gateway-id&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HDFC:&lt;/strong&gt; &lt;code&gt;UPI-NAME-VPA@HANDLE-IFSC-REF-PAYMENT TO &amp;lt;vpa&amp;gt;&lt;/code&gt;, all upper case, with &lt;code&gt;-&lt;/code&gt; as the separator even though names and UPI IDs also contain &lt;code&gt;-&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HSBC:&lt;/strong&gt; gives you a &lt;em&gt;scanned image PDF&lt;/em&gt;, with each transaction spread over four lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expense apps handle this with keyword rules. On my data, rules got &lt;strong&gt;65% of categories right&lt;/strong&gt;. That's fine for "Swiggy → food" and useless for the long tail: dividends from 30 different companies, money moving between my own accounts, shopkeepers paid via QR to their personal UPI IDs, card bills, SIPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Salaried Professionals &amp;amp; Freelancers&lt;/strong&gt; • Tax season requires manually hunting through hundreds of lines of junk data to track rent receipts, equity investments, insurance premiums, and savings interest. \&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small CA firms&lt;/strong&gt; • Every March, junior accountants waste hundreds of billable hours manually cleaning up messy Excel or PDF statements for individual clients. This tool processes a client's entire financial year in seconds. Because it runs completely offline, firms can guarantee zero data leaks—making it easily worth a subscription of a few thousand rupees per tax season.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output per line is small and strict:&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="nl"&gt;"merchant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Zerodha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"counterparty_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"merchant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"investment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"flags"&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="s2"&gt;"sip"&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;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;That's the fine-tuned model running &lt;strong&gt;on my MacBook Air through Ollama, offline&lt;/strong&gt;, on a synthetic statement (no real data in the video). The app (&lt;code&gt;streamlit run app/app.py&lt;/code&gt;) takes CSV, XLS or scanned-PDF exports and shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;ledger&lt;/strong&gt; with merchant, category, counterparty and flags for every line, plus a &lt;em&gt;"rules say"&lt;/em&gt; column wherever a keyword-rules expense app would have guessed differently&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;monthly spend&lt;/strong&gt; pivot and chart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no hosted version, on purpose. Real bank statements shouldn't be uploaded to anyone's server, including mine.&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/DebMondalX" rel="noopener noreferrer"&gt;
        DebMondalX
      &lt;/a&gt; / &lt;a href="https://github.com/DebMondalX/upi-decoder" rel="noopener noreferrer"&gt;
        upi-decoder
      &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;UPI Decoder&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Your bank statement says &lt;code&gt;UPI/DR/412398765432/SWIGGY LIMI/YESB/swiggy.stores@ye/Payment fr&lt;/code&gt;
UPI Decoder says &lt;em&gt;Swiggy · food delivery · ₹412&lt;/em&gt;. On your laptop, offline.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A small open model (Qwen3.5-4B), fine-tuned with &lt;a href="https://tinker-docs.thinkingmachines.ai/" rel="nofollow noopener noreferrer"&gt;Tinker&lt;/a&gt;
that turns cryptic Indian bank narrations into a clean, categorised ledger: who you paid, what for,
and which lines are noise (self-transfers, refunds, reversals). It also gives you a monthly summary
and an ITR view (rent, investments, insurance, interest).&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12" rel="nofollow"&gt;DEV Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write-up:&lt;/strong&gt; &lt;a href="https://dev.to/debmondalx/i-fine-tuned-a-4b-model-to-read-my-bank-statement-and-it-beat-the-397b-model-that-taught-it-3hm8" rel="nofollow"&gt;I fine-tuned a 4B model to read my bank statement, and it beat the 397B model that taught it&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Demo (runs offline on a MacBook Air, synthetic statement):&lt;/strong&gt; &lt;a href="https://youtu.be/T4COhEbQ5YI" rel="nofollow noopener noreferrer"&gt;https://youtu.be/T4COhEbQ5YI&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Results&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Trained on ~2,300 of my own transactions from three banks (ICICI, HDFC, HSBC, Dec 2023 – Oct 2026).
Two held-out test sets, one row per narration pattern:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;test_in&lt;/code&gt;&lt;/strong&gt;: later months than anything in…&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/DebMondalX/upi-decoder" 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;
  
  
  1. Parse and scrub, before any model sees it
&lt;/h3&gt;

&lt;p&gt;2,290 transactions from ICICI, HDFC and HSBC, December 2023 to October 2026. The parser finds each export's header row by itself, handles narrations split over two rows and &lt;code&gt;Dr/Cr&lt;/code&gt; columns, and rewrites HDFC's dash-separated format into a single shape.&lt;/p&gt;

&lt;p&gt;HSBC was the fun one. Its statements are &lt;strong&gt;image-only PDFs&lt;/strong&gt; with no text layer. I render each page, OCR it with Tesseract using word positions, and rebuild rows from the column layout. Then I &lt;strong&gt;check every row against the running balance&lt;/strong&gt;, because OCR loves turning &lt;code&gt;228,252.20&lt;/code&gt; into &lt;code&gt;252.2&lt;/code&gt;. When &lt;code&gt;previous ± amount ≠ balance&lt;/code&gt;, the reconciler tries "the amount was misread" and "the balance was misread", and keeps whichever makes the &lt;em&gt;next&lt;/em&gt; row consistent. That fixed every misread I checked against the page images.&lt;/p&gt;

&lt;p&gt;Then a scrubber replaces everything personal &lt;strong&gt;before any model sees the data&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account numbers, card numbers, UPI references, IFSC codes, PAN and phone numbers&lt;/li&gt;
&lt;li&gt;my own name, which becomes &lt;code&gt;SELF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;every person's name, which becomes a stable &lt;em&gt;fake&lt;/em&gt; name, so "Rahul" is always "Kabir Sen"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Banks truncate names and UPI IDs mid-word (&lt;code&gt;SHILA MOND&lt;/code&gt;, &lt;code&gt;samyakjai2@&lt;/code&gt;), so the scrubber matches truncations too. It also auto-detects person-like payees, which found ~270 names. Merchant names and merchant UPI IDs (&lt;code&gt;swiggy.stores@ye&lt;/code&gt;) are kept on purpose, because they're the signal.&lt;/p&gt;

&lt;p&gt;What the model sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPI/KABIR SEN/user_6c03@axl/HDFC0&amp;lt;IFSC&amp;gt;/&amp;lt;REF12&amp;gt;/PAYMENT TO VPA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. A 397B open model labels, a human-style review fixes conventions
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;Qwen3.5-397B-A17B on Tinker&lt;/strong&gt; as the teacher. It got a long prompt with category definitions and rules plus six worked examples, and labelled 1,076 de-duplicated training rows. Every label came back as valid JSON. The run cost about &lt;strong&gt;$2.60&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The teacher is good but inconsistent. It called some company dividends &lt;code&gt;other&lt;/code&gt; and others &lt;code&gt;salary&lt;/code&gt;. It labelled credits from my own HDFC account as "interest from HDFC Bank". It read the bank name at the end of a UPI line as the payee and called it a bank fee. So I wrote the labelling conventions down as code (&lt;code&gt;scripts/03b&lt;/code&gt;, &lt;code&gt;03c&lt;/code&gt;) and applied them on top. 164 labels changed, and the same rules define the test labels.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Honest splits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Training:&lt;/strong&gt; everything up to April 2026, with near-duplicate lines capped at 3 per pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;test_in&lt;/code&gt;:&lt;/strong&gt; 150 rows from May 2026 onwards, one per narration pattern, so 40 Swiggy orders can't inflate the score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;test_unseen&lt;/code&gt;:&lt;/strong&gt; 57 rows from &lt;strong&gt;HSBC, a bank that's entirely absent from training&lt;/strong&gt;, in OCR'd text.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Fine-tune with Tinker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Qwen3.5-4B&lt;/strong&gt;, LoRA rank 32, 3 epochs, learning rate 4.9e-4 (the cookbook's recommendation for this model), batch 32, about 90 steps, no-thinking renderer. The training script is ~60 lines around &lt;code&gt;tinker_cookbook.supervised.train&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The trick that matters: the student is trained with a &lt;strong&gt;one-line system prompt&lt;/strong&gt;. The teacher needed 1,360 input tokens per row of instructions and examples. The fine-tuned model has those instructions &lt;em&gt;in its weights&lt;/em&gt;, so each call is &lt;strong&gt;77 tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Held-out NLL went from 1.18 to 0.020, with train and eval curves tracking each other the whole way.&lt;/p&gt;

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

&lt;p&gt;Later months (&lt;code&gt;test_in&lt;/code&gt;, 150 rows):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;system&lt;/th&gt;
&lt;th&gt;category acc&lt;/th&gt;
&lt;th&gt;macro-F1&lt;/th&gt;
&lt;th&gt;merchant exact&lt;/th&gt;
&lt;th&gt;counterparty acc&lt;/th&gt;
&lt;th&gt;input tok/row&lt;/th&gt;
&lt;th&gt;$/1k rows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;keyword rules&lt;/td&gt;
&lt;td&gt;65.3%&lt;/td&gt;
&lt;td&gt;0.37&lt;/td&gt;
&lt;td&gt;72.0%&lt;/td&gt;
&lt;td&gt;82.0%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B, few-shot&lt;/td&gt;
&lt;td&gt;78.7%&lt;/td&gt;
&lt;td&gt;0.65&lt;/td&gt;
&lt;td&gt;71.3%&lt;/td&gt;
&lt;td&gt;74.7%&lt;/td&gt;
&lt;td&gt;1,360&lt;/td&gt;
&lt;td&gt;$0.48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-397B, few-shot (teacher)&lt;/td&gt;
&lt;td&gt;86.7%&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;td&gt;78.7%&lt;/td&gt;
&lt;td&gt;81.3%&lt;/td&gt;
&lt;td&gt;1,360&lt;/td&gt;
&lt;td&gt;$2.47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen3.5-4B + Tinker LoRA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.83&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;82.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.06&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;same model, 4-bit, on my MacBook Air (Ollama)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.86&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;82.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A bank it never saw (&lt;code&gt;test_unseen&lt;/code&gt;, HSBC, 57 rows):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;system&lt;/th&gt;
&lt;th&gt;category acc&lt;/th&gt;
&lt;th&gt;macro-F1&lt;/th&gt;
&lt;th&gt;merchant exact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;keyword rules&lt;/td&gt;
&lt;td&gt;75.4%&lt;/td&gt;
&lt;td&gt;0.68&lt;/td&gt;
&lt;td&gt;73.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B, few-shot&lt;/td&gt;
&lt;td&gt;80.7%&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;73.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-397B, few-shot&lt;/td&gt;
&lt;td&gt;87.7%&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;td&gt;77.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen3.5-4B + Tinker LoRA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.84&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77.2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;same, 4-bit on my laptop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;86.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.82&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77.2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the one I care about most. I downloaded the LoRA from Tinker, merged it into the base model, compressed it to 4-bit and ran it in Ollama on a MacBook Air: &lt;strong&gt;about 2 seconds per transaction, nothing leaves the laptop, and accuracy didn't drop&lt;/strong&gt;. On the unseen bank it lost one row out of 57.&lt;/p&gt;

&lt;p&gt;What I take from this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The 4B student beats the 397B teacher&lt;/strong&gt; on later months. It learned &lt;em&gt;my&lt;/em&gt; conventions; the teacher only has its own guesses. Counterparty accuracy is the starkest gap: 95% vs 81%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It generalises to a new bank format&lt;/strong&gt; as well as the 100x larger model does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;About 44x cheaper per row&lt;/strong&gt; than the teacher on Tinker, and &lt;strong&gt;free on my own laptop&lt;/strong&gt;, with the same accuracy after 4-bit compression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewing labels was worth it.&lt;/strong&gt; A run on the teacher's raw labels did slightly better on later-months category accuracy (94.0%). But it did much worse on the unseen bank (82.5% vs 87.7%), on self-transfers (F1 0.78 vs 0.93) and on merchant names.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it still fails
&lt;/h3&gt;

&lt;p&gt;I went through every miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brand vs legal name:&lt;/strong&gt; &lt;code&gt;BLINK COMMERCE PVT LTD&lt;/code&gt; (Blinkit) is called &lt;em&gt;utilities&lt;/em&gt;. The model knows Blinkit, not its legal name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HSBC card bills&lt;/strong&gt; (&lt;code&gt;TO &amp;lt;CARD&amp;gt;/HIB-…&lt;/code&gt;) come out as &lt;code&gt;other&lt;/code&gt;. HSBC's format never appeared in training; that's the price of an honest unseen-bank test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conventions I added late:&lt;/strong&gt; money moved between my own accounts with notes like "NSE IPO" or "LT stock" should count as &lt;em&gt;investment&lt;/em&gt;. That rule had only 6 training examples, and the model mostly said p2p.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judgement calls:&lt;/strong&gt; it thinks Rentomojo (furniture rental) is &lt;code&gt;rent&lt;/code&gt;. Honestly, fair.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Because this is &lt;strong&gt;bank data&lt;/strong&gt;. Every UPI line carries who you paid, where, how often and how much. A closed API means shipping all of that to someone else's server, per row, forever.&lt;/p&gt;

&lt;p&gt;With open weights the expensive part happens &lt;strong&gt;once&lt;/strong&gt;, on scrubbed data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by construction.&lt;/strong&gt; The teacher and the trainer only ever saw placeholders and fake names, never real names, numbers or accounts. After training, the weights are mine: &lt;code&gt;scripts/08_export.py&lt;/code&gt; downloads the LoRA from Tinker, merges it into Qwen3.5-4B, and Ollama compresses it to 4-bit, so it runs &lt;strong&gt;on a laptop with no network&lt;/strong&gt;. A CA firm can run it on an office laptop and honestly tell clients their statements never left the building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The economics work.&lt;/strong&gt; At $0.06 per 1,000 rows on Tinker, and $0 locally, you can charge ₹99 per statement or a few thousand per season to a CA firm and keep nearly all of it. At the teacher's price, or a frontier API's, the margin disappears on the long tail of tiny UPI payments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's &lt;em&gt;my&lt;/em&gt; model.&lt;/strong&gt; Everyone's statement has its own conventions: which account is "mine", what counts as an investment, what a "PG" payment means. Open weights plus a training API meant I could teach those in an evening, then check them on a held-out bank. A closed model would have needed the 1,360-token prompt on every call, and it would still disagree with me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; Teacher, student, tokenizer, renderer and export path are all open. If Tinker's prices change, the LoRA is a file I already have.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;I didn't get a real user in time. Nobody sends a friend their bank statement on a weekend deadline, which is fair, and is the product's whole thesis. So I showed it to the two people whose opinion matters most for this idea: an engineer, and a practising chartered accountant, the person who'd actually pay for it.&lt;br&gt;
The engineer (a backend dev friend):&lt;br&gt;
• Improvement suggestion: They pointed out that running batch inference on Streamlit's main thread freezes the UI during long CSV uploads. They suggested moving the Ollama generation loop into an asynchronous background worker with a visual progress bar.&lt;br&gt;
The CA:&lt;br&gt;
• Comments: The offline nature of the app was an immediate winner. They explained the legal &amp;amp; ethical constraints to upload a client's PDF statement to a random web app or OpenAI's servers. If this runs locally on their office desktop, that barrier completely disappears.&lt;br&gt;
• The Verdict: To fully trust it for audit purposes, they requested a visual "Confidence Score" or flag for rows where the model's prediction diverges from standard keyword rules. They said a small firm would gladly pay for a service like this, per tax season, if there were some more features like exporting to various formats, creating a report, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test labels:&lt;/strong&gt; the 207 test rows were labelled from scratch, independently of the Qwen teacher. The same written conventions apply to the training labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One person's data:&lt;/strong&gt; this is three banks, but one person's spending. Expect lower numbers on someone with a very different life; that's what the first-user test is for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency:&lt;/strong&gt; the laptop number (~2 s/row, p50) is a MacBook Air running the 4-bit model one row at a time. A statement has a few hundred &lt;em&gt;distinct&lt;/em&gt; narrations, so a year is a few minutes, once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refunds:&lt;/strong&gt; none in my test windows, so I can't claim anything about refund detection yet.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Tinker:&lt;/strong&gt; teacher labelling with Qwen3.5-397B, LoRA fine-tuning of Qwen3.5-4B, a reviewed-vs-unreviewed run comparison, and LoRA export for local inference. The fine-tune beats its 397B teacher at ~1/44th of the cost per row.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Automating GitHub Repository Setup with Pulumi</title>
      <dc:creator>DebMondalX</dc:creator>
      <pubDate>Sun, 06 Apr 2025 10:20:49 +0000</pubDate>
      <link>https://dev.to/debmondalx/automating-github-repository-setup-with-pulumi-o5o</link>
      <guid>https://dev.to/debmondalx/automating-github-repository-setup-with-pulumi-o5o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pulumi"&gt;Pulumi Deploy and Document Challenge&lt;/a&gt;: Get Creative with Pulumi and GitHub&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;A Python-based tool that automates the creation and configuration of GitHub repositories using Pulumi's Automation API along with the Pulumi GitHub provider.&lt;/p&gt;

&lt;p&gt;The goal was to simplify and standardize the repo setup process allowing users to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide custom inputs like repo name, description, and visibility&lt;/li&gt;
&lt;li&gt;Automatically provision a GitHub repository&lt;/li&gt;
&lt;li&gt;Avoid writing Pulumi YAML files manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interact entirely via a Python script with clean CLI prompts&lt;/p&gt;

&lt;p&gt;Think of it as a developer-focused script that can spin up GitHub repositories in a repeatable, customizable, and version-controlled way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo Link
&lt;/h2&gt;

&lt;p&gt;Here's a video demo of the code in action:&lt;br&gt;&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/MNe4-GDeSac"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/DebMondalX/pulumi-repo-automation" rel="noopener noreferrer"&gt;Github link&lt;/a&gt;. &lt;br&gt;
Be sure to check out the &lt;code&gt;README&lt;/code&gt; for setup instructions, project structure, and usage guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Journey
&lt;/h2&gt;

&lt;p&gt;When I first came across the Pulumi Deploy and Document Challenge, I started diving into the Pulumi documentation to explore the wide range of services and capabilities it offers. I wanted to create something useful yet approachable, and my idea gradually took shape as I explored Pulumi’s support for GitHub automation.&lt;/p&gt;

&lt;p&gt;This was actually my first experience using Pulumi, and also my first real hands-on dive into Infrastructure as Code (IaC), GitHub Automation, and dynamic configuration management. Concepts like stacks, state management, and the way Pulumi seamlessly integrates with cloud providers and third-party services were entirely new to me. I spent time understanding what Pulumi could do and what integrations were available, especially within the GitHub ecosystem.&lt;/p&gt;

&lt;p&gt;Initially, I played around with simple examples and code blocks, just to get a feel for the workflow—experimenting with Pulumi programs, trying out the Automation API, and exploring how configuration values could be set dynamically. Slowly, I began stitching those concepts together into a complete solution that automates GitHub repository setup via a Python script.&lt;/p&gt;

&lt;p&gt;One challenge was balancing automation with flexibility—I wanted to make sure that users could customize their repositories on the fly without needing to touch the code. Learning to achieve that through &lt;code&gt;pulumi.automation&lt;/code&gt; and &lt;code&gt;pulumi_github&lt;/code&gt; was definitely rewarding.&lt;/p&gt;

&lt;p&gt;That said, I didn’t get as much time as I’d have liked to fully explore everything Pulumi has to offer—but I’ve genuinely enjoyed this initial experience and plan to dive much deeper into Pulumi and the world of infrastructure automation in the near future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Pulumi with GitHub
&lt;/h2&gt;

&lt;p&gt;This project was my first hands-on experience using Pulumi, and it opened my eyes to how powerful Infrastructure as Code (IaC) can be even outside traditional cloud infrastructure. Pulumi’s integration with GitHub allowed me to automate the creation of repositories, initialize files, and structure them consistently—all from code. It felt like giving GitHub superpowers, but with full version control and repeatability.&lt;br&gt;
I wrote the automation logic in Python, and Pulumi handled the underlying complexity of API interactions with GitHub. One standout aspect was Pulumi’s resource model—it was easy to define, update, and destroy GitHub resources like repositories and files declaratively. This made experimentation and cleanup seamless.&lt;/p&gt;

&lt;p&gt;Pulumi Copilot was like pair programming with an expert who really knew the Pulumi APIs. I often needed help understanding specific API constructors and figuring out how to use them effectively in my context. Whether it was about adding files to a repo or setting repository metadata, I asked Copilot to walk me through the use cases and generate relevant code snippets.&lt;/p&gt;

&lt;p&gt;It significantly improved my development efficiency by reducing guesswork and letting me focus more on the logic of automation rather than digging through docs.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pulumichallenge</category>
      <category>github</category>
      <category>api</category>
    </item>
  </channel>
</rss>
