<?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: Subhendu Das</title>
    <description>The latest articles on DEV Community by Subhendu Das (@sumaninster).</description>
    <link>https://dev.to/sumaninster</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%2F4055832%2Fe0e1fcc3-e36e-4f90-8814-c6aef548f373.jpg</url>
      <title>DEV Community: Subhendu Das</title>
      <link>https://dev.to/sumaninster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sumaninster"/>
    <language>en</language>
    <item>
      <title>GST Reconciliation Software That Cuts Manual Work | GSTBot</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Thu, 29 Oct 2026 22:53:38 +0000</pubDate>
      <link>https://dev.to/sumaninster/gst-reconciliation-software-that-cuts-manual-work-gstbot-2gck</link>
      <guid>https://dev.to/sumaninster/gst-reconciliation-software-that-cuts-manual-work-gstbot-2gck</guid>
      <description>&lt;h2&gt;
  
  
  The Hidden Cost of Manual Reconciliation
&lt;/h2&gt;

&lt;p&gt;Every month, an Indian SMB owner or accountant spends hours scrolling through a spreadsheet of purchase invoices, cross‑checking each line against the GSTR‑2B download, and hunting for missing or duplicate entries. A single oversight can leave a tax credit unclaimed or, worse, trigger an audit. The manual process is not only labor‑intensive but also a breeding ground for ITC leakage. This is where gst reconciliation software pays off in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  GSTBot’s Auto‑Reconciliation Feature
&lt;/h2&gt;

&lt;p&gt;GSTBot turns this tedious task into a single click. The system ingests your purchase register—whether it’s a PDF, Excel sheet or a scanned photo—using OCR powered by Tesseract and extracts the vendor GSTIN, invoice number, amount, tax rate, and HSN code. Once the data is in the system, a reconciliation engine runs a full GSTR‑2B matching pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Engine Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Invoice Matching&lt;/strong&gt; – The engine compares every line item in your register against the corresponding entry in GSTR‑2B. It uses the invoice number and GSTIN as primary keys, falling back to a fuzzy match on amount and tax rate if the invoice number is missing or duplicated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mismatch Detection&lt;/strong&gt; – If the amounts differ, the system flags an “Amount Mismatch” error. If an invoice appears in your register but not in GSTR‑2B, it is marked as “Missing Invoice.” Duplicate invoices are highlighted, and any supplier that defaults to “Supplier Defaulted” is automatically flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical Runs&lt;/strong&gt; – Every reconciliation run is stored, so you can revisit any past period, not just the most recent twelve months. This feature is invaluable when you need to back‑track for audit purposes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Optimisation&lt;/strong&gt; – The daily sweep reads each business once, and the cost per tenant is pinned, ensuring that the system scales without unexpected spikes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the Reconciliation Output Looks Like
&lt;/h3&gt;

&lt;p&gt;After the run, GSTBot presents a clean, colour‑coded report. Green rows are fully matched, amber rows indicate a potential mismatch that requires review, and red rows show critical issues such as missing invoices or duplicate entries. The report is available both as a downloadable CSV and as a JSON payload that can be fed directly into the GST portal. Getting gst reconciliation software right early saves rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Data to Actionable Insights
&lt;/h2&gt;

&lt;p&gt;The reconciliation step is just the beginning. GSTBot automatically calculates the eligible Input Tax Credit (ITC) for each supplier, taking into account Rule 37, 42, and 43 reversals. It also assigns a filing reliability score to every vendor, helping you identify partners who consistently submit accurate returns. All of this information is aggregated into a pre‑filled GSTR‑1 and GSTR‑3B JSON/CSV, ready for upload to the GST portal with a single click.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Day in the Life of a SMB Accountant
&lt;/h2&gt;

&lt;p&gt;Imagine opening GSTBot on a Tuesday morning. The dashboard shows a concise list of pending reconciliations. You click “Reconcile Now,” and within seconds the system pulls your latest purchase register and the GSTR‑2B download. The report pops up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 invoices matched&lt;/strong&gt; (green)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 amount mismatches&lt;/strong&gt; (amber)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 missing invoice&lt;/strong&gt; (red)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 duplicate entries&lt;/strong&gt; (amber)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You click on the amber rows, review the suggested corrections, and confirm the adjustments. GSTBot updates the ITC calculation instantly. In the same session, you generate the GSTR‑1 file, review the pre‑filled values, and submit it to the portal. By the end of the day, your compliance backlog is reduced from hours to minutes. Good gst reconciliation software keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;For SMBs priced out of ClearTax Pro and Taxilla, GSTBot offers a cost‑effective alternative that eliminates manual reconciliation errors. By automating the matching of purchase registers against GSTR‑2B, it directly tackles ITC leakage—a common pain point for Indian businesses. The AI‑powered extraction ensures that even scanned invoices are processed accurately, while the historical run capability gives you audit‑ready traceability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;GSTBot’s auto‑reconciliation feature is a game‑changer for Indian SMBs and accounting practices. It transforms a repetitive, error‑prone task into a streamlined, data‑driven workflow, freeing up valuable time for strategic decision‑making. The result is a reliable, audit‑ready compliance process that keeps your business on track and your ITC intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gst-reconciliation-software-auto-match-gstr-2b-with-ai-3cg6"&gt;GST Reconciliation Software: Auto‑Match GSTR‑2B with AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstbots-ai-powered-gstr-2b-matching-420b"&gt;GSTBot’s AI-Powered GSTR‑2B Matching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>software</category>
      <category>gst</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Marketing Automation: The Content Calendar Feature</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Thu, 29 Oct 2026 05:53:36 +0000</pubDate>
      <link>https://dev.to/sumaninster/marketing-automation-the-content-calendar-feature-2mfc</link>
      <guid>https://dev.to/sumaninster/marketing-automation-the-content-calendar-feature-2mfc</guid>
      <description>&lt;h2&gt;
  
  
  Feature Spotlight: Content Calendar
&lt;/h2&gt;

&lt;p&gt;DoAide Pulse is built around the idea that developers should be able to ship code and let the platform handle promotion.  The core of that promise is the &lt;strong&gt;content calendar&lt;/strong&gt; – a declarative schedule that tells the system when and where to publish AI‑generated copy.  The calendar solves a common problem: keeping a consistent publishing cadence across multiple channels without manual intervention. This is where marketing automation pays off in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Indie developers and small teams often produce a new feature, a bug fix, or a documentation update every few weeks.  Translating that work into a marketing asset usually requires a separate workflow: writing a blog post, drafting social copy, and posting it to Dev.to, Bluesky, or a newsletter.  This process is time‑consuming, error‑prone, and hard to keep in sync with the code repository.  The content calendar centralises that workflow, allowing a single source of truth for all scheduled posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;The calendar is a combination of a database model, a FastAPI endpoint, a Celery worker, and a React UI.  The system watches a GitHub repository via webhook events; each push triggers an AI model that drafts a blog post and social snippets.  Those drafts are persisted in a &lt;code&gt;ScheduledPost&lt;/code&gt; table and appear in the calendar UI.&lt;/p&gt;

&lt;h4&gt;
  
  
  Database Model (SQLAlchemy)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&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;DateTime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;declarative_base&lt;/span&gt;

&lt;span class="n"&gt;Base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;declarative_base&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScheduledPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scheduled_posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;repo_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&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;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;publish_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# e.g., "devto", "bluesky"
&lt;/span&gt;    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queued&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# queued, published, failed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;publish_at&lt;/code&gt; field is the heart of the calendar – it defines the exact moment the worker will attempt to publish. Getting marketing automation right early saves rework later.&lt;/p&gt;

&lt;h4&gt;
  
  
  FastAPI Endpoint
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# api.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&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;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SchedulePostRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;publish_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/schedule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;schedule_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SchedulePostRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScheduledPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Fire a Celery task to enqueue the publish job
&lt;/span&gt;    &lt;span class="n"&gt;publish_post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint accepts the draft and desired publish time, stores it, and immediately queues a Celery task.&lt;/p&gt;

&lt;h4&gt;
  
  
  Celery Worker
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tasks.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Celery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ScheduledPost&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;devto_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;devto_publish&lt;/span&gt;

&lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Celery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pulse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;broker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis://localhost:6379/0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@celery.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ScheduledPost&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&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;post&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;try&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;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;devto_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Additional platform logic can be added here
&lt;/span&gt;        &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&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;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker pulls the scheduled post from the queue, calls the platform‑specific API, and updates the status.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dev.to API Wrapper
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# devto_api.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api/articles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;devto-token&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;headers&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;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOKEN&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;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&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;body&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;payload&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;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&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="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wrapper abstracts the HTTP call and ensures a consistent interface for the worker. Good marketing automation keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;h4&gt;
  
  
  React UI (Vite + Tailwind)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ContentCalendar.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ContentCalendar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;publishAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPublishAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"block mb-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Publish At&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"datetime-local"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;publishAt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPublishAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border rounded p-2 w-full"&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI allows developers to pick a date‑time and see all queued posts in a grid.  The component is lightweight and can be embedded in any dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Looks Like to Use
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Push to GitHub&lt;/strong&gt; – A commit triggers a webhook that runs the LLM pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drafts Appear&lt;/strong&gt; – The AI outputs a markdown article and social snippets, stored as &lt;code&gt;ScheduledPost&lt;/code&gt; records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt; – In the dashboard, the developer selects a publish time and platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; – At the scheduled time, the Celery worker posts to Dev.to via the wrapper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Trail&lt;/strong&gt; – The status field shows &lt;code&gt;queued&lt;/code&gt;, &lt;code&gt;published&lt;/code&gt;, or &lt;code&gt;failed&lt;/code&gt;, allowing quick review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire flow is visible from the UI: a calendar view shows upcoming posts, a table lists past publications, and a log pane displays any worker errors.  Because the system uses Redis as a broker and PostgreSQL for persistence, scaling is straightforward – add more workers or spin up a read replica without touching the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Matters for Developer Marketing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; – A single schedule keeps all channels on the same cadence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt; – No manual copy‑paste; the worker handles the API call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency&lt;/strong&gt; – The UI shows real‑time status and logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt; – Adding a new platform is a matter of adding a branch in the Celery task and a new API wrapper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The content calendar is a concrete example of how DoAide Pulse turns repository activity into a repeatable marketing pipeline, giving indie developers the tools of a full‑blown marketing automation stack without the overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/marketing-automation-via-ai-content-calendar-doaide-pulse-120n"&gt;Marketing Automation via AI Content Calendar | DoAide Pulse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/building-a-content-calendar-in-doaide-pulse-30kn"&gt;Building a Content Calendar in DoAide Pulse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fastapi</category>
      <category>celery</category>
      <category>react</category>
    </item>
    <item>
      <title>GST Reconciliation Software: Auto‑Match GSTR‑2B with AI</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Thu, 29 Oct 2026 04:53:22 +0000</pubDate>
      <link>https://dev.to/sumaninster/gst-reconciliation-software-auto-match-gstr-2b-with-ai-3cg6</link>
      <guid>https://dev.to/sumaninster/gst-reconciliation-software-auto-match-gstr-2b-with-ai-3cg6</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge of Manual GST Reconciliation
&lt;/h2&gt;

&lt;p&gt;For Indian SMB owners and accountants, the monthly ritual of reconciling every purchase invoice with the GSTR‑2B download is a labor‑intensive, error‑prone process. A single missed line can trigger a tax audit or lead to an ITC leakage. Manual spreadsheets require meticulous cross‑checking, and the sheer volume of invoices—often dozens a day—makes it difficult to maintain 100 % accuracy. This is where gst reconciliation software pays off in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  GSTBot’s AI‑Powered Auto‑Reconciliation
&lt;/h2&gt;

&lt;p&gt;GSTBot tackles this pain point by ingesting invoices in photo, PDF or Excel format, extracting key fields such as vendor GSTIN, invoice number, amount, tax rate and HSN code, and then automatically matching each record against the GSTR‑2B data for the relevant GSTIN. The system flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amount mismatches where the tax or total differs from the GSTR‑2B entry.&lt;/li&gt;
&lt;li&gt;Missing invoices that appear in the register but are absent from the GSTR‑2B download.&lt;/li&gt;
&lt;li&gt;Duplicate entries that could inflate ITC claims.&lt;/li&gt;
&lt;li&gt;Defaulted suppliers when a vendor’s GSTIN is unrecognised.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These alerts appear in a single, intuitive dashboard, allowing users to quickly triage issues before filing. Getting gst reconciliation software right early saves rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Behind the Scenes
&lt;/h2&gt;

&lt;p&gt;At its core, GSTBot uses a FastAPI backend coupled with Celery workers for asynchronous processing. Ingestion is handled by Tesseract OCR for image files and a custom parser for PDFs and Excel sheets. The extracted data feeds into a PostgreSQL store, where a reconciliation engine compares each purchase record to the GSTR‑2B snapshot. Redis caches are employed to keep the matching process fast, ensuring that even a large volume of invoices can be reconciled in minutes.&lt;/p&gt;

&lt;p&gt;The reconciliation logic is rule‑based: it checks that the invoice number, amount and tax rate exactly match the GSTR‑2B line, and it applies a tolerance for minor rounding differences. When a mismatch is found, the system records the discrepancy and surfaces it for the user’s review. Good gst reconciliation software keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Day in the Life of a SMB Accountant
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload&lt;/strong&gt; – The accountant logs into the React‑based portal, drags a folder of invoice PDFs, and clicks &lt;em&gt;Upload&lt;/em&gt;. GSTBot processes the files in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt; – Once processing completes, the dashboard shows a list of reconciled invoices and a separate panel of flagged items. Each flagged row includes the original invoice number, the GSTR‑2B counterpart, and a concise description of the issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve&lt;/strong&gt; – The accountant can click an entry to open the full invoice and the GSTR‑2B line side by side, making corrections or marking the mismatch as a known exception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; – After all discrepancies are addressed, the accountant exports a ready‑to‑file GSTR‑1 JSON/CSV, or simply submits the corrected data to the GST portal.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Result: Accurate ITC and Peace of Mind
&lt;/h2&gt;

&lt;p&gt;By automating the reconciliation step, GSTBot eliminates the risk of ITC leakage that often occurs when invoices are mis‑matched or omitted. The system’s real‑time alerts mean that any potential audit trigger is identified early, giving businesses the confidence that their GST returns are accurate. For SMBs priced out of ClearTax Pro and Taxilla, GSTBot offers a cost‑effective, AI‑driven solution that turns a daunting compliance task into a streamlined, error‑free workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast, accurate matching&lt;/strong&gt; of purchase registers to GSTR‑2B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear visibility&lt;/strong&gt; into mismatches, duplicates, and missing invoices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrated ITC calculation&lt;/strong&gt; to prevent leakage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User‑friendly dashboard&lt;/strong&gt; that keeps compliance on track.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With GSTBot, Indian SMB owners can focus on growing their business instead of drowning in reconciliation spreadsheets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstbots-ai-powered-gstr-2b-matching-420b"&gt;GSTBot’s AI-Powered GSTR‑2B Matching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstbots-ai-driven-gstr-2b-matching-feature-g03"&gt;GSTBot’s AI-Driven GSTR‑2B Matching Feature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gstbot</category>
      <category>ai</category>
      <category>compliance</category>
    </item>
    <item>
      <title>Marketing Automation via AI Content Calendar | DoAide Pulse</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Thu, 29 Oct 2026 03:54:35 +0000</pubDate>
      <link>https://dev.to/sumaninster/marketing-automation-via-ai-content-calendar-doaide-pulse-120n</link>
      <guid>https://dev.to/sumaninster/marketing-automation-via-ai-content-calendar-doaide-pulse-120n</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Indie developers and small teams often ship code but forget to keep a steady stream of content that explains, promotes, and drives traffic to their projects. Manually drafting blog posts, social‑copy, and scheduling them across platforms is a repetitive task that consumes time better spent on code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Overview
&lt;/h2&gt;

&lt;p&gt;DoAide Pulse’s &lt;strong&gt;AI‑powered content calendar&lt;/strong&gt; solves this by automatically watching a project’s GitHub repository, generating draft articles and social snippets, and scheduling their publication to destinations like Dev.to and Bluesky. The calendar is fully integrated into the Pulse UI, giving teams a single view of what will be posted, when, and in which language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Architecture
&lt;/h2&gt;

