<?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: Archit Mittal</title>
    <description>The latest articles on DEV Community by Archit Mittal (@automate-archit).</description>
    <link>https://dev.to/automate-archit</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%2F583571%2Fc2c0b665-b457-4281-a519-d6e76245e21f.png</url>
      <title>DEV Community: Archit Mittal</title>
      <link>https://dev.to/automate-archit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/automate-archit"/>
    <language>en</language>
    <item>
      <title>Build a GST Invoice Data Extractor in 45 Lines of Python</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Mon, 10 Aug 2026 12:53:37 +0000</pubDate>
      <link>https://dev.to/automate-archit/build-a-gst-invoice-data-extractor-in-45-lines-of-python-1510</link>
      <guid>https://dev.to/automate-archit/build-a-gst-invoice-data-extractor-in-45-lines-of-python-1510</guid>
      <description>&lt;p&gt;Every accountant I know has the same Monday ritual: open 60 supplier invoices, squint at each PDF, and retype the GSTIN, invoice number, taxable value and tax split into a spreadsheet. It takes three hours. It produces typos. And it happens again next Monday.&lt;/p&gt;

&lt;p&gt;Here is a 45-line Python script that does it in about four seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;Point the script at a folder of invoice PDFs. It reads each one, pulls out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supplier &lt;strong&gt;GSTIN&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoice number&lt;/strong&gt; and &lt;strong&gt;date&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Taxable value&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CGST / SGST / IGST&lt;/strong&gt; breakup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grand total&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...and writes everything to a single CSV you can hand straight to your CA or import into Tally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pdfplumber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the only dependency. &lt;code&gt;pdfplumber&lt;/code&gt; gives us reliable text extraction with layout awareness, which matters because invoice PDFs love to put the amount three columns away from its label.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;

