← SIMRANJAISWAL.INTHE ENGINE ROOM · 10 · BILLING

The invoice that cannot be rejected

A customer's AP portal does not argue; it rejects. A missing PO number, a GSTIN from the wrong state, an invoice dated eight days after delivery — each one bounces the invoice ten days later, and the payment clock starts again from zero. This engine reads every customer's own rules, runs all ten checks before the invoice leaves, and holds anything that would have been rejected with the fix list attached. It is running below, in your browser, on 800 invoices to 40 customers whose rules you can read.

◆ LIVE ENGINE · RUNS IN YOUR BROWSER · PYTHON REFERENCE PUBLIC
0INVOICES IN THE REFERENCE BOOK · 40 CUSTOMERS · 60 DAYS
0EACH ONE A CUSTOMER'S OWN RULE · ALL RUN, ALL LISTED
0₹ LAKH-DAYS OF CASH DELAY AVOIDED · REFERENCE RUN
0PORTAL REJECTIONS THAT NEVER HAPPENED · OF 800
01 · THE JOB

In the payment-date model, the single strongest predictor of a late invoice was not the customer. It was whether the invoice had been rejected once already. A rejection is not a billing error; it is a thirty-day loan you did not agree to make.

Large customers bill by portal now. The portal has rules — a PO that must exist and still have value on it, a GSTIN whose state matches the bill-to, a delivery note number, HSN codes on every line, a date within a week of delivery — and it enforces them without reading the covering email. In the collections case the invoices that sat longest were the ones that had bounced: re-issued, re-numbered, re-submitted, and paid on terms counted from the second attempt. The fix is cheap on the day the invoice is drafted and expensive ten days later. The Invoice Validator is the SEAL that moves the check to the cheap side: a requirements profile per customer, ten checks that run in order and report every failure, and a hold that lists exactly what to fix.

02 ·THE ENGINEONE PROFILE PER CUSTOMER, TEN CHECKS, ONE HOLD
AT DRAFT · THE INVOICE MEETS THE CUSTOMER'S PROFILE · EVERY CHECK THAT CUSTOMER REQUIRES RUNS, IN ORDER, AND ALL FAILURES ARE LISTED · ANY FAIL → HOLD WITH THE FIX LIST · ALL PASS → SEND ONCE · THE DASHED LOOP IS WHAT HAPPENS WITHOUT THE ENGINE: THE PORTAL REJECTS 4–13 DAYS LATER AND THE CLOCK RESTARTS.

The engine knows nothing clever. It does not guess what a portal wants; it is told, once, per customer, and it prints what it was told. A check that a customer does not require is not run for that customer — a missing HSN code is a defect only where HSN is a rule — which is why the profile matters more than the checks. The output is not a score. It is a list: this invoice, these failures, fix these before it goes.

03 ·THE RULESWHAT "HELD" MEANS, EXACTLY
CHECKRULEREQUIRED BY
C1 · PO_MISSINGThe invoice carries a PO number70% of customers
C2 · PO_EXHAUSTEDThat PO is open and has remaining value (closed or fully billed → fail)The same 70%
C3 · GSTIN_INVALIDGSTIN present and well-formed: ^[0-9]{2}[A-Z0-9]{13}$ with a state code 01–37. Format only — no checksumEveryone
C4 · STATE_MISMATCHThe GSTIN's state code equals the bill-to state (place of supply). Only run when C3 passedEveryone
C5 · ADDRESS_DRIFTBill-to address equals the customer masterEveryone
C6 · HSN_MISSINGEvery line carries an HSN code50% of customers
C7 · DATE_WINDOWInvoice date within 7 days of delivery40%
C8 · DN_MISSINGThe delivery note number is on the invoice35%
C9 · AMOUNT_VS_POInvoice value not more than 0.5% over the PO line30%
C10 · DUPLICATE_NUMBERThe invoice number has never been used beforeEveryone
Working days25% of portals accept submissions on working days only; a weekend send lands on Monday. A scheduling rule, not a defect — applied in both worlds25%
HOLDAny failure holds the invoice with the full list; the fix takes 1–2 days and it goes out clean. Without the validator the same invoice goes out as drafted; the portal rejects it 4–13 days later with p = 0.85 per applicable failure (the rest slip through), it is re-issued after the same fix, and the customer pays 30-day terms plus their habitual lag (normal(8, 6) days) from the re-issueThe clock restarts
# the validator — every check the customer requires, in order, all failures listed (engine.py)
def validate(inv, C, seen):
    fails = []
    if C["needs_po"] and inv["po"] is None: fails.append("PO_MISSING")
    if C["needs_po"] and inv["po"] is not None and inv["po"]["remaining"] <= 0: fails.append("PO_EXHAUSTED")
    g = inv["gstin"]
    gstin_ok = bool(GSTIN_RE.match(g)) and 1 <= int(g[:2]) <= 37        # ^[0-9]{2}[A-Z0-9]{13}$
    if not gstin_ok: fails.append("GSTIN_INVALID")
    if gstin_ok and int(g[:2]) != C["state"]: fails.append("STATE_MISMATCH")   # place of supply
    if inv["bill_to"] != C["address"]: fails.append("ADDRESS_DRIFT")
    if C["hsn"] and not inv["hsn"]: fails.append("HSN_MISSING")
    if C["date_win"] and inv["issue"] - inv["delivery"] > DATE_WINDOW: fails.append("DATE_WINDOW")
    if C["dn"] and not inv["dn"]: fails.append("DN_MISSING")
    if C["po_cap"] and inv["po"] is not None and inv["amount"] > inv["quote"] * (1 + AMOUNT_TOL): fails.append("AMOUNT_VS_PO")
    if inv["number"] in seen: fails.append("DUPLICATE_NUMBER")
    return fails                                          # any failure → HELD, with this list
