DEV Community

Subhendu Das
Subhendu Das

Posted on

Mastering GSTR‑2B Reconciliation with GSTBot

Feature Spotlight: GSTR‑2B Reconciliation Engine

The Problem

Indian SMBs spend dozens of hours each month reconciling the purchase register against the GSTR‑2B download. The traditional approach relies on exact string matching of vendor name, GSTIN, invoice number, amount, tax rate, and HSN code. Even a single typo or a minor rounding difference can cause a match to fail, forcing manual investigation and increasing the risk of ITC mis‑claiming.

Solution Architecture

GSTBot replaces the manual process with an automated reconciliation engine built on a proven stack:

  • FastAPI – a lightweight ASGI framework that exposes a /reconcile endpoint.
  • Python – the core language for business logic.
  • PostgreSQL – stores the purchase register, GSTR‑2B records, and reconciliation results.
  • Redis – provides a distributed lock to prevent concurrent reconciliation runs for the same period.
  • Celery – schedules the heavy lifting as an asynchronous task.

The engine works in three phases:

  1. Data Ingestion – PDFs, photos, or Excel files are parsed by an AI extractor into a structured JSON payload.
  2. Reconciliation – a tolerance‑aware matcher compares each register line to the GSTR‑2B set.
  3. Exception Classification – unmatched or partially matched lines are tagged with a recommended action.

Tolerance Engine

The heart of the matcher is a set of configurable tolerances that replace strict equality checks. A typical configuration looks like:

# config/tolerances.py
TOLERANCES = {
    "amount_pct": 0.02,          # 2% difference allowed
    "tax_rate_pct": 0.01,        # 1% difference allowed
    "hsn_variation": 1,          # one HSN code difference tolerated
    "date_window_days": 7,       # invoices within 7 days are considered
}
Enter fullscreen mode Exit fullscreen mode

During reconciliation, each register line is queried against the GSTR‑2B table using a single, indexed SQL statement. Recent commits (GB029 M8 performance audit) removed redundant double‑scan functions and narrowed the /rule37 query to credit‑eligible purchases, improving runtime from ~45 s to <10 s on a 10 k‑row dataset.

SELECT g.*
FROM gstr2b g
JOIN purchase_register p
  ON p.vendor_gstin = g.vendor_gstin
 AND p.invoice_number = g.invoice_number
WHERE ABS(p.amount - g.amount) / g.amount <= :amount_pct
  AND ABS(p.tax_rate - g.tax_rate) / g.tax_rate <= :tax_rate_pct
  AND ABS(EXTRACT(DAY FROM p.invoice_date - g.invoice_date)) <= :date_window_days
  AND ABS(p.hsn - g.hsn) <= :hsn_variation;
Enter fullscreen mode Exit fullscreen mode

The query uses a composite index on (vendor_gstin, invoice_number, amount, tax_rate, hsn, invoice_date) to keep lookups sub‑millisecond.

Concurrency Guard

To avoid double‑processing, the Celery task acquires a Redis lock keyed by the fiscal month and year. The lock has a short TTL (30 s) to prevent accidental deadlocks.

# tasks/reconcile.py
from redis import Redis
from celery import shared_task

redis = Redis()

@shared_task
def run_reconciliation(month, year):
    lock_key = f"reconcile:{year}-{month:02d}"
    if not redis.set(lock_key, "1", nx=True, ex=30):
        raise RuntimeError("Reconciliation already running for this period")
    try:
        # core logic
        pass
    finally:
        redis.delete(lock_key)
Enter fullscreen mode Exit fullscreen mode

Example API Flow

# api/main.py
from fastapi import FastAPI, Depends
from tasks.reconcile import run_reconciliation

app = FastAPI()

@app.post("/reconcile")
async def start_reconciliation(month: int, year: int):
    task = run_reconciliation.delay(month, year)
    return {"task_id": task.id, "status": "queued"}
Enter fullscreen mode Exit fullscreen mode

The UI (React + Vite) polls /tasks/{id} for status and displays a table of reconciled rows, color‑coded by exception type. Clicking a row reveals the underlying GSTR‑2B and register data, along with a suggested action such as "Adjust amount", "Verify GSTIN", or "Re‑file".

Real‑World Usage

A typical SMB uploads a 5 MB batch of invoices in a single operation. The AI extractor runs in the background, pushing JSON blobs to a Celery queue. Within 2 minutes, the /reconcile task completes, and the finance team can review the 1 % exception rate in a matter of minutes, rather than hours of manual checks.

Performance & Observability

Recent commits (GB032 M11) added structured logging for silent operations, while GB033 M5 surfaced swallowed errors in OAuth and payment flows. The observability stack now logs every reconciliation run, including query execution times and exception counts, making it straightforward to spot regressions.

Conclusion

GSTBot’s GSTR‑2B reconciliation engine turns a labor‑intensive, error‑prone task into a repeatable, auditable process. By leveraging tolerances, a robust query plan, and a distributed lock, the system delivers reliable ITC matching for Indian SMBs without the high cost of enterprise‑grade tools.


Key takeaways

  • Configurable tolerances replace brittle exact matching.
  • A single indexed query keeps latency low.
  • Redis locks and Celery ensure idempotent, concurrent‑safe runs.
  • The UI presents actionable exceptions, streamlining compliance.

GSTBot exemplifies how focused automation can elevate GST compliance for small businesses in India.

Top comments (0)