&lt;span class="c1"&gt;# GSTIN: 2-digit state code, 5 letters PAN, 4 digits, 1 letter, 1 alnum, 'Z', 1 checksum
&lt;/span&gt;&lt;span class="n"&gt;GSTIN&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b\d{2}[A-Z]{5}\d{4}[A-Z][A-Z\d]Z[A-Z\d]\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INV_NO&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice\s*(?:no\.?|number|#)\s*[:\-]?\s*([A-Za-z0-9/\-]{3,25})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DATE&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(\d{1,2}[/\-.]\d{1,2}[/\-.]\d{2,4})\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TAXABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxable\s*(?:value|amount)[^\d]{0,25}([\d,]+\.?\d{0,2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TAX&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(CGST|SGST|UTGST|IGST)\b\s*(?:@\s*)?(?:[\d.]+\s*%)?[^\d\n]{0,30}([\d,]+\.\d{2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TOTAL&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?:grand\s*total|total\s*(?:invoice\s*)?(?:value|amount))[^\d]{0,25}([\d,]+\.?\d{0,2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;taxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CGST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SGST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IGST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TAX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UTGST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SGST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;taxes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gstin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GSTIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_no&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INV_NO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxable_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAXABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;taxes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOTAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAIL &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Wrote &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; invoices -&amp;gt; invoices.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total tax credit: Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cgst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sgst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;igst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python gst_extract.py ~/Downloads/august-invoices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OK   AWS-INV-2026-0812.pdf
OK   RELIANCE-JIO-8871.pdf
FAIL scan_003.pdf: no extractable text
OK   ZOHO-IN-44120.pdf

Wrote 3 invoices -&amp;gt; invoices.csv
Total tax credit: Rs 41,208.60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why each regex looks like that
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The GSTIN pattern is not a guess.&lt;/strong&gt; A GSTIN is exactly 15 characters with a fixed shape: two state-code digits, the ten-character PAN (five letters, four digits, one letter), an entity number, a literal &lt;code&gt;Z&lt;/code&gt;, and a checksum character. Encoding that structure means you match real GSTINs and skip the random 15-character order IDs floating around the same page.&lt;/p&gt;

&lt;p&gt;Note &lt;code&gt;first(GSTIN, text, 0)&lt;/code&gt; uses group &lt;code&gt;0&lt;/code&gt; — the whole match — because the pattern has no capture groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;[^\d\n]{0,30}&lt;/code&gt; is the workhorse.&lt;/strong&gt; Invoice layouts scatter the label and its number across a table row. This says "allow up to 30 non-digit characters between the word CGST and the amount, but never cross a line break." Without the &lt;code&gt;\n&lt;/code&gt; exclusion you will happily grab a number from two rows down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The optional &lt;code&gt;(?:@\s*)?(?:[\d.]+\s*%)?&lt;/code&gt; is not optional in practice.&lt;/strong&gt; My first version of this regex was just &lt;code&gt;\b(CGST|...)\b[^\d\n]{0,30}(...)&lt;/code&gt; and it silently returned zero taxes on every invoice I threw at it. The reason: almost every real invoice writes the rate right after the label — &lt;code&gt;CGST @ 9%&lt;/code&gt;, &lt;code&gt;IGST 18%&lt;/code&gt; — and &lt;code&gt;9&lt;/code&gt; is a digit, so &lt;code&gt;[^\d\n]&lt;/code&gt; stopped dead before ever reaching the amount. Consuming the rate first fixes it. Test your regexes against actual invoices, not the one you imagined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taxes are summed, not assigned.&lt;/strong&gt; Multi-rate invoices list CGST three times, once per HSN slab. &lt;code&gt;findall&lt;/code&gt; plus &lt;code&gt;+=&lt;/code&gt; handles that. Single-rate invoices just add once and nothing breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UTGST folds into SGST&lt;/strong&gt; because for input-credit bookkeeping they behave identically, and keeping a fourth column that is empty 99% of the time is noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure you will hit first
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;FAIL scan_003.pdf: no extractable text&lt;/code&gt; means the PDF is a scan — a photo of paper with no text layer. &lt;code&gt;pdfplumber&lt;/code&gt; cannot read pixels. Add OCR as a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pytesseract pdf2image  &lt;span class="c"&gt;# plus: brew install tesseract poppler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pdf2image&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;convert_from_path&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytesseract&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pytesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;convert_from_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap the first two lines of &lt;code&gt;parse()&lt;/code&gt; for &lt;code&gt;text = read_text(pdf_path)&lt;/code&gt;. OCR is roughly 2 seconds per page instead of 40 milliseconds, so the length check matters — only scanned files pay that cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it actually trustworthy
&lt;/h2&gt;

&lt;p&gt;Regex extraction on invoices is right maybe 90-95% of the time, and the 5% is exactly what will burn you at filing time. One cheap guard catches most of it — arithmetic that should balance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxable_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;igst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mismatch: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;igst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;both IGST and CGST/SGST present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;row["flag"] = flag(row)&lt;/code&gt; before appending, sort the CSV by that column, and you review eight invoices by hand instead of sixty. The second check is worth keeping: IGST and CGST/SGST are mutually exclusive on a normal invoice, so seeing both almost always means the regex grabbed a number from a comparison table or a terms-and-conditions block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to take it next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schedule it.&lt;/strong&gt; A cron entry pointed at your invoice folder means the CSV is ready before you are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull from email directly.&lt;/strong&gt; &lt;code&gt;imaplib&lt;/code&gt; plus a filter on &lt;code&gt;has:attachment&lt;/code&gt; gets the PDFs into the folder without you touching anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate GSTINs offline.&lt;/strong&gt; The 15th character is a mod-36 checksum. Fifteen lines of Python catches typo'd GSTINs before the portal rejects your return.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split by supplier GSTIN&lt;/strong&gt; into separate sheets, which maps neatly onto GSTR-2B reconciliation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is 45 lines because it does one job: text in, structured rows out. That constraint is a feature — every line is readable, and when a supplier changes their invoice template you will find the regex that needs adjusting in under a minute.&lt;/p&gt;

&lt;p&gt;Three hours a month, gone.&lt;/p&gt;




&lt;p&gt;Follow me on Twitter @automate_archit for daily AI automation tips&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The ₹0 Automation Stack: Enterprise-Grade Workflows Without Paying for SaaS</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:04:38 +0000</pubDate>
      <link>https://dev.to/automate-archit/the-0-automation-stack-enterprise-grade-workflows-without-paying-for-saas-32gl</link>
      <guid>https://dev.to/automate-archit/the-0-automation-stack-enterprise-grade-workflows-without-paying-for-saas-32gl</guid>
      <description>&lt;p&gt;₹37,500 per month. That was the SaaS bill a Jaipur-based textile exporter was paying for automating invoices, GST reconciliation, and shipping notifications across three platforms. Three dashboards, three logins, three support tickets every time something broke.&lt;/p&gt;

&lt;p&gt;I replaced all of it with Python scripts running on a ₹500/month VPS. The recurring cost dropped to effectively zero — and the workflows actually became more reliable.&lt;/p&gt;

&lt;p&gt;This isn't a hypothetical framework. This is the exact stack I've deployed across 11 Indian businesses over the past 18 months, from CA firms filing ITR returns to stock traders screening 150+ equities before market open. Every tool in this stack is free. Every workflow runs in production today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Indian Businesses Overpay for Automation
&lt;/h2&gt;

&lt;p&gt;Most business owners discover automation through SaaS marketing. The pitch is compelling: drag-and-drop workflows, no coding required, instant results. What the pricing page doesn't tell you is that the "Starter" plan handles 100 tasks per month, and your GST reconciliation alone burns through that in three days.&lt;/p&gt;

&lt;p&gt;The real cost isn't the subscription — it's the upgrade treadmill. You start at ₹2,000/month, hit the task limit by week two, upgrade to ₹8,000/month, then discover that the webhook integration you need is locked behind the "Business" tier at ₹25,000/month.&lt;/p&gt;

&lt;p&gt;For businesses processing under 10,000 transactions monthly — which includes the vast majority of Indian SMBs, freelancers, and professional firms — a Python-based stack isn't just cheaper. It's more flexible, more transparent, and entirely under your control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack: Seven Layers, Zero Recurring Cost
&lt;/h2&gt;

&lt;p&gt;Here's every component of the automation stack I deploy for clients. Each layer is free, battle-tested, and replaceable without rebuilding the entire system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Layer 1 — Logic &amp;amp; Scripting: Python 3.11+&lt;/strong&gt;&lt;br&gt;
The backbone of every automation. Handles API calls, data transformation, conditional logic, and error handling. Free forever, runs anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2 — Data Processing: Pandas + OpenPyXL&lt;/strong&gt;&lt;br&gt;
Reads Excel sheets, CSVs, and bank statements. Cleans, matches, and transforms data. This single library replaces most of what businesses pay ₹10,000+/month for in data integration tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 — Database: SQLite (small) or PostgreSQL (scale)&lt;/strong&gt;&lt;br&gt;
SQLite ships with Python — zero setup. For anything above 50,000 records or multi-user access, PostgreSQL (free, open-source) handles it. Supabase offers free-tier hosted PostgreSQL if you don't want to manage a database server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 4 — Scheduling: cron + Python schedule library&lt;/strong&gt;&lt;br&gt;
cron handles time-based triggers on Linux. The Python schedule library handles interval-based logic within scripts. Together they replace paid workflow schedulers entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 5 — Notifications: Telegram Bot API + Gmail SMTP&lt;/strong&gt;&lt;br&gt;
Telegram bots are free, instant, and support rich formatting (tables, buttons, file attachments). Gmail SMTP handles email alerts up to 500/day. WhatsApp Business API is free under 1,000 messages/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 6 — Web Scraping &amp;amp; Browser Automation: Selenium + BeautifulSoup&lt;/strong&gt;&lt;br&gt;
For workflows that need to interact with websites that don't offer APIs — government portals, legacy ERP dashboards, bank statement downloads. Free, mature, well-documented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 7 — Hosting: VPS (₹500/month) or Home Server (₹0)&lt;/strong&gt;&lt;br&gt;
A basic VPS from providers like Hetzner or DigitalOcean runs the entire stack. For truly zero cost, an old laptop running Ubuntu works — I have a client whose stock screener runs on a repurposed 2018 ThinkPad.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Real Numbers: SaaS vs. ₹0 Stack
&lt;/h2&gt;

&lt;p&gt;Here's the cost comparison from three actual client deployments. These aren't projections — these are real invoices I've seen versus the cost of what replaced them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;SaaS Cost (Monthly)&lt;/th&gt;
&lt;th&gt;₹0 Stack Cost (Monthly)&lt;/th&gt;
&lt;th&gt;Annual Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Invoice processing + GST filing&lt;/td&gt;
&lt;td&gt;₹18,000&lt;/td&gt;
&lt;td&gt;₹0&lt;/td&gt;
&lt;td&gt;₹2,16,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bank reconciliation (3 accounts)&lt;/td&gt;
&lt;td&gt;₹12,000&lt;/td&gt;
&lt;td&gt;₹0&lt;/td&gt;
&lt;td&gt;₹1,44,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stock screening + WhatsApp alerts&lt;/td&gt;
&lt;td&gt;₹47,000 (advisory fee)&lt;/td&gt;
&lt;td&gt;₹0&lt;/td&gt;
&lt;td&gt;₹5,64,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily MIS report generation&lt;/td&gt;
&lt;td&gt;₹8,500&lt;/td&gt;
&lt;td&gt;₹0&lt;/td&gt;
&lt;td&gt;₹1,02,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Total annual savings across these four workflows: ₹10,26,000.&lt;/strong&gt; The one-time setup cost for all four was ₹1,45,000. Payback period: under 2 months.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Trade-Off Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;I'd be dishonest if I said this stack has no downsides. It does. The primary trade-off is setup time and maintenance ownership.&lt;/p&gt;

&lt;p&gt;A SaaS tool gives you a working automation in 20 minutes. A Python script takes 2-8 hours to build, test, and deploy. If something breaks at 2 AM, there's no support chat — you debug it yourself, or your automation consultant does.&lt;/p&gt;

&lt;p&gt;This matters. For a solopreneur who needs a quick Slack-to-email bridge, paying ₹2,000/month for a SaaS tool makes perfect sense. The ₹0 stack wins when you have workflows that are core to your business operations, when the SaaS bill has crossed ₹10,000/month, or when you need customisation that no drag-and-drop builder can handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Start: The First Workflow to Automate
&lt;/h2&gt;

&lt;p&gt;If you've never built a ₹0 automation before, start with the workflow that causes the most daily friction. For most Indian businesses, that's one of these three: bank statement download and matching, invoice data extraction from PDFs, or daily/weekly report generation from spreadsheets.&lt;/p&gt;

&lt;p&gt;Pick one. Build a Python script that handles the happy path — the 80% case where everything works normally. Add error handling and a Telegram notification for failures. Run it manually for a week to build trust. Then schedule it with cron and forget about it.&lt;/p&gt;

&lt;p&gt;That single workflow will teach you more about automation than any SaaS trial. And once it's running, it runs forever — no subscription renewal, no "your plan has been downgraded," no surprise pricing changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Reliability Question
&lt;/h3&gt;

&lt;p&gt;Skeptics ask: "Is a free stack reliable enough for production?" Fair question. My answer: a CA firm has been running Python-based ITR automation through two consecutive filing seasons — over 1,200 returns processed, zero missed deadlines. The scripts have error handling, retry logic, and instant Telegram alerts if anything fails. That's more observability than most SaaS dashboards provide.&lt;/p&gt;

&lt;p&gt;Reliability isn't about how much you pay. It's about how well you handle failures. A ₹0 script with proper logging and alerting beats a ₹20,000/month SaaS tool where failures disappear into a "contact support" black hole.&lt;/p&gt;

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

&lt;p&gt;In upcoming articles, I'll walk through specific implementations: automated bank reconciliation with Python and Pandas, stock screening bots that replace expensive advisory services, and GST reconciliation pipelines that file-ready returns without manual intervention.&lt;/p&gt;

&lt;p&gt;Every implementation will follow the same philosophy: zero recurring cost, production-grade reliability, and real rupee numbers from real Indian businesses.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>How a Weekend Python Script Saved a CA Firm 209 Hours During ITR Season</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sun, 21 Jun 2026 07:59:28 +0000</pubDate>
      <link>https://dev.to/automate-archit/how-a-weekend-python-script-saved-a-ca-firm-209-hours-during-itr-season-4k90</link>
      <guid>https://dev.to/automate-archit/how-a-weekend-python-script-saved-a-ca-firm-209-hours-during-itr-season-4k90</guid>
      <description>&lt;p&gt;₹3,12,000. That's what Rajesh Sharma's CA firm in Jaipur spent every ITR season hiring three temporary data entry operators for two months. They'd sit in a back room extracting numbers from Form 16 PDFs, cross-checking PAN details against the income tax portal, and pre-filling ITR forms — one cell at a time, one return at a time, 340 returns total.&lt;/p&gt;

&lt;p&gt;Last July, a single Python script replaced that entire process. Built in one weekend. Running cost: ₹0. Time saved across the season: 209 hours.&lt;/p&gt;

&lt;p&gt;This is not a theoretical tutorial. This is what actually happened, with real numbers, real friction, and the parts that almost went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: ITR Season Is a Two-Month Data Entry Marathon
&lt;/h2&gt;

&lt;p&gt;If you've never worked inside a CA firm during July-September, here's what it looks like. Clients send Form 16 PDFs over WhatsApp — sometimes as photos, sometimes as scanned copies with coffee stains. The firm's staff manually opens each PDF, types salary figures into a master Excel sheet, cross-references PAN numbers on the income tax portal, and fills in the ITR form fields one by one.&lt;/p&gt;

&lt;p&gt;Rajesh's firm handled 340 individual ITRs last year. Each return took an average of 38 minutes of pure data processing before a CA even looked at it for deductions and exemptions. That's 215 hours of someone staring at PDFs and typing numbers into boxes.&lt;/p&gt;

&lt;p&gt;The three temporary hires cost ₹1,04,000 each for the season — ₹3,12,000 total. And they still made errors. Rajesh's senior CA spent another 40+ hours catching and fixing data entry mistakes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Har saal yahi hota tha. Teen log hire karo, train karo, phir unki galtiyan dhundo. It felt like we were paying people to create problems we'd solve ourselves." (Every year the same thing. Hire three people, train them, then find their mistakes.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Script: What It Actually Does
&lt;/h2&gt;

&lt;p&gt;I didn't build Rajesh an AI platform. I didn't sell him a subscription. I built a Python script that does three things — and does them well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: PDF Text Extraction
&lt;/h3&gt;

&lt;p&gt;The script uses an open-source PDF library to read every Form 16 PDF in a folder. It extracts employer name, PAN, salary breakdowns (basic, HRA, special allowance), TDS deducted, and Section 80C/80D declarations. For scanned or photo PDFs — which made up about 30% of the documents — it falls back to OCR using Tesseract, another free tool.&lt;/p&gt;

&lt;p&gt;This stage alone eliminated about 70% of the manual typing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Validation and Cross-Check
&lt;/h3&gt;

&lt;p&gt;Every extracted PAN is validated against a checksum algorithm (PAN numbers follow a specific format — the script catches typos instantly). Salary totals are cross-checked: does basic + HRA + special allowance + other components equal gross salary? If not, the return gets flagged for manual review instead of silently carrying an error forward.&lt;/p&gt;

&lt;p&gt;This is where the script outperformed humans consistently. The temporary hires caught about 60% of calculation mismatches. The script catches 100% — because it doesn't get tired at 4 PM on a Thursday.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Pre-Filled Output
&lt;/h3&gt;

&lt;p&gt;The script generates a structured spreadsheet with every field mapped to the ITR form layout. Rajesh's CAs open the sheet, review the numbers, apply their professional judgment on deductions and exemptions, and file. The 38-minute-per-return data processing step dropped to about 6 minutes of review.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Numbers:&lt;/strong&gt;&lt;br&gt;
Before: 38 min/return x 340 returns = 215 hours of data processing&lt;br&gt;
After: 6 min/return x 340 returns = 34 hours of CA review&lt;br&gt;
Net saving: ~209 hours (after accounting for the 28 returns flagged for manual processing)&lt;br&gt;
Cost saving: ₹3,12,000 in seasonal hiring — replaced by a script with ₹0 running cost&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Almost Went Wrong
&lt;/h2&gt;

&lt;p&gt;I'd be lying if I said the script worked perfectly from day one. Two things nearly derailed it.&lt;/p&gt;

&lt;p&gt;The first problem was PDF variety. Indian employers don't follow a standard Form 16 template. Some PDFs had salary components in tables, others in plain text paragraphs, and a few used formats I'd never seen — with Devanagari headers mixed into English content. The first version of the script handled about 75% of PDFs correctly. I spent the second day of that weekend writing fallback extraction logic for edge cases. By the end, it handled 92% automatically — the remaining 8% (28 returns) got flagged for manual processing.&lt;/p&gt;

&lt;p&gt;The second problem was trust. Rajesh didn't trust the script's output initially — and he shouldn't have. We ran a parallel test: his staff processed the first 50 returns manually while the script processed the same 50. We compared outputs line by line. The script matched human output on 47 returns and was actually &lt;em&gt;more accurate&lt;/em&gt; on the other 3 (the staff had made small transcription errors).&lt;/p&gt;

&lt;p&gt;That parallel test took an extra day but bought something no amount of demos could: Rajesh's confidence that the numbers were right.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Jab tak maine apni aankhon se comparison nahi dekha, mujhe yakeen nahi aaya. But the numbers don't lie." (Until I saw the comparison with my own eyes, I didn't believe it. But the numbers don't lie.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond One CA Firm
&lt;/h2&gt;

&lt;p&gt;India has over 3.5 lakh practising Chartered Accountants. The vast majority of small and mid-size firms still process ITRs the way Rajesh used to — manually, with seasonal hires, under deadline pressure that leads to errors and late nights.&lt;/p&gt;

&lt;p&gt;The automation here isn't complicated. It's not an AI model that needs training data or a cloud platform that costs ₹50,000/month. It's a Python script using free libraries, running on the same laptop the CA already owns. The total infrastructure cost is zero.&lt;/p&gt;

&lt;p&gt;What it requires is someone who understands both the accounting workflow and the code — or a CA willing to work with someone who does. That intersection is where the real value lives. Not in the technology itself, but in knowing which 80% of the process is pure data movement and which 20% genuinely needs a Chartered Accountant's brain.&lt;/p&gt;

&lt;p&gt;If you're a CA firm owner reading this: you don't need to learn Python. You need to find someone who can spend a weekend understanding your Form 16 processing workflow and write a script tailored to it. The ROI pays for itself before August.&lt;/p&gt;

&lt;p&gt;And if you're a developer looking for freelance projects: ITR season starts in July. CA firms start panicking in June. That's your window. The ones who've done it manually for 20 years are the ones most ready to try something different — they just need someone to show them it works.&lt;/p&gt;

</description>
      <category>python</category>
      <category>aiops</category>
    </item>
    <item>
      <title>Claude Code vs Cursor vs Copilot: An Honest Review After 40 Production Automations</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sun, 21 Jun 2026 07:56:41 +0000</pubDate>
      <link>https://dev.to/automate-archit/claude-code-vs-cursor-vs-copilot-an-honest-review-after-40-production-automations-1oee</link>
      <guid>https://dev.to/automate-archit/claude-code-vs-cursor-vs-copilot-an-honest-review-after-40-production-automations-1oee</guid>
      <description>&lt;p&gt;₹40,000 to ₹70,000 — that's the extra monthly capacity my consulting practice has picked up since switching my primary dev loop to Claude Code six months ago. Forty production automations shipped in that window: bank reconciliation pipelines, stock screeners, ITR-prep bots, expense categorizers, GST filing helpers, a couple of trading systems I still babysit.&lt;/p&gt;

&lt;p&gt;This post is the scorecard. What Claude Code gets right, where it still breaks, and how I decide when to reach for it versus Cursor versus plain GitHub Copilot. No sponsored angle, no affiliate link. I pay for all three out of pocket. What follows is what I'd tell a friend over chai.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context — Who This Review Is For
&lt;/h2&gt;

&lt;p&gt;I'm not a web app engineer. I build finance and trading automations in Python, glue them to broker APIs and Google Sheets, and ship scripts that run on cron jobs or Railway containers. If you build similar back-end, script-first work, this review will transfer cleanly. If you're writing React all day, your mileage will differ.&lt;/p&gt;

&lt;p&gt;The bar I measure against is brutal. A client automation has to work on day one against real production data, handle every edge case a CA firm can dream up, and be debuggable by me at 11 PM when a trade doesn't fire. No tolerance for "it worked on my machine."&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Code Gets Right — The Four Wins That Actually Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Multi-file reasoning at a level nothing else touches
&lt;/h3&gt;

&lt;p&gt;Most AI coding assistants play well inside a single file. Claude Code holds an entire repo in its head. I asked it to refactor a bank-reconciliation project that spans eleven modules and 2,300 lines — it traced every call site, flagged one circular import I'd never noticed, and proposed a clean split in a single turn. Cursor starts to struggle past six or seven files. Copilot gives up around two.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It asks the right questions before writing code
&lt;/h3&gt;

&lt;p&gt;This surprised me. When I told Claude Code to "add retry logic to the Zerodha order function," it didn't just write it. It asked whether the retry should respect the broker's rate limits, what to do on partial fills, and whether idempotency keys were available. Those are the questions a senior engineer would ask. The answers shape whether the automation is safe for real money or a ticking time bomb.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Long-running tasks stay coherent
&lt;/h3&gt;

&lt;p&gt;I regularly hand Claude Code a task that takes 20-40 minutes — "refactor the GST prep pipeline, add test coverage, run the suite, fix any regressions." It plans, executes, self-corrects, and comes back with a diff that's ready to review. The agentic loop is tight. No hallucinated file paths. No code that assumes packages that don't exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It respects the code that's already there
&lt;/h3&gt;

&lt;p&gt;Copilot rewrites style aggressively. Claude Code reads the file, matches existing conventions, and produces diffs that don't feel foreign two weeks later. For a consulting practice where I hand off code to clients' in-house teams, that matters more than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude Code Still Breaks
&lt;/h2&gt;

&lt;p&gt;Honest time. Three real failure modes I've hit.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Indian fintech APIs are still weak ground
&lt;/h3&gt;

&lt;p&gt;Ask Claude Code about the Zerodha Kite Connect SDK and it gets 80% right. The last 20% — ticker formats for F&amp;amp;O, post-2024 margin changes, specific error codes — is where it hallucinates plausibly. I always double-check anything broker-specific against the official docs. For one client I caught it confidently using a parameter name that was deprecated nine months ago. The test suite caught it before production did. Barely.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Long debug sessions can loop
&lt;/h3&gt;

&lt;p&gt;If the first three fixes don't resolve an issue, it sometimes cycles — tries approach A, moves to B, comes back to A wrapped in a helper. I've learned to stop the loop manually, paste the stack trace into a fresh context, and state the constraint explicitly ("do not change the signature of X"). The second attempt almost always lands.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cost at scale needs discipline
&lt;/h3&gt;

&lt;p&gt;Unconstrained, a single long agentic task can burn through tokens fast. For a big refactor I'll sometimes spend the equivalent of a cheap dinner in one session. That's still trivial against the time saved, but only if you're measuring. The techniques in how I cut a client's AI API bill from ₹85K to ₹12K/month apply here too — set bounded budgets, use smaller models for simple sub-tasks, and don't let agentic loops run without a ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code vs Cursor vs Copilot — When I Use Each
&lt;/h2&gt;

&lt;p&gt;Simple rule of thumb, from six months of real use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task Type&lt;/th&gt;
&lt;th&gt;My Pick&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New automation from scratch&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Plans across files, asks clarifying questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single-file editing at speed&lt;/td&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Faster inline completions, tight editor loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boilerplate-heavy typing&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;Cheapest, good at the obvious stuff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large refactor across 10+ files&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Nothing else holds this much context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Working in a legacy repo I don't know&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Reads and understands before writing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pair-programming over screen-share&lt;/td&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Inline suggestions are less disruptive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three stay installed. Picking the right one is like picking the right screwdriver — the mistake is treating one tool as the answer to every problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rupee Impact — What 40 Automations Looks Like in Numbers
&lt;/h2&gt;

&lt;p&gt;A summary from my consulting log since switching:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build time:&lt;/strong&gt; 38% lower than my pre-Claude baseline on average.&lt;br&gt;
&lt;strong&gt;Client revisions:&lt;/strong&gt; Down by roughly half — better first drafts mean fewer "can you also..." cycles.&lt;br&gt;
&lt;strong&gt;Debugging time:&lt;/strong&gt; Up slightly, because I now take on projects I wouldn't have before.&lt;br&gt;
&lt;strong&gt;Capacity:&lt;/strong&gt; ₹40,000-₹70,000 of extra billable capacity per month. Over six months, a real second income line without hiring anyone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Non-Obvious Tip Most Teams Miss
&lt;/h2&gt;

&lt;p&gt;The setting that changed my workflow most: &lt;strong&gt;a CLAUDE.md file at the root of every project.&lt;/strong&gt; Claude Code reads it automatically. I put project context, naming conventions, the specific broker API version we're using, and our testing rules. It's the difference between getting a generic Python suggestion and getting a suggestion that knows we use Pandas 2.2, pytest, and a specific way of mocking Kite Connect.&lt;/p&gt;

&lt;p&gt;If you skip this step, you're using maybe 60% of the tool. I've seen teams adopt Claude Code, get indifferent results, and blame the model. Nine times out of ten, the CLAUDE.md was missing or empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Switch?
&lt;/h2&gt;

&lt;p&gt;If you're already on Cursor and happy, don't rip it out. Layer Claude Code on top for planning and large refactors and see how the split feels for a month. If you're still on Copilot-only and doing anything more complex than boilerplate, the jump to Claude Code is the single highest-ROI tooling change I've made as an automation consultant this year.&lt;/p&gt;

&lt;p&gt;"Jo tool sahi kaam karta hai, wohi chuno." (Pick the tool that does the actual job.)&lt;/p&gt;

&lt;p&gt;Not everything should be automated, and not every process should be shipped with an AI pair programmer either. For production automations where quality and context matter more than keystroke speed, Claude Code is the best tool I've used. The ceiling is high. The floor is higher than any other assistant I've tried.&lt;/p&gt;

&lt;p&gt;Which AI coding tool are you reaching for most right now — and for what kind of work?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>The CFO's AI Playbook: 5 Finance Automations Every Indian Business Should Run in 2026</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sun, 21 Jun 2026 07:53:49 +0000</pubDate>
      <link>https://dev.to/automate-archit/the-cfos-ai-playbook-5-finance-automations-every-indian-business-should-run-in-2026-2ld8</link>
      <guid>https://dev.to/automate-archit/the-cfos-ai-playbook-5-finance-automations-every-indian-business-should-run-in-2026-2ld8</guid>
      <description>&lt;p&gt;Over 60% of APAC finance leaders say AI-led automation is their top priority for 2026. For Indian businesses, that stat hides a quieter truth: most SMBs have no idea &lt;em&gt;which&lt;/em&gt; automation to start with.&lt;/p&gt;

&lt;p&gt;I've built finance automations for CA firms, D2C brands, trading desks, family-run manufacturers, and a few fintech startups. The pattern is always the same. Five finance processes eat the most hours, hide the most errors, and respond best to a simple Python layer on top of whatever ledger you already use.&lt;/p&gt;

&lt;p&gt;This is the playbook. No enterprise suite. No subscriptions you don't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Bank Reconciliation — The Single Biggest Time Sink
&lt;/h2&gt;

&lt;p&gt;Every finance team I meet has the same nightmare. Statements from three or four banks. Tally or Zoho on the other side. An Excel sheet in the middle. Eight hours a month — sometimes more — matching rows.&lt;/p&gt;

&lt;p&gt;We replaced it with a Python script that pulls statements from email attachments, categorizes transactions using keyword rules, cross-references entries with Tally, and flags only the mismatches. Eight hours dropped to fifteen minutes of review.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Tu 2 saal pehle kyu nahi mila?" (Why didn't I meet you two years ago?)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Cash Application — Matching Payments to Invoices
&lt;/h2&gt;

&lt;p&gt;In India, money arrives in more shapes than most tools expect: UPI, NEFT, RTGS, IMPS, cheques, partial payments, grouped settlements where one transfer covers four invoices.&lt;/p&gt;

&lt;p&gt;I build a three-layer pipeline. Layer one parses payment references. Layer two tries deterministic matches. Layer three hands ambiguous ones to a lightweight AI model that suggests matches with a confidence score.&lt;/p&gt;

&lt;p&gt;A D2C brand went from a seven-day receivables gap to same-day application. Their working capital position shifted by roughly ₹14 lakhs.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Real-Time P&amp;amp;L Reporting
&lt;/h2&gt;

&lt;p&gt;Most Indian SMB founders see their P&amp;amp;L twenty days after the month ends. A scheduled Python script pulls trial balance data overnight, categorizes new entries, and renders a dashboard with revenue, gross margin, operating expenses and EBITDA as of yesterday.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You stop making capital decisions on twenty-day-old data. For a business doing ₹30L/month, a single well-timed cut based on real-time numbers can move EBITDA by 2-3 percentage points.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. GST Filing Prep
&lt;/h2&gt;

&lt;p&gt;GSTR-1, GSTR-3B, and the quarterly reconciliations. I build GST prep bots that run on the 28th of every month. They pull sales and purchase data, compute filing figures, pull vendor GSTR-2A data via the GSTN API, flag reconciliation mismatches, and produce a filing-ready summary.&lt;/p&gt;

&lt;p&gt;A small manufacturing client went from three anxious days a month to a two-hour review window.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Expense Categorization
&lt;/h2&gt;

&lt;p&gt;Miscategorized expenses are a silent killer in Indian SMB P&amp;amp;Ls. Expense categorization automation uses an AI model trained on your own chart of accounts. High-confidence entries auto-categorize. The ambiguous 10-15% goes to a review queue.&lt;/p&gt;

&lt;p&gt;For a client with ~800 expense entries a month, this shifted their finance lead's time from 6 hours of categorization to 45 minutes of review. Accuracy went from ~82% to 97%.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Five Automations at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Automation&lt;/th&gt;
&lt;th&gt;Time Saved / Month&lt;/th&gt;
&lt;th&gt;Typical Rollout&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bank reconciliation&lt;/td&gt;
&lt;td&gt;6-10 hours&lt;/td&gt;
&lt;td&gt;2-4 weekends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash application&lt;/td&gt;
&lt;td&gt;15-25 hours&lt;/td&gt;
&lt;td&gt;3-6 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time P&amp;amp;L&lt;/td&gt;
&lt;td&gt;8-12 hours + faster decisions&lt;/td&gt;
&lt;td&gt;1-2 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GST filing prep&lt;/td&gt;
&lt;td&gt;12-20 hours&lt;/td&gt;
&lt;td&gt;3-5 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expense categorization&lt;/td&gt;
&lt;td&gt;4-8 hours&lt;/td&gt;
&lt;td&gt;1-2 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Stack all five and you're recovering 45-75 hours of skilled finance time every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Start
&lt;/h2&gt;

&lt;p&gt;Do not try to build all five at once. Start with bank reconciliation — bounded, high-ROI, and an immediate win. Once it's running reliably for two months, pick the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Jo kaam AI se ho sakta hai, AI kare. Jo judgment se hota hai, woh humans ke paas rahe." (Let AI do what AI can do. Let judgment stay with humans.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which of these five is eating the most hours in your finance team right now?&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stock Screener Automation: How a Trader Replaced ₹47K/Month Advisory with a Python Script</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sun, 21 Jun 2026 07:46:15 +0000</pubDate>
      <link>https://dev.to/automate-archit/stock-screener-automation-how-a-trader-replaced-47kmonth-advisory-with-a-python-script-43bn</link>
      <guid>https://dev.to/automate-archit/stock-screener-automation-how-a-trader-replaced-47kmonth-advisory-with-a-python-script-43bn</guid>
      <description>&lt;p&gt;₹47,000 every month. That's what my friend Rohit was paying a fund advisory service to tell him which stocks matched his criteria. Six filters — RSI below 40, volume spike above 200% of 20-day average, 200-DMA crossover, sector rotation signals, delivery percentage above 60%, and promoter holding changes in the last quarter.&lt;/p&gt;

&lt;p&gt;One Sunday afternoon, a Python script, and a free broker API later — Rohit doesn't pay that ₹47,000 anymore. "Bhai, pehle 2 ghante screening mein jaate the. Ab chai peete peete list aa jaati hai."&lt;/p&gt;

&lt;p&gt;This is the story of how a retail trader replaced an expensive advisory with a script that costs literally nothing to run — and why this pattern applies to far more than just stock screening.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: 2 Hours Every Morning, Staring at Screens
&lt;/h2&gt;

&lt;p&gt;Rohit is a swing trader based in Pune. He's been trading for seven years, and his edge has always been discipline — he never enters a trade without all six of his filters confirming. The problem was execution speed. Every morning before market open, he'd manually check 150+ stocks across three different screening tools, cross-reference the results, and build his watchlist.&lt;/p&gt;

&lt;p&gt;Two hours. Every single trading day. That's roughly 500 hours a year spent on a task that follows the exact same logic every time.&lt;/p&gt;

&lt;p&gt;The advisory service he subscribed to ran similar filters, but their shortlist arrived at 9:30 AM — 15 minutes after market open. By then, the best entry prices on momentum stocks were already gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A Python Script That Runs Before You Wake Up
&lt;/h2&gt;

&lt;p&gt;The script connects to Rohit's broker API at 9:15 AM — one minute before market opens. It pulls the previous day's closing data for all NSE-listed stocks (roughly 2,000 symbols). Then it runs six filters in sequence.&lt;/p&gt;

&lt;p&gt;The entire filtering process takes about 12 seconds. By 9:16 AM, Rohit receives a WhatsApp message with a shortlist of 5-12 stocks that passed all six filters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The numbers that matter:&lt;/strong&gt; 2 hours/day reduced to 0 active time. ₹47,000/month advisory cancelled. Total recurring cost: ₹0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why This Works Better Than an Advisory Service
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; An automated script delivers results in seconds. A team of analysts takes 30-45 minutes. In momentum trading, those minutes are the difference between a 2% gain and a missed entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customisation.&lt;/strong&gt; Rohit's six filters are specific to his trading style. The advisory service overlapped about 70% with what he wanted. That remaining 30% mismatch meant he was still doing manual screening on top of the advisory's shortlist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adaptability.&lt;/strong&gt; When Rohit wanted to add a seventh filter last month (institutional buying from bulk deal data), it was live by Saturday evening. Try asking an advisory service to customise their methodology for one subscriber.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Breakdown: ₹5.64L/Year Saved
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Advisory Service&lt;/th&gt;
&lt;th&gt;Python Script&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;₹47,000&lt;/td&gt;
&lt;td&gt;₹0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Annual cost&lt;/td&gt;
&lt;td&gt;₹5,64,000&lt;/td&gt;
&lt;td&gt;₹2,000 (one-time API fee)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery time&lt;/td&gt;
&lt;td&gt;9:30 AM (15 min after open)&lt;/td&gt;
&lt;td&gt;9:16 AM (before open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customisation&lt;/td&gt;
&lt;td&gt;Fixed filters&lt;/td&gt;
&lt;td&gt;Fully custom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual screening still needed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What You Actually Need to Build This
&lt;/h2&gt;

&lt;p&gt;You need Python 3.x with Pandas, a broker API account (Kite Connect from Zerodha at ₹2,000 one-time, or Fyers API v3 which is completely free), a server or always-on machine, and the WhatsApp Business API for notifications.&lt;/p&gt;

&lt;p&gt;If you've ever written an IF statement in Excel, you can understand the logic behind a stock screener script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Lesson
&lt;/h2&gt;

&lt;p&gt;Rohit's story isn't really about stock screening. It's about a pattern: &lt;strong&gt;the most expensive services are often the ones running the simplest logic on publicly available data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The sweet spot for automation is repetitive tasks with clear rules and expensive human execution. Before you automate anything, ask yourself: Is the logic explicit enough to explain to a 10-year-old? Does the task repeat on a predictable schedule? Is the current cost of doing it manually more than the one-time cost of building the script?&lt;/p&gt;

&lt;p&gt;If all three answers are yes, you've found your next automation project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened After
&lt;/h2&gt;

&lt;p&gt;Six months in, Rohit's trading performance improved — not because the script picked better stocks, but because it freed his morning for what actually matters: reading quarterly results, analysing management commentary, and making judgment calls.&lt;/p&gt;

&lt;p&gt;That's what good automation does. It doesn't replace your expertise. It removes the mechanical layer so you can spend 100% of your time on the work that actually moves the needle.&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>automation</category>
      <category>finance</category>
    </item>
    <item>
      <title>Why I Tell Clients NOT to Automate Everything — The Over-Automation Trap</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sun, 21 Jun 2026 07:44:43 +0000</pubDate>
      <link>https://dev.to/automate-archit/why-i-tell-clients-not-to-automate-everything-the-over-automation-trap-2p17</link>
      <guid>https://dev.to/automate-archit/why-i-tell-clients-not-to-automate-everything-the-over-automation-trap-2p17</guid>
      <description>&lt;p&gt;₹1,20,000. That's how much a client spent building an automated follow-up system I had to tell him to rip out thirty days later.&lt;/p&gt;

&lt;p&gt;I'm an automation consultant. My entire business depends on people wanting to automate things. And yet, the most valuable advice I give — the advice clients actually remember — is telling them what &lt;em&gt;not&lt;/em&gt; to automate.&lt;/p&gt;

&lt;p&gt;This might sound counterintuitive. But after building automation systems for CA firms, traders, e-commerce brands, and service businesses across India, I've developed a rule: &lt;strong&gt;the best automation experts say "no" more often than they say "yes."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The ₹1.2 Lakh Mistake That Changed How I Consult
&lt;/h2&gt;

&lt;p&gt;A client ran a high-ticket consulting practice. His close rate was exceptional — north of 40% — because every prospect felt personally attended to. He'd send WhatsApp voice notes, check in on their business unprompted, and remember details from conversations months earlier.&lt;/p&gt;

&lt;p&gt;He came to me wanting to "scale" his follow-ups. We built a 4-touchpoint automated sequence: personalised messages triggered by pipeline stage, scheduled over 14 days. It was technically beautiful.&lt;/p&gt;

&lt;p&gt;His conversion rate dropped 40% in the first month.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Bhai, mere automation ne tera business slow kar diya." That's what I had to tell him. My automation slowed down his business.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem wasn't the technology. The problem was the assumption. His prospects weren't buying his service — they were buying &lt;em&gt;him&lt;/em&gt;. The personal touch wasn't a nice-to-have; it was the entire value proposition.&lt;/p&gt;

&lt;p&gt;We ripped it out. Went back to manual follow-ups. Conversions recovered within two weeks.&lt;/p&gt;

&lt;p&gt;That experience taught me something I now share with every new client: &lt;strong&gt;before you automate any customer-facing process, ask one question — "Does my customer want speed, or do they want ME?"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Processes You Should Never Automate
&lt;/h2&gt;

&lt;p&gt;After making this mistake and watching dozens of other businesses make similar ones, I've identified four categories of work where automation consistently backfires.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Relationship-Driven Interactions
&lt;/h3&gt;

&lt;p&gt;If your customer is paying a premium because they trust &lt;em&gt;you&lt;/em&gt; personally, automating your communication tells them they aren't worth your time. This applies to consulting, wealth management, legal advisory, and any high-ticket service where the relationship IS the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Processes That Change Every Quarter
&lt;/h3&gt;

&lt;p&gt;Automation works best on stable, repeatable workflows. If your process shifts every few months — because of regulatory changes, seasonal cycles, or evolving business models — you'll spend more time maintaining the automation than doing it manually.&lt;/p&gt;

&lt;p&gt;I had a client in the import-export space whose customs documentation process changed with every new trade circular. We built an automation. It broke within six weeks. We rebuilt it. It broke again. Eventually, the honest answer was: this process isn't stable enough to automate profitably.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. High-Exception Workflows
&lt;/h3&gt;

&lt;p&gt;Some workflows look simple on a whiteboard but have fifty edge cases in practice. ITR filing, for instance, has a core process that's highly automatable — but the exceptions require human judgment. The trick is automating the 80% that's mechanical and leaving the 20% for human expertise.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. When Maintenance Costs Exceed Manual Costs
&lt;/h3&gt;

&lt;p&gt;Every automation system has a maintenance burden. I use a simple calculation: if the annual maintenance cost is more than 60% of the manual effort it replaces, I advise against building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The STAR Framework
&lt;/h2&gt;

&lt;p&gt;Before greenlighting any automation project, I run it through the STAR test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stable:&lt;/strong&gt; Has the process been the same for at least 6 months? If not, wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transferable:&lt;/strong&gt; Can you explain the process to a new hire in under 30 minutes with zero ambiguity? If not, it has hidden complexity that will haunt the automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arithmetic:&lt;/strong&gt; Does the math work? Calculate: (hours saved × hourly rate) minus (build cost amortized over 12 months + monthly maintenance). If negative, don't build it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replaceable:&lt;/strong&gt; Is the human element genuinely replaceable, or is it the reason the customer pays? If a bot can do it without the customer noticing — automate away. If they'd notice and care — stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Skill Is Knowing Where to Draw the Line
&lt;/h2&gt;

&lt;p&gt;The automation industry has a bias problem. Every tool vendor, every course, every consultant has an incentive to say "yes, automate it." The revenue model rewards building, not advising against building.&lt;/p&gt;

&lt;p&gt;But the consultants who build lasting client relationships are the ones who occasionally say: "Don't automate this. Here's why. Here's what to do instead."&lt;/p&gt;

&lt;p&gt;The best automation isn't about replacing everything. It's about knowing which 80% to automate so you can be 100% present for the 20% that actually matters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Jo har cheez automate karta hai, wo kisi cheez mein mahir nahi hota." — The one who automates everything masters nothing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next time you're tempted to automate a process, run it through the STAR test first. You might save yourself ₹1.2 lakh and a very awkward phone call to your client.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>strategy</category>
      <category>consulting</category>
      <category>python</category>
    </item>
    <item>
      <title>Build a GST Invoice Parser in 70 Lines of Python</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 03:41:45 +0000</pubDate>
      <link>https://dev.to/automate-archit/build-a-gst-invoice-parser-in-70-lines-of-python-h50</link>
      <guid>https://dev.to/automate-archit/build-a-gst-invoice-parser-in-70-lines-of-python-h50</guid>
      <description>&lt;p&gt;If you run a small business in India, you know the monthly ritual: a folder full of vendor invoice PDFs, and a spreadsheet that needs GSTIN, invoice number, date, taxable value, and GST amounts pulled out of every single one. Doing it by hand takes hours. Let's automate it in about 70 lines of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;A script that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scans a folder of invoice PDFs&lt;/li&gt;
&lt;li&gt;Extracts GSTIN, invoice number, date, taxable value, CGST/SGST/IGST, and total&lt;/li&gt;
&lt;li&gt;Validates the GSTIN format (including the checksum-style structure)&lt;/li&gt;
&lt;li&gt;Writes everything to a clean CSV you can import into Tally or Excel&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pdfplumber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the only dependency. &lt;code&gt;pdfplumber&lt;/code&gt; handles text extraction from digitally generated PDFs (which most GST invoices are).&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;

&lt;span class="c1"&gt;# GSTIN: 2-digit state code + 10-char PAN + entity code + 'Z' + check char
&lt;/span&gt;&lt;span class="n"&gt;GSTIN_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b\d{2}[A-Z]{5}\d{4}[A-Z]\d[A-Z]\d\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INV_NO_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?:Invoice\s*(?:No|Number|#)\.?\s*[:\-]?\s*)([A-Z0-9\/\-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DATE_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4})\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AMOUNT_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?:\u20b9|Rs\.?|INR)\s*([\d,]+\.?\d*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;LABELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxable\s*(?:value|amount)\s*[:\-]?\s*(?:\u20b9|Rs\.?|INR)?\s*([\d,]+\.?\d*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CGST[^\d]*([\d,]+\.\d{2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SGST[^\d]*([\d,]+\.\d{2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;igst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IGST[^\d]*([\d,]+\.\d{2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?:grand\s*total|total\s*amount|amount\s*payable)\s*[:\-]?\s*(?:\u20b9|Rs\.?|INR)?\s*([\d,]+\.?\d*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;STATE_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;valid_gstin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gstin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GSTIN_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gstin&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;gstin&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;STATE_CODES&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;gstins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;GSTIN_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;valid_gstin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;inv_no&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INV_NO_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor_gstin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gstins&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gstins&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NOT FOUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_no&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv_no&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inv_no&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NOT FOUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NOT FOUND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LABELS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;

    &lt;span class="c1"&gt;# Sanity check: taxable + taxes should roughly equal total
&lt;/span&gt;    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sgst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;igst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pdfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No PDFs found in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;parse_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdfs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices_extracted.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parsed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; invoices -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;flagged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warning: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flagged&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; invoice(s) have tax totals that don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t add up — check manually.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python gst_parser.py ~/Downloads/vendor-invoices/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parsed 23 invoices -&amp;gt; invoices_extracted.csv
Warning: 2 invoice(s) have tax totals that don't add up — check manually.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That mismatch flag is my favourite part. It catches data-entry errors on the vendor's side &lt;em&gt;and&lt;/em&gt; extraction failures on ours — anything where taxable value + CGST + SGST + IGST doesn't land within ₹1 of the stated total gets flagged for a human look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why regex and not AI?
&lt;/h2&gt;

&lt;p&gt;For digitally generated invoices, regex is faster, free, and deterministic. A month of invoices parses in under two seconds with zero API costs. Where this approach breaks down is scanned/photographed invoices — for those, add an OCR step with &lt;code&gt;pytesseract&lt;/code&gt; before the regex pass, or hand the text to an LLM for extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three upgrades worth making
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vendor name capture.&lt;/strong&gt; Most invoices put the vendor name within a couple of lines of the GSTIN. Grab the surrounding lines and you can auto-fill the vendor column too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GSTIN verification.&lt;/strong&gt; The format check above catches typos, but the government's GST portal can confirm a GSTIN is actually registered. Useful before claiming input tax credit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HSN code extraction.&lt;/strong&gt; If you need item-level detail for GSTR filings, &lt;code&gt;pdfplumber&lt;/code&gt;'s &lt;code&gt;extract_tables()&lt;/code&gt; handles the line-item tables most invoice formats use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Seventy lines, one dependency, and the monthly invoice grind becomes a two-second script run. The same pattern — extract text, regex the fields you care about, validate, write CSV — works for bank statements, salary slips, and purchase orders too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow me on Twitter @automate_archit for daily AI automation tips.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build a GST Invoice Generator in 87 Lines of Python</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 03:40:49 +0000</pubDate>
      <link>https://dev.to/automate-archit/build-a-gst-invoice-generator-in-87-lines-of-python-106m</link>
      <guid>https://dev.to/automate-archit/build-a-gst-invoice-generator-in-87-lines-of-python-106m</guid>
      <description>&lt;p&gt;If you freelance or run a small business in India, you have probably paid for invoicing software that does one thing: multiply numbers and put them in a PDF. Today we will build that ourselves — a GST-compliant tax invoice generator that reads line items from a CSV and produces a clean PDF, in 87 lines of Python.&lt;/p&gt;

&lt;p&gt;It handles the part everyone gets wrong: the CGST/SGST vs IGST split. Intra-state sales split the tax into equal CGST and SGST halves; inter-state sales charge a single IGST. Our script decides automatically by comparing the first two digits of the buyer's and seller's GSTINs (those digits are the state code).&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;p&gt;One dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fpdf2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fpdf2&lt;/code&gt; is a maintained, pure-Python PDF library — no system packages, works the same on Windows, Mac, and Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  The input format
&lt;/h2&gt;

&lt;p&gt;Keep your line items in a plain CSV. Anyone on your team can edit this in Excel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;description,hsn,qty,rate,gst_pct
Website development,998314,1,45000,18
Annual hosting,998315,1,12000,18
SSL certificate,998316,2,1500,18
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The full code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;GST invoice generator — reads line items from CSV, outputs a PDF invoice.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FPDF&lt;/span&gt;

&lt;span class="n"&gt;SELLER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme Tech Services Pvt Ltd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;221 MG Road, Bengaluru, KA 560001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gstin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;29ABCDE1234F1Z5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;29&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hsn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hsn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gst_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gst_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_totals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interstate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gst_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;taxable&lt;/span&gt;
        &lt;span class="n"&gt;tax_total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interstate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FPDF&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TAX INVOICE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SELLER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | GSTIN: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SELLER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gstin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SELLER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice date: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  |  Bill to: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (GSTIN: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gstin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;widths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HSN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate (Rs)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GST %&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Amount (Rs)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hsn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gst_pct&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;taxable&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Taxable value: Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;interstate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IGST: Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tax_total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CGST: Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tax_total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;   SGST: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                 &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tax_total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Grand total: Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMARGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;csv_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;buyer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Globex Retail LLP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gstin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;27PQRSX5678G1Z3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;interstate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gstin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;SELLER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_totals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;load_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;interstate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;build_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interstate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf written - grand total Rs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax_total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python gst_invoice.py items.csv
&lt;span class="c"&gt;# invoice.pdf written - grand total Rs 70,800.00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Loading (&lt;code&gt;load_items&lt;/code&gt;).&lt;/strong&gt; &lt;code&gt;csv.DictReader&lt;/code&gt; turns each CSV row into a dict keyed by the header row, so the code reads like the spreadsheet. We cast &lt;code&gt;qty&lt;/code&gt;, &lt;code&gt;rate&lt;/code&gt;, and &lt;code&gt;gst_pct&lt;/code&gt; to floats up front so the math functions never deal with strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tax math (&lt;code&gt;compute_totals&lt;/code&gt;).&lt;/strong&gt; Each line's taxable value is &lt;code&gt;qty x rate&lt;/code&gt;, and tax is a simple percentage of that. We accumulate a subtotal and a tax total and keep the per-row figures for the PDF table. The interstate decision happens once, in the entrypoint: &lt;code&gt;buyer GSTIN state code != seller state code&lt;/code&gt; means IGST applies. In our example the buyer's GSTIN starts with &lt;code&gt;27&lt;/code&gt; (Maharashtra) and the seller's with &lt;code&gt;29&lt;/code&gt; (Karnataka), so the invoice shows a single IGST line. Change the buyer's GSTIN to start with &lt;code&gt;29&lt;/code&gt; and you will see it split into equal CGST and SGST halves instead — no other change needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The PDF (&lt;code&gt;build_pdf&lt;/code&gt;).&lt;/strong&gt; &lt;code&gt;fpdf2&lt;/code&gt;'s &lt;code&gt;cell()&lt;/code&gt; API is old-school but predictable: each cell has a fixed width, &lt;code&gt;border=1&lt;/code&gt; draws the table lines, and &lt;code&gt;new_y="NEXT"&lt;/code&gt; moves the cursor down a row. The &lt;code&gt;widths&lt;/code&gt; tuple is the entire layout — adjust six numbers and the whole table reflows. Total layout code: about 40 lines, and you own every pixel of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending it
&lt;/h2&gt;

&lt;p&gt;A few upgrades that each take only a handful of lines: pull &lt;code&gt;SELLER&lt;/code&gt; and the buyer from a JSON config instead of hardcoding; add an invoice number that auto-increments from a counter file; loop over a folder of CSVs to batch-generate a month of invoices; or add &lt;code&gt;pdf.image("logo.png", x=10, y=8, w=30)&lt;/code&gt; for letterhead branding.&lt;/p&gt;

&lt;p&gt;One honest caveat: this covers the common single-rate service invoice. If you deal with cess, reverse charge, or e-invoicing (IRN/QR codes), those are regimes of their own — treat this as the foundation, not a compliance department.&lt;/p&gt;

&lt;p&gt;That's a working invoice pipeline in 87 lines — CSV in, compliant PDF out, and the tax split handled correctly without a subscription.&lt;/p&gt;

&lt;p&gt;Follow me on Twitter @automate_archit for daily AI automation tips.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code vs Cursor vs Copilot: An Honest Review After 40 Production Automations</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 03:39:36 +0000</pubDate>
      <link>https://dev.to/automate-archit/claude-code-vs-cursor-vs-copilot-an-honest-review-after-40-production-automations-516m</link>
      <guid>https://dev.to/automate-archit/claude-code-vs-cursor-vs-copilot-an-honest-review-after-40-production-automations-516m</guid>
      <description>&lt;p&gt;₹40,000 to ₹70,000 — that's the extra monthly capacity my consulting practice has picked up since switching my primary dev loop to Claude Code six months ago. Forty production automations shipped in that window: bank reconciliation pipelines, stock screeners, ITR-prep bots, expense categorizers, GST filing helpers, a couple of trading systems I still babysit.&lt;/p&gt;

&lt;p&gt;This post is the scorecard. What Claude Code gets right, where it still breaks, and how I decide when to reach for it versus Cursor versus plain GitHub Copilot. No sponsored angle, no affiliate link. I pay for all three out of pocket. What follows is what I'd tell a friend over chai.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context — Who This Review Is For
&lt;/h2&gt;

&lt;p&gt;I'm not a web app engineer. I build finance and trading automations in Python, glue them to broker APIs and Google Sheets, and ship scripts that run on cron jobs or Railway containers. If you build similar back-end, script-first work, this review will transfer cleanly. If you're writing React all day, your mileage will differ.&lt;/p&gt;

&lt;p&gt;The bar I measure against is brutal. A client automation has to work on day one against real production data, handle every edge case a CA firm can dream up, and be debuggable by me at 11 PM when a trade doesn't fire. No tolerance for "it worked on my machine."&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Code Gets Right — The Four Wins That Actually Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Multi-file reasoning at a level nothing else touches
&lt;/h3&gt;

&lt;p&gt;Most AI coding assistants play well inside a single file. Claude Code holds an entire repo in its head. I asked it to refactor a bank-reconciliation project that spans eleven modules and 2,300 lines — it traced every call site, flagged one circular import I'd never noticed, and proposed a clean split in a single turn. Cursor starts to struggle past six or seven files. Copilot gives up around two.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It asks the right questions before writing code
&lt;/h3&gt;

&lt;p&gt;This surprised me. When I told Claude Code to "add retry logic to the Zerodha order function," it didn't just write it. It asked whether the retry should respect the broker's rate limits, what to do on partial fills, and whether idempotency keys were available. Those are the questions a senior engineer would ask. The answers shape whether the automation is safe for real money or a ticking time bomb.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Long-running tasks stay coherent
&lt;/h3&gt;

&lt;p&gt;I regularly hand Claude Code a task that takes 20-40 minutes — "refactor the GST prep pipeline, add test coverage, run the suite, fix any regressions." It plans, executes, self-corrects, and comes back with a diff that's ready to review. The agentic loop is tight. No hallucinated file paths. No code that assumes packages that don't exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It respects the code that's already there
&lt;/h3&gt;

&lt;p&gt;Copilot rewrites style aggressively. Claude Code reads the file, matches existing conventions, and produces diffs that don't feel foreign two weeks later. For a consulting practice where I hand off code to clients' in-house teams, that matters more than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude Code Still Breaks
&lt;/h2&gt;

&lt;p&gt;Honest time. Three real failure modes I've hit.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Indian fintech APIs are still weak ground
&lt;/h3&gt;

&lt;p&gt;Ask Claude Code about the Zerodha Kite Connect SDK and it gets 80% right. The last 20% — ticker formats for F&amp;amp;O, post-2024 margin changes, specific error codes — is where it hallucinates plausibly. I always double-check anything broker-specific against the official docs. For one client I caught it confidently using a parameter name that was deprecated nine months ago. The test suite caught it before production did. Barely.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Long debug sessions can loop
&lt;/h3&gt;

&lt;p&gt;If the first three fixes don't resolve an issue, it sometimes cycles — tries approach A, moves to B, comes back to A wrapped in a helper. I've learned to stop the loop manually, paste the stack trace into a fresh context, and state the constraint explicitly ("do not change the signature of X"). The second attempt almost always lands.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cost at scale needs discipline
&lt;/h3&gt;

&lt;p&gt;Unconstrained, a single long agentic task can burn through tokens fast. For a big refactor I'll sometimes spend the equivalent of a cheap dinner in one session. That's still trivial against the time saved, but only if you're measuring. The techniques in &lt;a href="https://architmittal.com/blog/ai-api-cost-optimization-85k-to-12k" rel="noopener noreferrer"&gt;how I cut a client's AI API bill from ₹85K to ₹12K/month&lt;/a&gt; apply here too — set bounded budgets, use smaller models for simple sub-tasks, and don't let agentic loops run without a ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code vs Cursor vs Copilot — When I Use Each
&lt;/h2&gt;

&lt;p&gt;Simple rule of thumb, from six months of real use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task Type&lt;/th&gt;
&lt;th&gt;My Pick&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New automation from scratch&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Plans across files, asks clarifying questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single-file editing at speed&lt;/td&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Faster inline completions, tight editor loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boilerplate-heavy typing&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;Cheapest, good at the obvious stuff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large refactor across 10+ files&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Nothing else holds this much context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Working in a legacy repo I don't know&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Reads and understands before writing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pair-programming over screen-share&lt;/td&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Inline suggestions are less disruptive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three stay installed. Picking the right one is like picking the right screwdriver — the mistake is treating one tool as the answer to every problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rupee Impact — What 40 Automations Looks Like in Numbers
&lt;/h2&gt;

&lt;p&gt;A summary from my consulting log since switching:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build time:&lt;/strong&gt; 38% lower than my pre-Claude baseline on average.&lt;br&gt;
&lt;strong&gt;Client revisions:&lt;/strong&gt; Down by roughly half — better first drafts mean fewer "can you also..." cycles.&lt;br&gt;
&lt;strong&gt;Debugging time:&lt;/strong&gt; Up slightly, because I now take on projects I wouldn't have before.&lt;br&gt;
&lt;strong&gt;Capacity:&lt;/strong&gt; ₹40,000-₹70,000 of extra billable capacity per month. Over six months, a real second income line without hiring anyone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same tooling shift that helped me ship a &lt;a href="https://architmittal.com/blog/weekend-python-script-ca-firm-209-hours-itr" rel="noopener noreferrer"&gt;weekend Python script that saved a CA firm 209 hours during ITR season&lt;/a&gt; is now the default for every new project — from the &lt;a href="https://architmittal.com/blog/stock-screener-automation-trader-replaced-47k-advisory-python" rel="noopener noreferrer"&gt;stock screener that replaced a ₹47K/month advisory&lt;/a&gt; to the full &lt;a href="https://architmittal.com/blog/cfos-ai-playbook-finance-automation-india-2026" rel="noopener noreferrer"&gt;CFO playbook of five finance automations&lt;/a&gt; I rolled out to two SMBs this quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Non-Obvious Tip Most Teams Miss
&lt;/h2&gt;

&lt;p&gt;The setting that changed my workflow most: &lt;strong&gt;a CLAUDE.md file at the root of every project.&lt;/strong&gt; Claude Code reads it automatically. I put project context, naming conventions, the specific broker API version we're using, and our testing rules. It's the difference between getting a generic Python suggestion and getting a suggestion that knows we use Pandas 2.2, pytest, and a specific way of mocking Kite Connect.&lt;/p&gt;

&lt;p&gt;If you skip this step, you're using maybe 60% of the tool. I've seen teams adopt Claude Code, get indifferent results, and blame the model. Nine times out of ten, the CLAUDE.md was missing or empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Switch?
&lt;/h2&gt;

&lt;p&gt;If you're already on Cursor and happy, don't rip it out. Layer Claude Code on top for planning and large refactors and see how the split feels for a month. If you're still on Copilot-only and doing anything more complex than boilerplate, the jump to Claude Code is the single highest-ROI tooling change I've made as an automation consultant this year.&lt;/p&gt;

&lt;p&gt;"Jo tool sahi kaam karta hai, wohi chuno." (Pick the tool that does the actual job.)&lt;/p&gt;

&lt;p&gt;Not everything should be automated, and &lt;a href="https://architmittal.com/blog/over-automation-trap-when-not-to-automate" rel="noopener noreferrer"&gt;not every process should be shipped with an AI pair programmer either&lt;/a&gt;. For production automations where quality and context matter more than keystroke speed, Claude Code is the best tool I've used. The ceiling is high. The floor is higher than any other assistant I've tried.&lt;/p&gt;

&lt;p&gt;Which AI coding tool are you reaching for most right now — and for what kind of work?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Archit Mittal helps businesses automate chaos. Follow on LinkedIn: &lt;a href="https://linkedin.com/in/automate-archit" rel="noopener noreferrer"&gt;@automate-archit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get automation insights every Saturday — join The Automation Dispatch at &lt;a href="https://architmittal.com/newsletter" rel="noopener noreferrer"&gt;architmittal.com/newsletter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>productivity</category>
      <category>python</category>
    </item>
    <item>
      <title>How a Weekend Python Script Saved a CA Firm 209 Hours During ITR Season</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 03:38:01 +0000</pubDate>
      <link>https://dev.to/automate-archit/how-a-weekend-python-script-saved-a-ca-firm-209-hours-during-itr-season-3lng</link>
      <guid>https://dev.to/automate-archit/how-a-weekend-python-script-saved-a-ca-firm-209-hours-during-itr-season-3lng</guid>
      <description>&lt;h1&gt;
  
  
  How a Weekend Python Script Saved a CA Firm 209 Hours During ITR Season
&lt;/h1&gt;

&lt;p&gt;₹3,12,000. That's what Rajesh Sharma's CA firm in Jaipur spent every ITR season hiring three temporary data entry operators for two months. They'd sit in a back room extracting numbers from Form 16 PDFs, cross-checking PAN details against the income tax portal, and pre-filling ITR forms — one cell at a time, one return at a time, 340 returns total.&lt;/p&gt;

&lt;p&gt;Last July, a single Python script replaced that entire process. Built in one weekend. Running cost: ₹0. Time saved across the season: 209 hours.&lt;/p&gt;

&lt;p&gt;This is not a theoretical tutorial. This is what actually happened, with real numbers, real friction, and the parts that almost went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: ITR Season Is a Two-Month Data Entry Marathon
&lt;/h2&gt;

&lt;p&gt;If you've never worked inside a CA firm during July–September, here's what it looks like. Clients send Form 16 PDFs over WhatsApp — sometimes as photos, sometimes as scanned copies with coffee stains. The firm's staff manually opens each PDF, types salary figures into a master Excel sheet, cross-references PAN numbers on the income tax portal, and fills in the ITR form fields one by one.&lt;/p&gt;

&lt;p&gt;Rajesh's firm handled 340 individual ITRs last year. Each return took an average of 38 minutes of pure data processing before a CA even looked at it for deductions and exemptions. That's 215 hours of someone staring at PDFs and typing numbers into boxes.&lt;/p&gt;

&lt;p&gt;The three temporary hires cost ₹1,04,000 each for the season — ₹3,12,000 total. And they still made errors. Rajesh's senior CA spent another 40+ hours catching and fixing data entry mistakes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Har saal yahi hota tha. Teen log hire karo, train karo, phir unki galtiyan dhundo. It felt like we were paying people to create problems we'd solve ourselves."&lt;br&gt;
(Every year the same thing. Hire three people, train them, then find their mistakes.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Script: What It Actually Does
&lt;/h2&gt;

&lt;p&gt;I didn't build Rajesh an AI platform. I didn't sell him a subscription. I built a Python script that does three things — and does them well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: PDF Text Extraction
&lt;/h3&gt;

&lt;p&gt;The script uses an open-source PDF library to read every Form 16 PDF in a folder. It extracts employer name, PAN, salary breakdowns (basic, HRA, special allowance), TDS deducted, and Section 80C/80D declarations. For scanned or photo PDFs — which made up about 30% of the documents — it falls back to OCR using Tesseract, another free tool.&lt;/p&gt;

&lt;p&gt;This stage alone eliminated about 70% of the manual typing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Validation and Cross-Check
&lt;/h3&gt;

&lt;p&gt;Every extracted PAN is validated against a checksum algorithm (PAN numbers follow a specific format — the script catches typos instantly). Salary totals are cross-checked: does basic + HRA + special allowance + other components equal gross salary? If not, the return gets flagged for manual review instead of silently carrying an error forward.&lt;/p&gt;

&lt;p&gt;This is where the script outperformed humans consistently. The temporary hires caught about 60% of calculation mismatches. The script catches 100% — because it doesn't get tired at 4 PM on a Thursday.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Pre-Filled Output
&lt;/h3&gt;

&lt;p&gt;The script generates a structured spreadsheet with every field mapped to the ITR form layout. Rajesh's CAs open the sheet, review the numbers, apply their professional judgment on deductions and exemptions, and file. The 38-minute-per-return data processing step dropped to about 6 minutes of review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: 38 min/return x 340 returns = 215 hours of data processing&lt;/li&gt;
&lt;li&gt;After: 6 min/return x 340 returns = 34 hours of CA review&lt;/li&gt;
&lt;li&gt;Net saving: ~209 hours (after accounting for the 28 returns flagged for manual processing)&lt;/li&gt;
&lt;li&gt;Cost saving: ₹3,12,000 in seasonal hiring — replaced by a script with ₹0 running cost&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Almost Went Wrong
&lt;/h2&gt;

&lt;p&gt;I'd be lying if I said the script worked perfectly from day one. Two things nearly derailed it.&lt;/p&gt;

&lt;p&gt;The first problem was PDF variety. Indian employers don't follow a standard Form 16 template. Some PDFs had salary components in tables, others in plain text paragraphs, and a few used formats I'd never seen — with Devanagari headers mixed into English content. The first version of the script handled about 75% of PDFs correctly. I spent the second day of that weekend writing fallback extraction logic for edge cases. By the end, it handled 92% automatically — the remaining 8% (28 returns) got flagged for manual processing.&lt;/p&gt;

&lt;p&gt;The second problem was trust. Rajesh didn't trust the script's output initially — and he shouldn't have. We ran a parallel test: his staff processed the first 50 returns manually while the script processed the same 50. We compared outputs line by line. The script matched human output on 47 returns and was actually &lt;em&gt;more accurate&lt;/em&gt; on the other 3 (the staff had made small transcription errors).&lt;/p&gt;

&lt;p&gt;That parallel test took an extra day but bought something no amount of demos could: Rajesh's confidence that the numbers were right.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Jab tak maine apni aankhon se comparison nahi dekha, mujhe yakeen nahi aaya. But the numbers don't lie."&lt;br&gt;
(Until I saw the comparison with my own eyes, I didn't believe it. But the numbers don't lie.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond One CA Firm
&lt;/h2&gt;

&lt;p&gt;India has over 3.5 lakh practising Chartered Accountants. The vast majority of small and mid-size firms still process ITRs the way Rajesh used to — manually, with seasonal hires, under deadline pressure that leads to errors and late nights.&lt;/p&gt;

&lt;p&gt;The automation here isn't complicated. It's not an AI model that needs training data or a cloud platform that costs ₹50,000/month. It's a Python script using free libraries, running on the same laptop the CA already owns. The total infrastructure cost is zero.&lt;/p&gt;

&lt;p&gt;What it requires is someone who understands both the accounting workflow and the code — or a CA willing to work with someone who does. That intersection is where the real value lives. Not in the technology itself, but in knowing which 80% of the process is pure data movement and which 20% genuinely needs a Chartered Accountant's brain.&lt;/p&gt;

&lt;p&gt;If you're a CA firm owner reading this: you don't need to learn Python. You need to find someone who can spend a weekend understanding your Form 16 processing workflow and write a script tailored to it. The ROI pays for itself before August.&lt;/p&gt;

&lt;p&gt;And if you're a developer looking for freelance projects: ITR season starts in July. CA firms start panicking in June. That's your window. The ones who've done it manually for 20 years are the ones most ready to try something different — they just need someone to show them it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can a Python script really automate ITR filing for a CA firm?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. A Python script can automate the repetitive data extraction, validation, and pre-filling stages of ITR preparation. The CA still reviews and signs off on every return — the script handles the 80% of work that is pure data movement, not professional judgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much time can automation save during ITR season?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this case, a single script saved 209 hours across one ITR season by automating PDF extraction, data validation, and form pre-filling. The exact savings depend on your firm's volume and how manual your current process is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it legal for CA firms to use automation scripts for ITR filing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Absolutely. The automation handles data processing — extracting numbers from Form 16s, validating PAN details, and pre-filling fields. The Chartered Accountant still reviews every return, applies professional judgment, and digitally signs the final filing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does it cost to build an ITR automation script?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The script in this case study was built in a single weekend using free, open-source Python libraries. Total running cost: ₹0. No SaaS subscriptions, no API fees, no licensing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Archit Mittal helps businesses automate chaos. Follow on LinkedIn: &lt;a href="https://linkedin.com/in/automate-archit" rel="noopener noreferrer"&gt;@automate-archit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get automation insights every Saturday — join &lt;a href="https://architmittal.com/newsletter" rel="noopener noreferrer"&gt;The Automation Dispatch&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>fintech</category>
      <category>india</category>
    </item>
    <item>
      <title>How I Cut a Client's AI API Bill from ₹85K to ₹12K/Month — Without Losing Quality</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 03:36:49 +0000</pubDate>
      <link>https://dev.to/automate-archit/how-i-cut-a-clients-ai-api-bill-from-85k-to-12kmonth-without-losing-quality-24hm</link>
      <guid>https://dev.to/automate-archit/how-i-cut-a-clients-ai-api-bill-from-85k-to-12kmonth-without-losing-quality-24hm</guid>
      <description>&lt;p&gt;₹85,000 per month. That was the AI API bill sitting in my client's inbox when they called me in a mild panic last quarter. They run a mid-sized e-commerce operation in Pune — about 4,000 orders a day — and had integrated AI into customer support, product descriptions, and internal reporting. The AI was working beautifully. The invoice was not.&lt;/p&gt;

&lt;p&gt;"Archit bhai, AI toh kaam kar raha hai, lekin cost control se bahar ja raha hai." (The AI is working, but the costs are spiralling out of control.)&lt;/p&gt;

&lt;p&gt;Three weeks later, their monthly bill was ₹12,400. Same tasks. Same quality. No corners cut. Here's exactly what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Every Task Was Using the Most Expensive Model
&lt;/h2&gt;

&lt;p&gt;When I audited their setup, the issue was obvious within five minutes. Every single API call — whether it was classifying a customer complaint into one of 8 categories or generating a 2,000-word product description — was hitting the same premium model. It's the most common mistake I see with businesses adopting AI: they pick one model during the proof-of-concept phase and never revisit that decision as they scale.&lt;/p&gt;

&lt;p&gt;Think of it this way. You wouldn't hire a senior chartered accountant to do data entry. But that's essentially what was happening — a top-tier reasoning model was being used to answer "Is this complaint about shipping or billing?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #1: Model Routing — The Single Biggest Cost Lever
&lt;/h2&gt;

&lt;p&gt;Model routing is the practice of sending each task to the cheapest model that can handle it at acceptable quality. I categorized their ~47 distinct API call types into three tiers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Task Types&lt;/th&gt;
&lt;th&gt;Model Class&lt;/th&gt;
&lt;th&gt;Cost Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Simple&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Classification, extraction, yes/no decisions, formatting&lt;/td&gt;
&lt;td&gt;Lightweight (Haiku-class)&lt;/td&gt;
&lt;td&gt;~90% cheaper per call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Summarization, short-form content, template-based responses&lt;/td&gt;
&lt;td&gt;Mid-tier (Sonnet-class)&lt;/td&gt;
&lt;td&gt;~50% cheaper per call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long product descriptions, nuanced customer responses, analytics reports&lt;/td&gt;
&lt;td&gt;Premium (Opus-class)&lt;/td&gt;
&lt;td&gt;Full price — but only 12% of calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result? 68% of their API calls moved to the lightweight tier, 20% to mid-tier, and only 12% stayed on premium. That single change dropped the bill from ₹85K to roughly ₹38K. No quality degradation — we ran A/B tests on customer satisfaction scores for two weeks before fully switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #2: Prompt Caching — Stop Paying for the Same Context Twice
&lt;/h2&gt;

&lt;p&gt;Their customer support bot sent the same 1,200-token system prompt with every single API call. That's company policies, tone guidelines, product catalog context — all identical across thousands of daily calls. Every call was paying full input token pricing for information the model had already processed moments ago.&lt;/p&gt;

&lt;p&gt;Prompt caching solves this. The first call processes the full system prompt, and subsequent calls within the cache window reference it at a fraction of the cost. For their volume — around 6,000 support interactions per day — this alone saved ₹8,000-10,000 monthly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick math:&lt;/strong&gt; 6,000 calls/day x 1,200 tokens x 30 days = 216 million input tokens/month just on repeated system prompts. At standard pricing, that's a significant chunk of the bill that prompt caching nearly eliminates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fix #3: Batching Non-Urgent Requests
&lt;/h2&gt;

&lt;p&gt;Not everything needs a real-time response. Their internal reporting pipeline — daily sales summaries, inventory alerts, marketing performance digests — was making individual API calls as each data point came in. Sixty to eighty calls that could easily be batched into three or four.&lt;/p&gt;

&lt;p&gt;We restructured their reporting to collect data throughout the day and process it in batch windows — once at 6 AM, once at 2 PM, once at 10 PM. Batch API pricing is typically 50% cheaper than real-time, and for internal reports, a few hours of delay is completely acceptable.&lt;/p&gt;

&lt;p&gt;"Pehle har choti cheez ke liye alag call jaati thi. Ab ek baar mein sab ho jaata hai." (Earlier, every small thing triggered a separate call. Now everything processes at once.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #4: Output Token Discipline
&lt;/h2&gt;

&lt;p&gt;This one is subtle but adds up fast. Their product description prompts asked the model to "write a detailed, comprehensive product description." The model happily obliged — averaging 800-1,000 tokens per response when the actual requirement was 200-300 tokens for their product cards.&lt;/p&gt;

&lt;p&gt;We rewrote prompts with explicit length constraints and structured output formats. Instead of open-ended generation, the model received exact specifications: "Write a product description in exactly 3 sentences. First sentence: what it is. Second: key benefit. Third: who it's for."&lt;/p&gt;

&lt;p&gt;Output tokens are more expensive than input tokens on most providers. Cutting average output length by 60% across thousands of daily calls compounded into real savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Optimization&lt;/th&gt;
&lt;th&gt;Monthly Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model routing&lt;/td&gt;
&lt;td&gt;₹47,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt caching&lt;/td&gt;
&lt;td&gt;₹9,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request batching&lt;/td&gt;
&lt;td&gt;₹8,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output token discipline&lt;/td&gt;
&lt;td&gt;₹7,900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total monthly bill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;₹12,400 (down from ₹85,000)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's an 85% reduction. The AI does exactly the same work. The customer satisfaction score actually went &lt;em&gt;up&lt;/em&gt; by 3% — likely because the lighter models respond faster, and customers prefer quicker replies over marginally more eloquent ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Most People Get Wrong About AI Costs
&lt;/h2&gt;

&lt;p&gt;The instinct is to shop for a cheaper provider. "Should I switch from OpenAI to Claude? From Claude to an open-source model?" Sometimes that helps, but the real leverage is architectural. I've seen businesses switch providers three times and still overpay because the fundamental pattern — one model for everything, no caching, verbose outputs — never changes.&lt;/p&gt;

&lt;p&gt;If your AI API bill is higher than you'd like, start with these questions: How many of your API calls actually need a premium model? Are you sending the same context repeatedly? Can any of your calls be batched? Are your prompts asking for more output than you use?&lt;/p&gt;

&lt;p&gt;The answers usually reveal that 60-80% of your bill is waste hiding in plain sight. You don't need to spend less on AI. You need to spend smarter.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How much do AI API calls typically cost for a small business in India?&lt;/strong&gt;&lt;br&gt;
Most small businesses using AI APIs spend between ₹15,000 and ₹1,00,000 per month depending on volume and model choice. The biggest cost driver is using premium models for tasks that cheaper models handle equally well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is model routing and how does it reduce AI costs?&lt;/strong&gt;&lt;br&gt;
Model routing means sending each task to the cheapest AI model that can handle it well. Simple classification tasks go to lightweight models, while complex reasoning goes to premium models. This alone can cut costs by 40-60%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can prompt caching really save money on AI API bills?&lt;/strong&gt;&lt;br&gt;
Yes. If your application sends similar system prompts or context repeatedly, prompt caching avoids reprocessing those tokens. Depending on your use case, this can reduce input token costs by 50-90% on repeated calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it worth switching AI providers just to save costs?&lt;/strong&gt;&lt;br&gt;
Not always. The real savings come from architectural changes — model routing, caching, batching, and output token optimization — not from switching providers. That said, comparing pricing across providers for your specific use case is always smart.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Archit Mittal helps businesses automate chaos. Follow on LinkedIn: &lt;a href="https://linkedin.com/in/automate-archit" rel="noopener noreferrer"&gt;@automate-archit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related reads:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://architmittal.com/blog/weekend-python-script-ca-firm-209-hours-itr" rel="noopener noreferrer"&gt;How a Weekend Python Script Saved a CA Firm 209 Hours During ITR Season&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://architmittal.com/blog/zero-rupee-automation-stack-enterprise-workflows" rel="noopener noreferrer"&gt;The ₹0 Automation Stack: Enterprise-Grade Workflows Without Paying for SaaS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://architmittal.com/blog/over-automation-trap-when-not-to-automate" rel="noopener noreferrer"&gt;Why I Tell Clients NOT to Automate Everything — The Over-Automation Trap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>costoptimization</category>
      <category>india</category>
    </item>
  </channel>
</rss>