04 ·RUN ITTHE ENGINE, LIVE, ON A BOOK YOU CONTROL
SEED
AN 800-INVOICE BOOK IS GENERATED FROM THE PRINTED RULES AND VALIDATED AGAINST 40 CUSTOMER PROFILES IN YOUR BROWSER. NOTHING LEAVES THIS PAGE.
CASH DELAY AVOIDED · ₹ LAKH-DAYS

Run the book to see what the validator holds back.

FAILURES BY CHECK

Two things to try. Switch the validator off and the big number becomes the delay you are already paying for: the same 800 invoices, the same portals, nobody checking first. Then drag PO defects to ×3: held invoices climb, but only at the 70% of customers who require a PO — the other 30% never notice, which is the whole argument for a profile per customer rather than one rulebook for all.

05 ·WHAT IT FINDSTHE REFERENCE RUN · SEED 42

Failures by reason

DEFECTS INJECTED (FAINT) · FAILURES WHERE THE CUSTOMER CARES (SOLID) · REJECTIONS WITHOUT

Days to cash, with and without

INVOICES BY DAYS FROM INVOICE DATE TO PAYMENT · 5-DAY BINS
WITH THE VALIDATORWITHOUT

Rejections by customer, without the validator

TOP 10 · REJECTED OF INVOICES SENT · ₹ LAKH-DAYS OF CASH DELAYED

The requirement matrix

40 CUSTOMERS × THE RULES EACH ONE ENFORCES · BOTTOM ROWS: HELD BY THE VALIDATOR, REJECTED WITHOUT IT

The ten checks, by the numbers

The hold list, top of the pile

06 ·WATCHHOW IT RUNS WHEN NOBODY IS LOOKING
WHENWHAT HAPPENSWHO SEES IT
At draftEvery invoice the ERP creates is validated against its customer's profile before it gets a number. Any failure → held, with the list. Nothing held is sent.Billing, in the same screen they draft in. The fix is minutes, not a re-issue.
09:00 dailyThe hold list: one row per held invoice, customer, value, the failures, how long it has been held. Tie-out: drafted = sent + held. Any gap → no send run.The billing lead. A hold older than two days is a conversation, not a queue.
On any rejectionA portal rejection that still arrives is a rule the profile did not know. It becomes a line in that customer's profile the same day — the engine learns the customer, not the other way round.Finance ops; the profile diff is reviewed, not guessed.
WeeklyTwo numbers: rejections avoided and days-to-cash by customer, with and without. Profiles reviewed only when a customer's rejections move.Leadership — the WATCH stage of the Leak Ledger.
07 · THE TAKEAWAY

The customer's rules are the customer's rules; the only choice is when you find out. Ten days after sending, a rejection costs a re-issue, a re-number and a restarted clock. Ten minutes before sending, it costs a PO lookup. Write the profile down, run every check, list every failure, and the portal has nothing left to say.

THE BOOK IS SYNTHETIC AND GENERATED FROM THE RULES PRINTED IN engine.py (SEED 42; DEFECT RATES AND THE PORTAL'S PROBABILITY AS SHOWN ON THE SLIDERS). NO CLIENT DATA, NO REAL GSTINs, NO REAL CUSTOMERS. THE ENGINE LOGIC IS THE REAL SHAPE OF THE PRE-SEND CHECKS SIMRAN BUILT AFTER THE COLLECTIONS AND PAYMENT-DATE WORK, WHERE A PORTAL REJECTION WAS THE STRONGEST SINGLE SIGNAL OF A LATE PAYMENT; OUTCOME FIGURES IN THOSE CASES ARE AS REPORTED THERE. THE PORTAL'S REJECTION PROBABILITY, ITS 4–13 DAY DELAY AND THE 1–2 DAY FIX ARE ASSUMPTIONS, PRINTED. THE BROWSER ENGINE IS A LINE-FOR-LINE PORT OF THE PYTHON AND REPRODUCES results.json EXACTLY ON THE REFERENCE SETTINGS — THE STATUS LINE ABOVE SAYS SO WHEN IT DOES.

Invoices bouncing off a portal? A validator is a two-to-three-week SEAL: your customers' profiles, written down; every check run at draft; a hold list with the fix on every line — and a clock that starts once.

Start with a TRACE → engine.py README results.json The case it came from →