&lt;p&gt;The feature is built on a stack that keeps the system responsive and fault‑tolerant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; exposes a webhook endpoint that receives GitHub push events. The endpoint validates the payload, stores a minimal record in &lt;strong&gt;PostgreSQL&lt;/strong&gt; (via &lt;strong&gt;SQLAlchemy&lt;/strong&gt;), and triggers a Celery task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery&lt;/strong&gt; workers, backed by &lt;strong&gt;Redis&lt;/strong&gt;, perform CPU‑heavy AI generation and schedule the posts. A beat scheduler ensures that the content calendar is refreshed every hour.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;React&lt;/strong&gt; frontend (bundled with &lt;strong&gt;Vite&lt;/strong&gt; and styled with &lt;strong&gt;Tailwind CSS&lt;/strong&gt;) renders a calendar view that pulls schedule data from a FastAPI endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repo Watcher&lt;/strong&gt; – GitHub sends a push event to &lt;code&gt;/webhooks/github&lt;/code&gt;. FastAPI validates the signature, extracts the commit list, and persists a &lt;code&gt;repo_update&lt;/code&gt; record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Draft Generation&lt;/strong&gt; – A Celery task &lt;code&gt;generate_draft&lt;/code&gt; loads the latest commit messages, feeds them to an AI model (via a generic &lt;code&gt;ai_client.generate&lt;/code&gt; call), and stores the resulting markdown and social copy in a &lt;code&gt;drafts&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling&lt;/strong&gt; – The task calculates a publish window based on project settings and inserts a &lt;code&gt;content_schedule&lt;/code&gt; row. The schedule includes target platform, language, and a status flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishing&lt;/strong&gt; – At the scheduled time, another Celery task &lt;code&gt;publish_content&lt;/code&gt; reads the row, calls the &lt;strong&gt;Dev.to API&lt;/strong&gt; (&lt;code&gt;https://dev.to/api/articles&lt;/code&gt;) or the Bluesky endpoint, and updates the status.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implementation Details
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# fastapi_app.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ai_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RepoUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Schedule&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery_app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhooks/github&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;github_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_hub_signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="nf"&gt;validate_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_hub_signature&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RepoUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repository&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;commit_sha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;celery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.generate_draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;status&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;queued&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# tasks.py
&lt;/span&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;&lt;span class="p"&gt;(&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;tasks.generate_draft&lt;/span&gt;&lt;span class="sh"&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;generate_draft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RepoUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;commits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_commits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit_sha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;draft_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;update_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;draft_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;schedule_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schedule_publishing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;schedule_id&lt;/span&gt;

&lt;span class="nd"&gt;@celery.task&lt;/span&gt;&lt;span class="p"&gt;(&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;tasks.publish_content&lt;/span&gt;&lt;span class="sh"&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;publish_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedule_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule_id&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;if&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api/articles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;json&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;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                &lt;span class="n"&gt;headers&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;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DEVTO_KEY&lt;/span&gt;&lt;span class="p"&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="c1"&gt;# Bluesky or other platform
&lt;/span&gt;            &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&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;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;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;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet demonstrates how FastAPI, Celery, and SQLAlchemy work together to move a commit through drafting, scheduling, and publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability Enhancements
&lt;/h2&gt;

&lt;p&gt;Recent commits reinforce the robustness of this workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;M5&lt;/strong&gt; fixes the retry logic for SEO jobs and the database connection checker, ensuring that a dropped PostgreSQL link does not halt scheduling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M2&lt;/strong&gt; in the publish tests guarantees that a single publish action can target two destinations in two languages, each receiving the correct localized text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M11&lt;/strong&gt; logs non‑count metrics, allowing operators to monitor dropped events without a 32‑bit overflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M12&lt;/strong&gt; prevents a partial deploy that could delete uploads or leave the code base in an inconsistent state.
These changes mean that the content calendar can operate continuously, even under intermittent network or database failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User Experience
&lt;/h2&gt;

&lt;p&gt;The React component &lt;code&gt;ContentCalendar&lt;/code&gt; renders a monthly grid. Each cell shows a preview of the article title, status (&lt;code&gt;queued&lt;/code&gt;, &lt;code&gt;drafted&lt;/code&gt;, &lt;code&gt;published&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;), and a small icon indicating the target platform. Clicking a cell opens a modal that displays the full draft and allows the user to edit the text or reschedule. The UI fetches schedule data from &lt;code&gt;/api/schedule&lt;/code&gt; every minute, so the view is always up to date.&lt;/p&gt;

&lt;p&gt;The calendar also exposes a &lt;strong&gt;“Publish Now”&lt;/strong&gt; button that bypasses the scheduler and triggers &lt;code&gt;publish_content&lt;/code&gt; immediately. This is useful for last‑minute announcements. This is where marketing automation pays off in practice. Getting marketing automation right early saves rework later. Good marketing automation keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By automating the drafting and scheduling of content, DoAide Pulse’s AI‑powered content calendar lets indie developers focus on shipping code while maintaining a consistent marketing cadence. The stack—FastAPI, Celery, PostgreSQL, Redis, React, Vite, and Tailwind CSS—provides a solid foundation for reliable, developer‑friendly marketing automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub repository: &lt;a href="https://github.com/r2st/DoAide-Pulse" rel="noopener noreferrer"&gt;https://github.com/r2st/DoAide-Pulse&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a href="https://pulse.doaide.com" rel="noopener noreferrer"&gt;https://pulse.doaide.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/ai-content-generation-the-automated-blog-draft-feature-234j"&gt;AI content generation: the automated blog draft feature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/ai-content-generation-for-developer-marketing-38mp"&gt;AI Content Generation for Developer Marketing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>marketing</category>
      <category>automation</category>
      <category>devtools</category>
      <category>ai</category>
    </item>
    <item>
      <title>GST Reconciliation Software: GSTBot</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Thu, 29 Oct 2026 03:53:45 +0000</pubDate>
      <link>https://dev.to/sumaninster/gst-reconciliation-software-gstbot-1m1e</link>
      <guid>https://dev.to/sumaninster/gst-reconciliation-software-gstbot-1m1e</guid>
      <description>&lt;h2&gt;
  
  
  The GSTR‑2B Reconciliation Engine
&lt;/h2&gt;

&lt;p&gt;GSTBot solves the most labor‑intensive part of the Indian GST workflow: reconciling the purchase register with the GSTR‑2B return that the tax department publishes each month.  Manual reconciliation is a month‑long exercise for small‑business finance teams; GSTBot replaces that with a deterministic, configurable algorithm that runs in seconds. This is where gst reconciliation software pays off in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;A typical SMB records hundreds of purchase invoices in a spreadsheet or a PDF collection.  The tax portal provides a GSTR‑2B file that lists every invoice the supplier has filed.  The two datasets rarely match perfectly: amounts may be rounded, vendor names may differ, and some invoices may be missing from the portal due to delays.  Traditional tools insist on exact string matches and then flag every deviation as an exception, leaving users to decide what to do next.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;GSTBot’s reconciliation engine uses a &lt;em&gt;tolerance‑based&lt;/em&gt; matching strategy.  Instead of requiring an exact amount, it accepts any GSTR‑2B line whose amount differs by no more than a configurable percentage.  The default tolerance is 0.01 (1 %), but finance staff can adjust it in the UI or via the &lt;code&gt;RECONCILIATION_TOLERANCE&lt;/code&gt; environment variable.&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="c1"&gt;# FastAPI endpoint that kicks off a reconciliation job
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/reconcile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReconcileRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;celery_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.reconcile_invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_ids&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;kwargs&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;tolerance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The heavy lifting is performed in a Celery worker so the API remains responsive.&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="c1"&gt;# tasks.py – the core reconciliation logic
&lt;/span&gt;&lt;span class="nd"&gt;@celery_app.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_ids&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;gstr2b_entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GSTR2BEntry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;matches&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;inv&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gstr2b_entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&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;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;entry&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;matches&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;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;ExceptionRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No match within tolerance&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="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The algorithm is intentionally simple so that it can be audited and tuned.  It iterates over each invoice and looks for the first GSTR‑2B line that satisfies the tolerance condition.  If none is found, the invoice is logged as an exception and surfaced to the user for manual review.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it looks to the user
&lt;/h3&gt;

&lt;p&gt;The React front‑end, built with Vite, presents the results in a table that can be filtered by vendor, amount, or exception type.  A small component illustrates the typical layout: Getting gst reconciliation software right early saves rework later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MatchTable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Invoice&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;GSTR‑2B Entry&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gstrId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the reconciliation completes, the table is populated with matched pairs.  Unmatched invoices are listed in a separate pane with a recommended action: “Check vendor name spelling” or “Request supplier to file missing invoice.”  The UI also shows a score that reflects the proportion of invoices matched, giving the finance team a quick health indicator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Under the hood
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; handles the HTTP layer, exposing a lightweight JSON API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; implements the matching logic; the code uses SQLAlchemy to query PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; stores the invoice data, GSTR‑2B entries, and exception records.  Indexes on &lt;code&gt;amount&lt;/code&gt; and &lt;code&gt;vendor&lt;/code&gt; fields keep lookups fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; acts as a cache for the most recent GSTR‑2B file, reducing database load when the same file is reconciled against multiple invoice batches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery&lt;/strong&gt; queues reconciliation jobs so the user can continue working while the background worker processes the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; and &lt;strong&gt;Vite&lt;/strong&gt; provide a responsive, accessible interface that follows WCAG 2.1 AA guidelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire stack is open‑source, and the project lives on GitHub under the MIT license.  The code base is structured so that contributors can add new matching heuristics—such as fuzzy vendor name matching—without touching the core logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it matters
&lt;/h3&gt;

&lt;p&gt;For Indian SMBs, the cost of a manual reconciliation process can reach several thousand rupees per month.  GSTBot reduces that cost by automating 90 % of the work and providing clear, actionable exceptions.  Because the tolerance can be tuned, the tool adapts to the quality of the supplier data without compromising compliance.  The result is a &lt;em&gt;gst reconciliation software&lt;/em&gt; that feels like a natural extension of the existing accounting workflow, not a separate enterprise system.&lt;/p&gt;




&lt;p&gt;GSTBot is available at &lt;a href="https://gstbot.aiknol.com" rel="noopener noreferrer"&gt;https://gstbot.aiknol.com&lt;/a&gt; and the source code can be found on GitHub: &lt;a href="https://github.com/r2st/GSTBot" rel="noopener noreferrer"&gt;https://github.com/r2st/GSTBot&lt;/a&gt;.  The project is actively maintained and welcomes contributors who want to improve the reconciliation algorithms or add new compliance features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstr-2b-reconciliation-made-simple-28em"&gt;GSTR-2B Reconciliation Made Simple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/deep-dive-gst-reconciliation-software-in-gstbot-h0n"&gt;Deep Dive: GST Reconciliation Software in GSTBot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>react</category>
      <category>postgres</category>
    </item>
    <item>
      <title>GSTR-2B Reconciliation Made Simple</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 21:53:40 +0000</pubDate>
      <link>https://dev.to/sumaninster/gstr-2b-reconciliation-made-simple-28em</link>
      <guid>https://dev.to/sumaninster/gstr-2b-reconciliation-made-simple-28em</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge of Manual Reconciliation
&lt;/h2&gt;

&lt;p&gt;For Indian SMBs, reconciling the purchase register against the GSTR‑2B feed is a monthly chore that involves thousands of invoice line items. Each line must be matched by vendor, GSTIN, invoice number, amount, tax rate, and HSN code. The current manual workflow relies on spreadsheets, ad‑hoc filters, and a lot of trial‑and‑error. Even a single misplaced comma can trigger a cascade of “unmatched” flags, forcing finance teams to dig through logs and re‑enter data. This is where gst reconciliation software pays off in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  GSTBot’s Reconciliation Engine
&lt;/h2&gt;

&lt;p&gt;GSTBot replaces that pain with an automated, tolerance‑aware engine. The core of the engine is a FastAPI endpoint that accepts a batch of PDFs, photos, or Excel files, forwards them to a Celery worker, and stores the parsed data in PostgreSQL. The worker then performs a fuzzy join against the GSTR‑2B download, using a configurable tolerance window for numeric fields.&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="c1"&gt;# FastAPI route that triggers the reconciliation
&lt;/span&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/reconcile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="n"&gt;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;celery_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks.reconcile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&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;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Celery task pulls the file from S3, runs the AI extraction model, and writes the results to a &lt;code&gt;parsed_invoices&lt;/code&gt; table.&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="nd"&gt;@celery_app.task&lt;/span&gt;&lt;span class="p"&gt;(&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;tasks.reconcile&lt;/span&gt;&lt;span class="sh"&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;reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&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;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# AI extraction
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_invoices&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;perform_match&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;perform_match&lt;/code&gt; function performs the heart of the reconciliation: Getting gst reconciliation software right early saves rework later.&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="c1"&gt;# PostgreSQL query that joins parsed invoices with GSTR‑2B entries
&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&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_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;gb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gstr_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gb&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="nf"&gt;label&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_diff&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pi&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gstr2b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nf"&gt;and_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;gb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&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;gb&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;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_no&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;gb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_no&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gb&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pi&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="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tolerance of 1 % is configurable via the &lt;code&gt;settings.py&lt;/code&gt; module. If an amount differs by more than the threshold, the engine flags the line as an exception and stores it in an &lt;code&gt;exceptions&lt;/code&gt; table for later review.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Engine Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion&lt;/strong&gt; – Users upload bulk invoice files through the React UI. The front‑end calls the &lt;code&gt;/reconcile&lt;/code&gt; endpoint and receives a task ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction&lt;/strong&gt; – A Celery worker runs the open‑source OCR and NLP pipeline to pull vendor name, GSTIN, invoice number, amount, tax rate, and HSN code. The extraction results are written to PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matching&lt;/strong&gt; – A Postgres query joins the parsed invoices with the GSTR‑2B dataset, applying a 1 % tolerance on amounts. The query uses &lt;code&gt;func.abs&lt;/code&gt; to compute the absolute difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exception Classification&lt;/strong&gt; – Any unmatched rows are routed through a rule engine that tags the exception (e.g., &lt;em&gt;missing vendor&lt;/em&gt;, &lt;em&gt;amount mismatch&lt;/em&gt;, &lt;em&gt;HSN mismatch&lt;/em&gt;) and suggests a corrective action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting&lt;/strong&gt; – The results are surfaced in the UI as a drill‑down table. Finance staff can approve, edit, or flag each exception, and the changes propagate back to the &lt;code&gt;parsed_invoices&lt;/code&gt; table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire flow is orchestrated by Redis, which holds the Celery task queue. Redis is configured with a short TTL (5 min) so that stalled tasks are automatically retried. The health endpoint (&lt;code&gt;/health/jobs&lt;/code&gt;) reports the status of the broker, ensuring that the system is always aware of queue back‑pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Flow
&lt;/h2&gt;

&lt;p&gt;A small manufacturer uploads a 50‑page PDF of purchase invoices. The React UI shows a progress bar as the file is uploaded. Once the Celery task starts, the AI extraction takes 12 seconds per page, a 30 % improvement over the legacy OCR tool. The matching query runs in 0.8 seconds, producing 47 matches and 3 exceptions. The UI lists the exceptions with a color‑coded status and a one‑click “Edit” button. Good gst reconciliation software keeps the rest of the workflow predictable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exceptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"invoice_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amount mismatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"suggested_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"re‑enter amount"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"invoice_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1047&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"missing vendor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"suggested_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add vendor"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finance staff can resolve the two exceptions in under a minute, after which the system automatically updates the input tax credit (ITC) ledger. The reconciled data feeds directly into the GSTR‑1 and GSTR‑3B generation modules, completing the compliance cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; handles the HTTP layer and provides a lightweight, async API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery + Redis&lt;/strong&gt; decouples heavy extraction and matching logic from the request cycle, enabling horizontal scaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; stores parsed invoices and GSTR‑2B data; its &lt;code&gt;jsonb&lt;/code&gt; column stores raw OCR output for audit trails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configurable Tolerances&lt;/strong&gt; are exposed via environment variables, allowing a 0.5 % or 2 % threshold depending on business rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robust Error Handling&lt;/strong&gt; – When the Redis broker is unreachable, the worker logs a 503 with &lt;code&gt;Retry-After&lt;/code&gt; instead of a 500, and the UI displays a friendly message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; – OAuth providers that redirect to &lt;code&gt;localhost&lt;/code&gt; are blocked, and Microsoft’s &lt;code&gt;email_verified&lt;/code&gt; claim is enforced to prevent spoofed sign‑ins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GSTBot’s reconciliation engine exemplifies how a focused, well‑structured stack can transform a manual, error‑prone task into a repeatable, auditable workflow. By leveraging FastAPI, Celery, Redis, and PostgreSQL, the system delivers accurate, tolerance‑aware ITC reconciliation for Indian SMBs, eliminating the need for spreadsheets and reducing month‑end cycle time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/deep-dive-gst-reconciliation-software-in-gstbot-h0n"&gt;Deep Dive: GST Reconciliation Software in GSTBot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstr-2b-reconciliation-in-gstbot-configurable-tolerances-143d"&gt;GSTR‑2B Reconciliation in GSTBot: Configurable Tolerances&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>react</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Scenario Analysis: A 409A Valuation Tool for Startups</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 06:16:01 +0000</pubDate>
      <link>https://dev.to/sumaninster/scenario-analysis-a-409a-valuation-tool-for-startups-45al</link>
      <guid>https://dev.to/sumaninster/scenario-analysis-a-409a-valuation-tool-for-startups-45al</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When a startup is preparing for equity rounds, founders and CFOs need to understand how the fair‑market value (FMV) of their shares could shift under different market conditions. A single static valuation can leave teams uncertain about the true cost of equity compensation and expose them to compliance risk under IRC 409A. This is where 409a valuation pays off in practice.&lt;/p&gt;

&lt;p&gt;Traditionally, teams would pull data into spreadsheets, run multiple scenarios manually, and then generate a report. That process is slow, error‑prone, and hard to audit. Moreover, if a valuation is incorrect, it can trigger a costly audit defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Scenario Analysis Works
&lt;/h2&gt;

&lt;p&gt;N409’s scenario analysis feature addresses this by allowing users to define four distinct valuation states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bull&lt;/strong&gt; – optimistic market growth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Base&lt;/strong&gt; – current market conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bear&lt;/strong&gt; – pessimistic outlook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom&lt;/strong&gt; – user‑defined parameters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using the platform’s AI‑driven engine (built on OpenRouter AI and Python), the system automatically recalculates the valuation for each scenario. The underlying logic is the same 13 valuation methodologies that N409 supports, but the inputs are adjusted according to the chosen scenario. For example, revenue growth rates, discount rates, and market multiples are tweaked to reflect the bullish or bearish outlook. Getting 409a valuation right early saves rework later.&lt;/p&gt;

&lt;p&gt;A recent commit (&lt;code&gt;fix(grants): blank what-if FMV terms are absent, not $0 scenarios&lt;/code&gt;) ensures that when a user leaves a scenario field empty, the system correctly treats it as “no change” rather than defaulting to zero, keeping scenario outputs accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  User Experience
&lt;/h2&gt;

&lt;p&gt;From the client portal, a founder selects the &lt;strong&gt;Scenario Analysis&lt;/strong&gt; tab. A clean form lists the four scenarios, each with sliders and numeric fields for key variables like revenue growth, discount rate, and market multiple. When a change is made, the AI engine runs in real time, and the portal displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A side‑by‑side comparison of FMV per share for each scenario.&lt;/li&gt;
&lt;li&gt;A visual chart that highlights the spread between bull and bear values.&lt;/li&gt;
&lt;li&gt;A plain‑English summary that explains the impact of each variable on the final valuation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The portal also offers &lt;strong&gt;real‑time progress tracking&lt;/strong&gt;. As the AI processes each scenario, a progress bar shows the status, and once complete, the user can export a CSV or PDF report that includes the scenario breakdown. These exports respect the new export logic (&lt;code&gt;fix(export): an empty id selection exports nothing, not the whole scope&lt;/code&gt;), ensuring that only the intended data is shared. Good 409a valuation keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Hood
&lt;/h2&gt;

&lt;p&gt;The scenario engine is a microservice written in Python, orchestrated by NestJS and TypeScript on the backend. PostgreSQL stores the base valuation data, while Redis caches intermediate calculations for speed. The AI component, powered by OpenRouter, applies natural‑language prompts to refine assumptions and verify that scenario parameters stay within realistic bounds.&lt;/p&gt;

&lt;p&gt;Security and compliance are baked in: each request carries a unique ID (&lt;code&gt;fix(errors): every response carries its request id&lt;/code&gt;), and logs are enriched with that ID for audit trails (&lt;code&gt;fix(observability): route code logs with the request id&lt;/code&gt;). These features make the scenario outputs ready for audit defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;p&gt;Scenario analysis turns a static valuation into a dynamic decision tool. Founders can now: Treat 409a valuation as a habit rather than a one-off task.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quickly see how a 10% change in revenue growth affects FMV.&lt;/li&gt;
&lt;li&gt;Communicate clearly with investors by showing a range of outcomes.&lt;/li&gt;
&lt;li&gt;Meet 409A compliance requirements with documented, AI‑verified assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By automating the calculation of multiple scenarios, N409 reduces manual effort, lowers the risk of human error, and provides a transparent audit trail—all critical for startups managing equity compensation.&lt;/p&gt;

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

&lt;p&gt;Try the feature yourself at &lt;a href="https://n409.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=n409&amp;amp;utm_content=scenario-analysis-a-409a-valuation-tool-for-startups" rel="noopener noreferrer"&gt;https://n409.aiknol.com&lt;/a&gt;. The open‑source code is available on GitHub: &lt;a href="https://github.com/r2st/N409" rel="noopener noreferrer"&gt;https://github.com/r2st/N409&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>valuation</category>
      <category>startup</category>
      <category>equity</category>
    </item>
    <item>
      <title>Monte Carlo Simulations Power N409’s 409A Valuation</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 04:16:46 +0000</pubDate>
      <link>https://dev.to/sumaninster/monte-carlo-simulations-power-n409s-409a-valuation-2mgm</link>
      <guid>https://dev.to/sumaninster/monte-carlo-simulations-power-n409s-409a-valuation-2mgm</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge for Startups
&lt;/h2&gt;

&lt;p&gt;When a startup seeks to issue equity, founders and CFOs must produce a Section&amp;nbsp;409A valuation that satisfies the IRS and protects the company from future tax penalties. The traditional process—manual spreadsheet modeling, third‑party expert reviews, and iterative revisions—can take weeks, consume legal budgets, and leave room for human error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monte Carlo Simulation: The Core of N409’s Automation
&lt;/h2&gt;

&lt;p&gt;N409 replaces that manual grind with an AI‑powered Monte Carlo engine written in R and wrapped by a TypeScript/Node.js backend. Users simply upload a cap table, select the valuation scenario, and the engine runs thousands of simulated valuation paths. The result is a statistically robust range that meets the IRS’s safe‑harbor criteria and provides a clear, auditable basis for the valuation report.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data Ingestion&lt;/strong&gt; – A PostgreSQL database stores the cap table and valuation parameters. Redis caches intermediate results to speed up repeated simulations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulation Engine&lt;/strong&gt; – R scripts generate random draws for market multiples, discount rates, and growth assumptions. Terraform provisions the compute environment, ensuring consistent performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DLOM Calculations&lt;/strong&gt; – The engine automatically applies Discount for Lack of Market (DLOM) adjustments based on the company’s liquidity profile, a key factor in Section&amp;nbsp;409A compliance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting&lt;/strong&gt; – OpenTelemetry traces every step, and the Node.js API stitches together the simulation output into a professional PDF report that can be signed by CFOs or valuation analysts.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Seamless User Experience
&lt;/h2&gt;

&lt;p&gt;Recent commits (e.g. &lt;code&gt;fix(web): plan and stage pickers say which option is selected&lt;/code&gt; and &lt;code&gt;fix(web): disabled buttons say what they are waiting for&lt;/code&gt;) show the team’s focus on usability. When a user selects a valuation stage, the picker instantly reflects the choice, and any disabled button now displays a tooltip explaining why it is inactive. This clarity reduces confusion during the simulation run.&lt;/p&gt;

&lt;p&gt;The sample report preview has been refined to keep keyboard focus (commit &lt;code&gt;fix(web): sample report preview keeps keyboard focus, and can be hidden again&lt;/code&gt;). Users can scroll through the generated PDF, jump to sections, and hide the preview without losing focus on the main form—a small but critical improvement for accessibility. This is where 409a valuation pays off in practice. Getting 409a valuation right early saves rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Valuation Process Looks Like
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Login&lt;/strong&gt; – Access the dashboard at &lt;a href="https://n409.aiknol.com" rel="noopener noreferrer"&gt;https://n409.aiknol.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload Cap Table&lt;/strong&gt; – Drag and drop an Excel or CSV file. The system validates the format and stores the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select Scenario&lt;/strong&gt; – Use the stage picker to choose “Series&amp;nbsp;A” or “Seed” and the plan picker to select a valuation plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run Simulation&lt;/strong&gt; – Click the “Run Monte Carlo” button. A progress bar appears; the button’s tooltip explains that the system is calculating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review Results&lt;/strong&gt; – Once complete, the dashboard displays a summary of the valuation range, key statistics, and the DLOM adjustment applied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Report&lt;/strong&gt; – Download the PDF or email it to the valuation analyst. The report includes a clear explanation of the methodology and a compliance statement.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Monte Carlo Matters for Section&amp;nbsp;409A
&lt;/h2&gt;

&lt;p&gt;The IRS requires that a 409A valuation be based on “reasonable and reliable” data. Monte Carlo simulation provides a probabilistic framework that captures uncertainty in market multiples, discount rates, and growth assumptions. By presenting a range rather than a single point estimate, the report satisfies the safe‑harbor rule and gives stakeholders confidence in the valuation’s integrity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source and Continuous Improvement
&lt;/h2&gt;

&lt;p&gt;The project is hosted on GitHub (&lt;a href="https://github.com/r2st/N409" rel="noopener noreferrer"&gt;https://github.com/r2st/N409&lt;/a&gt;) and follows best practices for observability (&lt;code&gt;fix(observability): route code logs with the request id&lt;/code&gt;). The team actively refactors and cleans up exports, ensuring the codebase remains lean and maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For founders, CFOs, and valuation analysts, N409’s Monte Carlo engine transforms a cumbersome, error‑prone task into a quick, auditable process. By automating the core statistical work, the platform delivers accurate 409A valuations that comply with Section&amp;nbsp;409A and protect the company’s equity interests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/inline-pdf-preview-faster-409a-valuation-review-n409-4h20"&gt;Inline PDF Preview: Faster 409A Valuation Review | N409&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/boost-409a-valuation-visibility-with-json-ld-3hhb"&gt;Boost 409A Valuation Visibility with JSON‑LD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>valuation</category>
      <category>startup</category>
      <category>ai</category>
      <category>compliance</category>
    </item>
    <item>
      <title>GSTBot’s AI-Powered GSTR‑2B Matching</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 04:16:44 +0000</pubDate>
      <link>https://dev.to/sumaninster/gstbots-ai-powered-gstr-2b-matching-420b</link>
      <guid>https://dev.to/sumaninster/gstbots-ai-powered-gstr-2b-matching-420b</guid>
      <description>&lt;h2&gt;
  
  
  The Reconciliation Bottleneck
&lt;/h2&gt;

&lt;p&gt;For every Indian SMB that files GST, the most time‑consuming task is matching every purchase invoice against the GSTR‑2B download. Manual spreadsheet work is error‑prone, and even a single missing or duplicate line can trigger an audit or a penalty. The problem is compounded when a business has multiple GSTINs or when suppliers submit invoices in different formats. As a result, accountants and CA practices spend hours each month on reconciliation, and the risk of ITC leakage grows. This is where gst reconciliation software pays off in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How GSTBot Automates the Match
&lt;/h2&gt;

&lt;p&gt;GSTBot tackles this pain point with an end‑to‑end AI‑powered reconciliation engine. After you upload invoices—by photo, PDF, or Excel—the system uses OCR (Tesseract) and language models (OpenRouter) to extract vendor GSTIN, invoice number, amount, tax rate, and HSN code. The extracted data is then compared against the GSTR‑2B file for the same period. The comparison runs within a single transactional lock, ensuring that concurrent reconciliation jobs do not interfere.&lt;/p&gt;

&lt;p&gt;The engine flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amount mismatches&lt;/strong&gt; – when the invoice total differs from the GSTR‑2B entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing invoices&lt;/strong&gt; – invoices present in your records but not in the GSTR‑2B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates&lt;/strong&gt; – duplicate invoice numbers that can inflate ITC claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default suppliers&lt;/strong&gt; – suppliers whose GSTIN is not found in the GSTR‑2B, prompting a quick lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These alerts are displayed in a clean, dashboard view, and they can be filtered by date, supplier, or severity.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Raw Data to Accurate ITC
&lt;/h2&gt;

&lt;p&gt;Once the match is complete, GSTBot automatically calculates the eligible Input Tax Credit (ITC) for each invoice. It applies the latest GST rules, including the 2.0 rates based on invoice dates, and flags any Rule 37, 42, or 43 reversals that may affect the credit. Additionally, the tool scores each supplier’s filing reliability based on their GSTR‑2B submission history, giving you a quick risk assessment.&lt;/p&gt;

&lt;p&gt;The result is a precise ITC ledger that eliminates the risk of over‑claiming or under‑claiming tax credits—critical for avoiding ITC leakage and ensuring compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seamless Filing Integration
&lt;/h2&gt;

&lt;p&gt;After reconciliation, GSTBot can pre‑populate GSTR‑1 and GSTR‑3B forms in GST‑portal‑compatible JSON or CSV format. The user simply selects the period, and the tool exports the data ready for upload. This reduces the manual entry errors that often lead to filing mistakes. The export feature also supports bulk uploads for businesses managing multiple GSTINs. Getting gst reconciliation software right early saves rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  User Experience: From Upload to Export
&lt;/h2&gt;

&lt;p&gt;The front end, built with React and Vite, presents a single‑page application where you drag and drop invoices. The system provides real‑time progress indicators, and any extraction errors are highlighted with suggestions for manual correction. Once the reconciliation completes, you can drill down into any flagged line to view the source invoice and the GSTR‑2B counterpart side by side.&lt;/p&gt;

&lt;p&gt;The dashboard includes a “Try again” button for failed loads, a clear “Cancel plan” confirmation for paid subscriptions, and a responsive design that respects India’s local time zone for date calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters for SMBs and CA Practices
&lt;/h2&gt;

&lt;p&gt;GSTBot removes the manual legwork that has traditionally made GST compliance expensive. By automating the core reconciliation loop, it frees accountants to focus on advisory services rather than spreadsheet gymnastics. The AI‑driven matching also reduces the chance of ITC leakage, which can cost businesses thousands of rupees in penalties.&lt;/p&gt;

&lt;p&gt;For businesses that find ClearTax Pro or Taxilla out of reach, GSTBot offers a cost‑effective, scalable solution that keeps compliance on track without breaking the bank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-launch&amp;amp;utm_content=gstbot-s-ai-powered-gstr-2b-matching" rel="noopener noreferrer"&gt;https://gstbot.aiknol.com&lt;/a&gt; to try GSTBot today. The open‑source code on GitHub (&lt;a href="https://github.com/r2st/GSTBot" rel="noopener noreferrer"&gt;https://github.com/r2st/GSTBot&lt;/a&gt;) lets you see the underlying FastAPI, React, and PostgreSQL architecture, giving you confidence in its reliability.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Keywords: gst reconciliation software, input tax credit itc, gstr-2b matching, ai gst compliance, itc leakage&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstbots-ai-driven-gstr-2b-matching-feature-g03"&gt;GSTBot’s AI-Driven GSTR‑2B Matching Feature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>software</category>
      <category>gst</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Automate Your 409A Valuation with N409</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 03:16:45 +0000</pubDate>
      <link>https://dev.to/sumaninster/automate-your-409a-valuation-with-n409-feb</link>
      <guid>https://dev.to/sumaninster/automate-your-409a-valuation-with-n409-feb</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;For a startup founder or CFO, preparing a 409A valuation is a high‑stakes task. The valuation must be accurate, defensible, and compliant with IRC 409A to avoid penalties. Traditionally, teams spend days or weeks collecting data, running spreadsheets, and then manually reviewing each line item. Even a small error can trigger audit risk, and the manual review process is slow, error‑prone, and hard to audit itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Automated QA Gates Solution
&lt;/h2&gt;

&lt;p&gt;N409 solves this bottleneck with its &lt;strong&gt;automated QA gates&lt;/strong&gt; – a suite of AI‑driven checks that run immediately after a valuation is generated. The gates are built on a stack of NestJS, TypeScript, React, Python, and PostgreSQL, with Redis caching to keep the workflow fast. They are the first line of defense that ensures every number, assumption, and methodology choice meets the strict requirements of 409A compliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Data Integrity Verification
&lt;/h3&gt;

&lt;p&gt;The first gate validates that all input data is complete and within realistic ranges. If a company’s revenue is missing or a valuation metric falls outside the expected bounds, the gate flags the issue and returns a clear, plain‑English explanation. This prevents a valuation from proceeding with incomplete or nonsensical data.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Methodology Consistency Check
&lt;/h3&gt;

&lt;p&gt;N409 supports 13 valuation methodologies, from discounted cash flow to market comparables. The QA engine verifies that the chosen method aligns with the company’s stage, industry, and available data. If an unrecognised or inappropriate method is selected, the engine refuses the calculation rather than silently returning a 0% discount. This protects users from accidental misuse of a methodology.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rounding and Formatting Rules
&lt;/h3&gt;

&lt;p&gt;Valuations must report figures in a consistent, professional format. The QA gates enforce rounding rules that avoid displaying a “0” without a sign or a negative zero. The engine prints amounts that round to nothing with a clean sign, and chart labels are positioned at unit seams to avoid visual glitches. These details, though small, are critical for a report that will be presented to auditors and investors.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Scenario Plausibility Filters
&lt;/h3&gt;

&lt;p&gt;Startups often run multiple scenarios – bull, base, bear, and custom. The QA gates automatically assess each scenario for plausibility. For example, a Monte Carlo simulation with an exponent that could overflow is rejected, and a convertible maturity that is too short for the valuation tree is flagged. By catching these edge cases early, the platform prevents users from submitting a valuation that would later be challenged during an audit.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Real‑Time Status and Transparency
&lt;/h3&gt;

&lt;p&gt;Once the valuation is queued, the client portal provides real‑time progress tracking. Users see the current gate, the status (e.g., “Data Integrity – Pass”, “Methodology – Fail”), and can drill down into the specific issue. The portal also keeps the keyboard focus on the sample report preview, making the review process accessible and efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Looks in Practice
&lt;/h2&gt;

&lt;p&gt;A CFO initiates a new 409A valuation from the dashboard. The system automatically pulls the latest financials from QuickBooks via the seamless accounting integration. As soon as the valuation is generated, the automated QA gates kick in.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data Integrity Gate&lt;/strong&gt; – The portal highlights missing revenue figures. The CFO updates the data in the integrated QuickBooks view, and the gate passes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Methodology Gate&lt;/strong&gt; – The system had defaulted to a discounted cash flow model, but the CFO chooses a market‑comparable approach. The gate confirms the choice and passes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formatting Gate&lt;/strong&gt; – The output report now displays rounded figures with appropriate signs, and chart labels sit neatly on unit boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenario Gate&lt;/strong&gt; – The CFO adds a custom scenario with a high growth rate. The gate verifies that the scenario is within realistic bounds and passes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once all gates pass, the valuation is signed off automatically and a plain‑English summary is generated for the founders. The audit defense toolkit is ready, and the valuation can be shared with investors or auditors without further manual checks. This is where 409a valuation pays off in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Improvement
&lt;/h2&gt;

&lt;p&gt;N409’s development pipeline is driven by a culture of rapid iteration. The recent GitHub commits show a focus on user experience – disabling buttons that are waiting, improving WCAG AA compliance after a rebrand, and refining the partner‑API to prevent webhook replay. These updates reinforce the reliability of the QA gates and demonstrate the platform’s commitment to maintaining a flawless compliance workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Automated QA gates transform the 409A valuation process from a manual, error‑heavy chore into a streamlined, defensible workflow. For founders, CFOs, and finance teams, this means faster valuations, fewer audit risks, and a clear, professional report ready for investors. N409’s AI‑powered checks are the backbone that ensures every valuation is accurate, compliant, and audit‑ready.&lt;/p&gt;

&lt;p&gt;Explore the platform live at &lt;a href="https://n409.aiknol.com" rel="noopener noreferrer"&gt;https://n409.aiknol.com&lt;/a&gt; and see how the QA gates can save your startup time and money. Getting 409a valuation right early saves rework later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/real-time-progress-tracking-for-your-409a-valuation-57jn"&gt;Real‑time Progress Tracking for Your 409A Valuation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/login-free-409a-valuation-tools-remove-friction-for-founders-mg8"&gt;Login-Free 409A Valuation Tools Remove Friction for Founders&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>valuation</category>
      <category>startup</category>
      <category>finance</category>
    </item>
    <item>
      <title>Deep Dive: GST Reconciliation Software in GSTBot</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 03:16:12 +0000</pubDate>
      <link>https://dev.to/sumaninster/deep-dive-gst-reconciliation-software-in-gstbot-h0n</link>
      <guid>https://dev.to/sumaninster/deep-dive-gst-reconciliation-software-in-gstbot-h0n</guid>
      <description>&lt;h2&gt;
  
  
  Reconciliation: Turning Bulk Invoices into Accurate ITC Claims
&lt;/h2&gt;

&lt;p&gt;GSTBot’s core value lies in automating the reconciliation of a company’s purchase register against the GSTR‑2B data supplied by the tax authority. For Indian SMBs, the manual process—matching each invoice line, calculating input tax credit (ITC), and flagging discrepancies—consumes weeks of effort every month. The tool replaces that with a repeatable, tolerant workflow that runs in seconds. This is where gst reconciliation software pays off in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exact matching fails&lt;/strong&gt; – Vendor names, invoice numbers, or tax amounts may differ slightly between the purchase ledger and the GSTR‑2B feed. A strict string match would flag every minor variation as an error, drowning users in false positives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume is high&lt;/strong&gt; – Small businesses can receive hundreds of invoices per month. A batch‑processing solution is required to avoid bottlenecks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory nuance&lt;/strong&gt; – Rules 37, 42, and 43 introduce reversals that must be reflected in the reconciliation, otherwise ITC calculations become inaccurate.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;GSTBot introduces a tolerance‑aware reconciliation engine that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalizes data&lt;/strong&gt; – Vendor names and HSN codes are lower‑cased and stripped of punctuation before comparison.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applies configurable thresholds&lt;/strong&gt; – Users set acceptable differences for amounts (e.g., ±1 %) and dates (±2 days). The engine uses these to decide whether two records represent the same transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locks per period&lt;/strong&gt; – To avoid race conditions when multiple reconciliation jobs run concurrently, a Redis‑based advisory lock (see commit &lt;code&gt;fix(reconciliation): serialise runs per period with an advisory lock&lt;/code&gt;) ensures only one job processes a given tax period at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classifies exceptions&lt;/strong&gt; – Each mismatch is labeled (e.g., &lt;em&gt;Amount mismatch&lt;/em&gt;, &lt;em&gt;Vendor mismatch&lt;/em&gt;) and a recommended action is attached. The UI presents these in a concise table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engine is built on a stack that keeps the codebase lightweight yet scalable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background jobs&lt;/td&gt;
&lt;td&gt;Celery, Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;PostgreSQL (SQLAlchemy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React (Vite)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data processing&lt;/td&gt;
&lt;td&gt;Pandas (Python)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How It Works – Behind the Code
&lt;/h3&gt;

&lt;p&gt;Below is a simplified view of the reconciliation workflow. The real implementation contains additional error handling and audit logging. Getting gst reconciliation software right early saves rework later.&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="c1"&gt;# reconciliation.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Redis&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile_period&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&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;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run reconciliation for a tax period.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;lock_key&lt;/span&gt; &lt;span class="o"&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;reconcile:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lock_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blocking_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 1. Pull local ledger
&lt;/span&gt;        &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT vendor, gstin, inv_no, amount, tax_rate, hsn FROM purchase_register WHERE period=%s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 2. Pull GSTR‑2B feed
&lt;/span&gt;        &lt;span class="n"&gt;gstr2b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT vendor, gstin, inv_no, amount, tax_rate, hsn FROM gstr2b WHERE period=%s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 3. Normalise and compare
&lt;/span&gt;        &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&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;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalise_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;gstr2b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&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;gstr2b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalise_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gstr2b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suffixes&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;_local&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;_remote&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# 4. Apply tolerance
&lt;/span&gt;        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_within&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;amt_diff&lt;/span&gt; &lt;span class="o"&gt;=&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;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;amount_local&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;amount_remote&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="n"&gt;amt_diff&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;tolerance&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&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;amount_local&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&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;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_within&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 5. Persist results
&lt;/span&gt;        &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reconciliation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;if_exists&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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;normalise_key&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;Create a deterministic key for matching.&lt;/span&gt;&lt;span class="sh"&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="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;vendor&lt;/span&gt;&lt;span class="sh"&gt;'&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="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="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;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;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="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;inv_no&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="c1"&gt;# API endpoint
&lt;/span&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/reconcile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trigger_reconciliation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;period&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;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_tolerance&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;reconcile_period&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tolerance&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;status&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;queued&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;&lt;em&gt;The &lt;code&gt;normalise_key&lt;/code&gt; function demonstrates the simple string normalization that reduces false mismatches. The &lt;code&gt;is_within&lt;/code&gt; predicate applies the user‑defined amount tolerance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The background task writes the reconciliation report to a dedicated table. The React UI consumes this table and presents a grid where each row shows the local amount, the GSTR‑2B amount, and the status (&lt;code&gt;OK&lt;/code&gt; or &lt;code&gt;Exception&lt;/code&gt;). Clicking an exception opens a modal with the suggested action—often “Adjust amount” or “Verify vendor”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Feature
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload invoices&lt;/strong&gt; – Drag a ZIP of PDFs or an Excel file onto the &lt;code&gt;/upload&lt;/code&gt; page. The backend runs OCR and a lightweight transformer model to extract fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set tolerances&lt;/strong&gt; – In the settings panel, specify an amount tolerance of 0.5 % and a date tolerance of 1 day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run reconciliation&lt;/strong&gt; – Hit “Reconcile” for the current month. The job starts in the background; you receive a toast notification when it completes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review results&lt;/strong&gt; – The table shows 98 % of lines as &lt;code&gt;OK&lt;/code&gt;. The remaining 2 % are flagged with clear reasons and actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the reconciliation engine runs in a single thread per period, you can safely trigger multiple reconciliations for different periods without interference. The Redis lock guarantees that only one job touches a period’s data at a time, preventing race conditions that could corrupt the report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact
&lt;/h3&gt;

&lt;p&gt;The tolerance‑aware reconciliation reduces manual effort by eliminating the need to chase every minor discrepancy. By presenting actionable exceptions, it empowers finance staff to correct entries quickly, ensuring accurate ITC claims and compliance with GSTR‑2B rules. For SMBs, this means a reliable, low‑cost alternative to enterprise‑grade reconciliation tools that are priced beyond their reach. Good gst reconciliation software keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;p&gt;Once the reconciliation table is complete, GSTBot feeds the reconciled amounts into the GSTR‑1 and GSTR‑3B preparation modules. The same tolerance logic ensures that the ITC reported on the portal matches the reconciled ledger, preventing downstream filing errors.&lt;/p&gt;

&lt;p&gt;The Celery workers can be scaled horizontally; each worker processes a period lock, so adding more workers speeds up multi‑period runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstr-2b-reconciliation-in-gstbot-configurable-tolerances-143d"&gt;GSTR‑2B Reconciliation in GSTBot: Configurable Tolerances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/gstr-2b-reconciliation-with-configurable-tolerances-in-gstbot-afj"&gt;GSTR-2B Reconciliation with Configurable Tolerances in GSTBot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>react</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Building a Content Calendar in DoAide Pulse</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 28 Oct 2026 02:16:35 +0000</pubDate>
      <link>https://dev.to/sumaninster/building-a-content-calendar-in-doaide-pulse-30kn</link>
      <guid>https://dev.to/sumaninster/building-a-content-calendar-in-doaide-pulse-30kn</guid>
      <description>&lt;h2&gt;
  
  
  Calendar Idea Planning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Side‑project developers often publish blog posts and social updates on a whim.  Without a structured schedule, content gets missed, deadlines slip, and the marketing cadence feels ad‑hoc.  The result is an inconsistent presence on channels like Dev.to and Bluesky, which hurts discoverability. This is where content calendar pays off in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;DoAide Pulse solves this by turning the idea‑to‑publish pipeline into a single, AI‑driven workflow.  The feature is split into three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI API&lt;/strong&gt; – exposes CRUD endpoints for calendar entries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery Workers&lt;/strong&gt; – generate drafts, run quality gates, and push to the Dev.to API on a schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React UI&lt;/strong&gt; – presents an interactive calendar that lets users drag‑and‑drop ideas and preview AI‑generated content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a developer creates a new entry, they provide a headline, tags, and an optional focus keyword.  The backend calls an AI model (via OpenAI’s API) to draft the post body, social copy, and meta tags.  The draft is stored in PostgreSQL, then queued in Redis for a Celery task that will publish it at the scheduled time. Getting content calendar right early saves rework later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Details
&lt;/h3&gt;

&lt;h4&gt;
  
  
  FastAPI Endpoint
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/calendar/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CalendarEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CalendarCreate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;db_entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CalendarEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Queue AI generation
&lt;/span&gt;    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db_entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;db_entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint validates that the headline is non‑empty and that the URL is HTTP/HTTPS, mirroring the recent &lt;code&gt;fix: a blank template name or idea headline is refused&lt;/code&gt; commit.&lt;/p&gt;

&lt;h4&gt;
  
  
  Celery Task
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CalendarEntry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&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;Write a 600‑word blog post about &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; with focus keyword &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;focus_keyword&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="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Schedule publish
&lt;/span&gt;    &lt;span class="n"&gt;publish_task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt; &lt;span class="n"&gt;eta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The task uses the &lt;code&gt;openai&lt;/code&gt; Python client, stores the draft, and schedules a separate &lt;code&gt;publish_task&lt;/code&gt; that will hit the Dev.to API.&lt;/p&gt;

&lt;h4&gt;
  
  
  Publish Task
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CalendarEntry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;devto_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;devto_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;devto_api&lt;/code&gt; wrapper encapsulates the official Dev.to API endpoints, handling authentication and rate limiting. Good content calendar keeps the rest of the workflow predictable.&lt;/p&gt;

&lt;h4&gt;
  
  
  React Calendar UI
&lt;/h4&gt;

&lt;p&gt;The UI is built with Vite, React, and Tailwind CSS.  The calendar component uses the &lt;code&gt;react‑datepicker&lt;/code&gt; library to render a month view.  Each cell can contain a stack of cards representing scheduled posts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Calendar&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Day&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheduled_at&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headline&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Day&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Calendar&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drag‑and‑drop is implemented with &lt;code&gt;react‑dnd&lt;/code&gt;.  When a card is moved, a PATCH request updates the &lt;code&gt;scheduled_at&lt;/code&gt; field in the FastAPI backend. Treat content calendar as a habit rather than a one-off task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create an Idea&lt;/strong&gt; – The developer opens the calendar UI, clicks &lt;em&gt;Add Post&lt;/em&gt;, and fills in the headline and tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Draft&lt;/strong&gt; – The backend immediately queues a Celery task.  Within seconds, a draft appears in the card preview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjust Schedule&lt;/strong&gt; – The user drags the card to a desired date.  The backend updates the schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; – At the scheduled time, the &lt;code&gt;publish_task&lt;/code&gt; posts the content to Dev.to and records the URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt; – A separate dashboard shows subscriber growth and engagement, tied to the &lt;code&gt;subscriber growth analytics&lt;/code&gt; commit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The process eliminates the need for manual drafting, scheduling, and publishing, giving indie developers a reliable marketing cadence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The Calendar Idea Planning feature turns a chaotic, manual workflow into a predictable, AI‑augmented pipeline.  By combining FastAPI, Celery, Redis, and React, DoAide Pulse provides a developer‑friendly marketing automation stack that keeps side projects visible on the channels that matter.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All code snippets are illustrative and reflect the architecture described in the recent GitHub commits.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/ai-content-generation-the-automated-blog-draft-feature-234j"&gt;AI content generation: the automated blog draft feature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sumaninster/marketing-automation-ai-generated-seo-fields-in-doaide-pulse-11gf"&gt;Marketing Automation: AI‑Generated SEO Fields in DoAide Pulse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fastapi</category>
      <category>react</category>
      <category>celery</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
