<?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: Swathi Rajagopal</title>
    <description>The latest articles on DEV Community by Swathi Rajagopal (@swathi_securekloud).</description>
    <link>https://dev.to/swathi_securekloud</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%2F4112474%2F5d3655cb-78af-4c9f-8c8e-3e158cd2f994.jpg</url>
      <title>DEV Community: Swathi Rajagopal</title>
      <link>https://dev.to/swathi_securekloud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/swathi_securekloud"/>
    <language>en</language>
    <item>
      <title>How does AI extract data from documents?</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:11:07 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/how-does-ai-extract-data-from-documents-1m69</link>
      <guid>https://dev.to/swathi_securekloud/how-does-ai-extract-data-from-documents-1m69</guid>
      <description>&lt;p&gt;&lt;em&gt;Templates, AI and validation: a practical look at document data extraction, with Python examples.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every business runs on documents: invoices, purchase orders, delivery receipts, bank statements. And almost every business has someone copying numbers from those documents into a spreadsheet or system.&lt;/p&gt;

&lt;p&gt;Automating that work is called document data extraction. There are two broad ways to do it: templates and AI. This article explains how each works, where each one breaks, and how to make AI output safe to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  How template-based extraction works
&lt;/h2&gt;

&lt;p&gt;Think of a stencil. Lay it over a page, and you can trace the exact shape cut into it, quickly and perfectly, every time. But only if the page lines up with the stencil.&lt;/p&gt;

&lt;p&gt;Template-based extraction works the same way. You define where each field sits, or what text pattern surrounds it, and the system pulls out whatever matches. Here's a simple rule-based version using regular expressions:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;INVOICE_RULES&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;invoice_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice No[.:]?\s*([A-Z0-9/-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Date[.:]?\s*(\d{2}/\d{2}/\d{4})&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;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total[.:]?\s*(?:Rs\.?|₹)?\s*([\d,]+\.\d{2})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_with_rules&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;rules&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Apply each rule to the document text and return what matches.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;result&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;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice No: INV-2026/0098&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Date: 14/10/2026&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Total: ₹ 48,500.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract_with_rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INVOICE_RULES&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {'invoice_number': 'INV-2026/0098', 'invoice_date': '14/10/2026', 'total': '48,500.00'}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a supplier who always sends the same invoice layout, this is fast, cheap and completely predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why templates break when document layouts change
&lt;/h2&gt;

&lt;p&gt;Now a second supplier sends an invoice that says &lt;code&gt;Bill Number&lt;/code&gt; instead of &lt;code&gt;Invoice No&lt;/code&gt;, writes the date as &lt;code&gt;14-Oct-2026&lt;/code&gt;, and labels the total &lt;code&gt;Amount Payable&lt;/code&gt;.&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="n"&gt;doc2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bill Number: B/7781&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Dated 14-Oct-2026&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Amount Payable: Rs. 12,300.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract_with_rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INVOICE_RULES&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {'invoice_number': None, 'invoice_date': None, 'total': None}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field fails. Nothing is technically wrong with the rules. The document simply doesn't match the stencil.&lt;/p&gt;

&lt;p&gt;This is the core weakness of templates. Every new layout needs a new template, and every small change a supplier makes to their format can silently break an existing one. With dozens of suppliers, banks or transporters, template maintenance becomes a job in itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AI-based extraction works
&lt;/h2&gt;

&lt;p&gt;AI-based extraction flips the approach. Instead of telling the system &lt;em&gt;where&lt;/em&gt; a field is, you describe &lt;em&gt;what&lt;/em&gt; you want, and the model finds it, whatever the layout.&lt;/p&gt;

&lt;p&gt;A good way to do this is to define a schema: the fields you need, with their types and formats.&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="n"&gt;INVOICE_SCHEMA&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;invoice_number&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;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date, formatted as YYYY-MM-DD&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;total&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;number, without currency symbols or commas&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;line_items&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;list of {description: string, amount: number}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Ask the model to return only JSON that follows the schema.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&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;Extract the following fields from the document below.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return only valid JSON. Use null for any field you cannot find.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Document:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;document_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You send the prompt to the language model of your choice and parse the JSON it returns. Both invoices above, &lt;code&gt;Invoice No&lt;/code&gt; and &lt;code&gt;Bill Number&lt;/code&gt; alike, can now be handled by the same instruction, because the model understands that they mean the same thing.&lt;/p&gt;

&lt;p&gt;Asking for normalised formats in the schema (ISO dates, plain numbers) also saves a cleanup step later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI output still needs validation
&lt;/h2&gt;

&lt;p&gt;Here's the catch. A stencil either fits or doesn't, so templates fail loudly. AI rarely fails loudly. It can return a confident, well-formatted answer that is subtly wrong: a misread digit, a total picked from the wrong line, a date in the wrong century.&lt;/p&gt;

&lt;p&gt;So never pass AI output straight into a system. Check it first:&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="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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_invoice&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a list of problems found in an extracted invoice record.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;problems&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;field&lt;/span&gt; &lt;span class="ow"&gt;in&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_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&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;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;record&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;field&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;problems&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&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="nf"&gt;strptime&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="nf"&gt;get&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_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="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;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date is not a valid date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;problems&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total is not a number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line_items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;items_sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&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;items_sum&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;problems&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line items add up to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;items_sum&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, but total is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last check is the most valuable. &lt;a href="https://dev.to/swathi_securekloud/how-does-a-bank-statement-become-a-credit-signal-2do7"&gt;A document usually contains its own cross-checks&lt;/a&gt;: line items should add up to the total, debits and credits should balance, quantities should match across pages. Using the document's internal logic to test the extraction catches errors that no format check can.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to route documents that fail validation
&lt;/h2&gt;

&lt;p&gt;Validation tells you something is wrong. The next question is what to do about it.&lt;/p&gt;

&lt;p&gt;A practical pattern is to accept clean records automatically and &lt;a href="https://www.docugenieai.com/blog/human-in-the-loop-ai-document-processing" rel="noopener noreferrer"&gt;send everything else to a person&lt;/a&gt;, with the problems listed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route&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;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate_invoice&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;problems&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;auto_approved&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;record&lt;/span&gt;&lt;span class="sh"&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="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;needs_review&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;problems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;record&lt;/span&gt;&lt;span class="sh"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reviewer then sees exactly what to check, instead of re-reading the whole document. Over time, the share of documents that go straight through grows, and people spend their time only on the cases that genuinely need judgement.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose between templates and AI
&lt;/h2&gt;

&lt;p&gt;It isn't really either-or. In practice, many production systems combine both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; for a few high-volume, fixed layouts, where speed, cost and predictability matter most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt; for the long tail of varied layouts, scans and documents you haven't seen before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; on everything, whichever method produced it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human review&lt;/strong&gt; for whatever validation flags.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are starting today with many document formats, AI-first with strong validation is usually the faster path. Templates still earn their place where a layout is fixed and volumes are high.&lt;/p&gt;

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

&lt;p&gt;A stencil is perfect when the page lines up, and useless when it doesn't. AI reads the page the way a person would, which makes it far more flexible, but it also means its work needs checking.&lt;/p&gt;

&lt;p&gt;The reliable approach is the one that combines them: describe what you need, let AI handle the variety, test every result against the document's own logic, and bring in a person when something doesn't add up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.docugenieai.com/" rel="noopener noreferrer"&gt;Document understanding SaaS platforms&lt;/a&gt; such as DocuGenie.AI apply this approach to real-world documents like bank statements, financial statements and proof of delivery (POD). But the principles above apply to any extraction pipeline you build yourself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>documentunderstanding</category>
    </item>
    <item>
      <title>How do you match a Proof of Delivery to an Invoice?</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sat, 24 Oct 2026 15:31:02 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/how-do-you-match-a-proof-of-delivery-to-an-invoice-h78</link>
      <guid>https://dev.to/swathi_securekloud/how-do-you-match-a-proof-of-delivery-to-an-invoice-h78</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical guide to the matching layer that decides whether a payment is released, held or disputed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Extracting data from a proof of delivery (POD) is only half the job.&lt;/p&gt;

&lt;p&gt;Once you know who signed, when, and how many cartons arrived, the real question follows: does this match what the supplier or transporter is billing for?&lt;/p&gt;

&lt;p&gt;That's the matching layer. It's where extracted POD data turns into a decision: release the payment, hold it, or raise a dispute. This article walks through how to build it, with small Python examples you can adapt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why matching a POD to an Invoice matters
&lt;/h2&gt;

&lt;p&gt;An invoice says what someone wants to be paid for. A POD says what was actually received.&lt;/p&gt;

&lt;p&gt;When the two agree, payment can move with confidence. When they don't, the mismatch is exactly what a finance team needs to catch before the money leaves the account, not after.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://www.docugenieai.com/industry/logistics" rel="noopener noreferrer"&gt;Indian logistics&lt;/a&gt;, the chain usually includes four documents: the supplier invoice, the delivery challan, the lorry receipt (LR) and the transporter's freight bill. The POD is the one that &lt;a href="https://medium.com/@swathiraju204/what-can-ai-read-from-a-proof-of-delivery-74736b626881" rel="noopener noreferrer"&gt;confirms what really happened at the door&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to structure extracted POD data
&lt;/h2&gt;

&lt;p&gt;Matching only works if every POD lands in the same shape, whatever format it arrived in. A simple record might look like this:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PODRecord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pod_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;invoice_number&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="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;order_number&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="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;lr_number&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="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;delivery_date&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="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;recipient&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="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;signature_present&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;qty_received&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# item code -&amp;gt; quantity
&lt;/span&gt;    &lt;span class="n"&gt;remarks&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="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;source_file&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="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;source_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses Python's built-in &lt;a href="https://docs.python.org/3/library/dataclasses.html" rel="noopener noreferrer"&gt;dataclasses&lt;/a&gt;, which keep records like this short and readable.&lt;/p&gt;

&lt;p&gt;Two fields are easy to overlook but matter later: &lt;code&gt;remarks&lt;/code&gt;, which carries notes like "2 cartons damaged", and the source reference (&lt;code&gt;source_file&lt;/code&gt; and &lt;code&gt;source_page&lt;/code&gt;), which we'll come back to.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose match keys between POD, invoice, LR and freight bill
&lt;/h2&gt;

&lt;p&gt;Think of an old cinema ticket torn in two. Each half is useless on its own, but the matching serial number proves they belong together.&lt;/p&gt;

&lt;p&gt;Match keys are those serial numbers. They're the reference numbers that appear on both documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;POD to supplier invoice:&lt;/strong&gt; invoice number or order number&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POD to delivery challan:&lt;/strong&gt; order number or challan number&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POD to LR and freight bill:&lt;/strong&gt; LR number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good approach is to try keys in order of reliability, and stop at the first clean match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pod&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return (invoice, key_used) or (None, reason).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&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;invoice_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice_number&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;order_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_number&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;lr_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr_number&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalise_ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;inv&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;normalise_ref&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&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;key&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ambiguous_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that an ambiguous match is treated as a failure, not a guess. If two invoices share the same order number, a person should decide which one the POD belongs to.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to normalise reference numbers before matching
&lt;/h2&gt;

&lt;p&gt;The same LR number can appear as &lt;code&gt;LR No. 004521&lt;/code&gt; on the POD and &lt;code&gt;lr-4521&lt;/code&gt; on the freight bill. Compared as plain strings, they don't match.&lt;/p&gt;

&lt;p&gt;Normalising references first fixes most of these near misses:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalise_ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Turn &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LR No. 004521&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; and &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lr-4521&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; into the same key: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4521&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Remove common prefixes such as 'LR No.', 'INV#' or 'PO:'
&lt;/span&gt;    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^(LR|INV|PO)[\s\-:#/.]*(NO\.?)?[\s\-:#/.]*&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="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Keep only letters and digits
&lt;/span&gt;    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^A-Z0-9]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ref&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;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be careful with rules like stripping leading zeros. They help in most cases, but check them against your own data, since some organisations use zero-padded numbers that are genuinely different.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to compare quantities and flag short, damaged or refused deliveries
&lt;/h2&gt;

&lt;p&gt;Once a POD is matched to an invoice, compare what was billed with what was received, item by item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compare_quantities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;billed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compare billed and received quantities per item code.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;issues&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;billed_qty&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;billed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;received&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;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;billed_qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;issues&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;short&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;billed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;billed_qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;billed_qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;issues&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;over&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;billed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;billed_qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;got&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;billed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;issues&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;not_billed&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;received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&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;issues&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quantities don't tell the whole story, though. Damage and refusals usually show up in the receiver's handwritten remarks, so it's worth scanning those too:&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="n"&gt;REMARK_FLAGS&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;damaged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damag|broken|torn|leak&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;refused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refus|reject|returned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flags_from_remarks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remarks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find exception keywords in free-text remarks.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remarks&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REMARK_FLAGS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keyword rules like these are a starting point. Real remarks are messy, abbreviated and sometimes in regional languages, so production systems usually add AI-based classification on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to route each exception to the right team
&lt;/h2&gt;

&lt;p&gt;A generic "exception" bucket isn't enough. Each type of problem needs a specific owner:&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="n"&gt;ROUTES&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;short&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;receiving, then accounts payable (credit note)&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;over&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;receiving&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;not_billed&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;accounts payable&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;damaged&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;receiving, then accounts payable (credit note)&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;refused&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;returns and disputes (hold invoice)&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;missing_signature&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;manual review or transporter confirmation&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;no_match&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;accounts payable exceptions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pod&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="n"&gt;match_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a release or hold decision with routed exceptions.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;exceptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;exceptions&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;compare_quantities&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty_billed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty_received&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;exceptions&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;issue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signature_present&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;exceptions&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;exceptions&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;flags_from_remarks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remarks&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;pod_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;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pod_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;matched_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;match_result&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;hold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;release&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;exceptions&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;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route_to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ROUTES&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;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accounts payable exceptions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;#page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is simple: a clean match releases the payment, and anything else holds it and &lt;a href="https://www.docugenieai.com/blog/human-in-the-loop-ai-document-processing" rel="noopener noreferrer"&gt;tells someone exactly what to look at&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why every record should keep a reference to its source document
&lt;/h2&gt;

&lt;p&gt;Look at the last line of &lt;code&gt;decide()&lt;/code&gt;. Every decision carries a pointer back to the original file and page.&lt;/p&gt;

&lt;p&gt;That one field makes the whole system defensible. When a supplier disputes a credit note, or an auditor asks why an invoice was held, the reviewer can open the signed receipt in one click instead of searching a shared drive.&lt;/p&gt;

&lt;p&gt;Without it, your structured data is faster to query but much harder to trust. It’s the same principle as making credit signals explainable: &lt;a href="https://dev.to/swathi_securekloud/how-does-a-bank-statement-become-a-credit-signal-2do7"&gt;always show your working&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Remember the torn cinema ticket. The matching layer's job is to find the two halves that belong together, check that they really fit, and raise a hand when they don't.&lt;/p&gt;

&lt;p&gt;The examples here are deliberately simple. Production systems handle &lt;a href="https://www.docugenieai.com/blog/trip-sheet-automation-google-cloud" rel="noopener noreferrer"&gt;more document types&lt;/a&gt;, smarter remark classification and fuzzier matching. But the shape stays the same: structure the data, normalise the keys, compare carefully, route every exception to an owner, and always keep the source one click away.&lt;/p&gt;

&lt;p&gt;Getting the extraction right is the first step. Document understanding SaaS solution like &lt;a href="https://www.docugenieai.com/solution/pod-automation" rel="noopener noreferrer"&gt;DocuGenie.AI's POD automation&lt;/a&gt; handle that part across PDFs, scans and phone photos. The matching layer is what turns that data into decisions.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>logistics</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>How does a bank statement become a credit signal?</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sat, 24 Oct 2026 14:23:52 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/how-does-a-bank-statement-become-a-credit-signal-2do7</link>
      <guid>https://dev.to/swathi_securekloud/how-does-a-bank-statement-become-a-credit-signal-2do7</guid>
      <description>&lt;p&gt;&lt;em&gt;Turning a messy PDF into clear, explainable signals a lender can trust.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A lender looking at a bank statement isn't really interested in the document. They want answers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this person earn regularly?&lt;/li&gt;
&lt;li&gt;Is this statement genuine?&lt;/li&gt;
&lt;li&gt;What do they already owe every month?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting from a PDF to those answers is a surprisingly interesting engineering problem. This article walks through the main steps, with small Python examples you can adapt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why extracting bank statement data is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Imagine trying to read the menu at 50 different restaurants, where every menu puts the prices, dishes and sections in a different place. That's bank statements.&lt;/p&gt;

&lt;p&gt;Each bank uses its own layout. Columns move around. Some statements combine debit and credit into one column with a sign, others split them. Narrations are cryptic, like &lt;code&gt;NEFT/CR/ACME PVT LTD/SAL OCT&lt;/code&gt;. Tables continue across pages, sometimes repeating headers halfway through.&lt;/p&gt;

&lt;p&gt;And that's the clean case. Many statements arrive as scanned copies or phone photos, &lt;a href="https://dev.to/swathi_securekloud/what-does-intelligent-actually-means-in-intelligent-document-automation-ida-3600"&gt;which need OCR before any of this can start&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So the first goal is simple to state and hard to do: turn every statement, whatever its format, into one consistent structure. For example:&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="n"&gt;txn&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;date&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;2026-09-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# ISO format
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;narration&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;NEFT/CR/ACME PVT LTD/SAL SEP&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;type&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;CR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;# "CR" or "DR"
&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="mf"&gt;52000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;61850.50&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;Everything after this step depends on getting it right.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check whether a statement has been tampered with
&lt;/h2&gt;

&lt;p&gt;Before analysing anything, it's worth asking whether the numbers can be trusted.&lt;/p&gt;

&lt;p&gt;One useful check uses the running balance. In a genuine statement, each row's balance should equal the previous balance plus the credit, or minus the debit. When someone edits an amount to inflate their income, they often forget to fix every balance after it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_balance_breaks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txns&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Flag rows where the running balance doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t add up.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;breaks&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;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
        &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cur&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="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balance&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;change&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;breaks&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stated_balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;breaks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A break isn't automatic proof of fraud. Missing pages, reversed transactions or extraction errors can cause one too. That's why this should &lt;strong&gt;flag&lt;/strong&gt; a statement for review, not reject it.&lt;/p&gt;

&lt;p&gt;Real-world tampering checks go further, for example looking at PDF metadata, font inconsistencies and duplicate transaction IDs. But balance continuity alone catches a surprising amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to detect existing loan EMIs from transactions
&lt;/h2&gt;

&lt;p&gt;Existing EMIs tell a lender how much of a borrower's income is already committed.&lt;/p&gt;

&lt;p&gt;EMIs have a recognisable pattern: a debit to the same payee, for roughly the same amount, every month. Here's a simple way to find recurring debits:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_recurring_debits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_months&lt;/span&gt;&lt;span class="o"&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;tolerance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find debits to the same payee with a similar amount across months.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;txns&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;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Strip reference numbers so the same payee groups together
&lt;/span&gt;            &lt;span class="n"&gt;payee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d+&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="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;narration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;payee&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;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;recurring&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;payee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;7&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;amounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;similar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;all&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;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;avg&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;amounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_months&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;similar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;recurring&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monthly_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;months_seen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&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="n"&gt;recurring&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches EMIs, but also rent, SIPs and subscriptions, which are all recurring too. Telling them apart is where &lt;a href="https://www.docugenieai.com/blog/transaction-categorization-bank-statement-analysis" rel="noopener noreferrer"&gt;transaction categorization&lt;/a&gt; comes in, using narration keywords, known lender names and amount patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure income stability
&lt;/h2&gt;

&lt;p&gt;Two applicants can both earn ₹50,000 a month on average. One gets exactly ₹50,000 every month. The other gets ₹90,000 one month and ₹10,000 the next.&lt;/p&gt;

&lt;p&gt;The average hides the difference. A stability measure shows it.&lt;/p&gt;

&lt;p&gt;A simple option is to look at monthly income credits and compare how much they vary relative to their average (the &lt;a href="https://en.wikipedia.org/wiki/Coefficient_of_variation" rel="noopener noreferrer"&gt;coefficient of variation&lt;/a&gt;):&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pstdev&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;income_stability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;income_txns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a 0-1 score: closer to 1 means steadier monthly income.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;monthly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;income_txns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;monthly&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;t&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="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monthly&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# not enough history to judge
&lt;/span&gt;
    &lt;span class="n"&gt;variation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pstdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;variation&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code uses &lt;a href="https://docs.python.org/3/library/statistics.html" rel="noopener noreferrer"&gt;&lt;code&gt;pstdev&lt;/code&gt;&lt;/a&gt; from Python's built-in statistics module.&lt;/p&gt;

&lt;p&gt;Here &lt;code&gt;income_txns&lt;/code&gt; should contain only genuine income credits, such as salary or business receipts. Including transfers between the borrower's own accounts would distort the result, which is another reason categorization matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why every credit signal needs an explanation
&lt;/h2&gt;

&lt;p&gt;A score with no explanation is hard to trust and harder to defend.&lt;/p&gt;

&lt;p&gt;If a credit officer sees "income stability: 0.42," their next question is why. If the system can answer "income dropped sharply in July and August," the officer can ask the borrower about it. Maybe it was a seasonal business, or a job change.&lt;/p&gt;

&lt;p&gt;So it's worth designing every signal to carry its evidence:&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="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;existing_emi_load&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;18500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payee&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;ABC FINANCE EMI&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;monthly_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;months_seen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payee&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;XYZ BANK LOAN&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;monthly_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;6000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;months_seen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="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;This matters beyond good practice. Lending decisions affect people's lives, and regulators increasingly expect lenders to explain them. Signals that point back to specific transactions make that possible.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For the business side of this topic, see &lt;a href="https://medium.com/@swathiraju204/why-are-indian-lenders-looking-beyond-the-credit-score-185d8f0fac2e" rel="noopener noreferrer"&gt;Why are Indian lenders looking beyond the credit score?&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full pipeline looks like
&lt;/h2&gt;

&lt;p&gt;Putting it together, a bank statement analysis pipeline usually runs in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract:&lt;/strong&gt; read the PDF or scan and handle each bank's layout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalise:&lt;/strong&gt; convert every row into one consistent structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify:&lt;/strong&gt; run integrity checks like balance continuity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categorise:&lt;/strong&gt; label transactions as salary, EMI, rent, transfers and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derive signals:&lt;/strong&gt; income, stability, obligations, risk flags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explain:&lt;/strong&gt; attach the evidence behind every signal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 and 2 are often the hardest to get right at scale, given how many formats exist. Production tools such as &lt;a href="https://www.docugenieai.com/solution/bank-statement-analyzer" rel="noopener noreferrer"&gt;DocuGenie.AI's Bank Statement Analyzer&lt;/a&gt; handle that variety across Indian banks, but the core logic of steps 3 to 6 is the same as in the examples above.&lt;/p&gt;

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

&lt;p&gt;A bank statement is a record of how someone actually earns and spends. The engineering challenge is turning that record into signals that are accurate, checkable and explainable.&lt;/p&gt;

&lt;p&gt;The examples here are deliberately simple. Real systems add more bank formats, smarter categorisation and stronger fraud checks. But the principle holds: start with clean data, verify it, derive signals, and always show your working.&lt;/p&gt;

&lt;p&gt;That last step is what turns a number into something a lender can trust.&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>ai</category>
      <category>datascience</category>
      <category>python</category>
    </item>
    <item>
      <title>Cloud Cost Optimization for Developers: 5 Engineering Practices That Matter</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:47:58 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/cloud-cost-optimization-for-developers-5-engineering-practices-that-matter-5810</link>
      <guid>https://dev.to/swathi_securekloud/cloud-cost-optimization-for-developers-5-engineering-practices-that-matter-5810</guid>
      <description>&lt;p&gt;Cloud costs are influenced by more than infrastructure pricing. Application architecture, resource allocation, deployment choices, and data storage all affect how much an organisation spends on cloud services.&lt;/p&gt;

&lt;p&gt;Developers make many of these decisions during everyday engineering work. A service that runs continuously, a database that stores unused records, or a deployment that provisions more resources than required can increase costs over time. Cloud cost optimization is therefore not limited to finance or infrastructure teams. It is also an engineering responsibility. For developers working with AWS, Microsoft Azure, or Google Cloud, five practices can help control costs without compromising application performance or reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Right-Size Resources Based on Actual Usage
&lt;/h2&gt;

&lt;p&gt;Cloud resources are often provisioned with more capacity than applications need. This can happen when teams select larger instances to avoid performance issues or continue using configurations established during initial deployment. Overprovisioning creates unnecessary costs, particularly for services running around the clock. &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/welcome.html?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Right-sizing&lt;/a&gt; involves comparing allocated resources with actual application requirements.&lt;/p&gt;

&lt;p&gt;Developers should review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU utilisation:&lt;/strong&gt; Identify instances consistently operating below their available capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory usage:&lt;/strong&gt; Check whether applications need their current memory allocation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage performance:&lt;/strong&gt; Match storage capacity and performance tiers to workload requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic patterns:&lt;/strong&gt; Identify services with predictable periods of low activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud monitoring tools can provide the metrics needed for these assessments. However, average utilisation alone is not enough. Teams should also consider peak demand, memory pressure, response times, and application dependencies before reducing capacity. The objective is to provision enough resources for reliable performance without paying for capacity that remains unused.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use Autoscaling to Match Workload Demand
&lt;/h2&gt;

&lt;p&gt;Applications rarely experience the same traffic throughout the day. An API may receive heavy traffic during business hours and relatively few requests overnight. Running the same number of compute instances at all times can lead to unnecessary spending. Autoscaling allows infrastructure capacity to adjust based on workload demand. Depending on the platform, scaling decisions can use metrics such as CPU utilisation, request volume, queue depth, or application-specific signals.&lt;/p&gt;

&lt;p&gt;For example, a web application might scale out when request traffic increases and scale in when demand falls.&lt;/p&gt;

&lt;p&gt;Developers should consider three factors when configuring autoscaling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scaling thresholds:&lt;/strong&gt; Define when additional capacity is required and when resources can be reduced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application readiness:&lt;/strong&gt; Ensure services can start, become healthy, and handle requests without excessive delay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum capacity:&lt;/strong&gt; Maintain enough resources to meet availability and response-time requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Autoscaling is not automatically cost-efficient. Poorly configured thresholds, excessive minimum capacity, or frequent scaling activity can reduce its benefits. It works best when supported by realistic load testing and continuous monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Apply Storage Lifecycle Policies
&lt;/h2&gt;

&lt;p&gt;Storage costs often grow gradually as applications generate logs, backups, uploaded files, and historical records. Some of this data must remain immediately accessible. Other information may be required only for compliance, recovery, or occasional reference. Keeping everything in the same storage tier can increase costs unnecessarily. Developers can reduce this overhead by defining lifecycle policies based on how data is used.&lt;/p&gt;

&lt;p&gt;Common practices include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moving infrequently accessed objects to lower-cost storage tiers.&lt;/li&gt;
&lt;li&gt;Setting retention periods for temporary files and application logs.&lt;/li&gt;
&lt;li&gt;Removing obsolete object versions when they are no longer required.&lt;/li&gt;
&lt;li&gt;Reviewing backup retention against recovery and compliance needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, application logs used for daily troubleshooting may not need to remain in high-cost storage indefinitely. Older records can move to an appropriate archival tier when access requirements allow. Lifecycle policies should account for retrieval charges, minimum storage durations, and recovery requirements. A cheaper storage tier may not be economical if data must be retrieved frequently. Storage optimization is most effective when retention rules are built into application and infrastructure design rather than applied after costs have accumulated.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Eliminate Idle Resources Across Development Environments
&lt;/h2&gt;

&lt;p&gt;Development, testing, and staging environments are essential for software delivery. However, they can also become a source of avoidable cloud spending. Virtual machines, databases, test clusters, and temporary infrastructure may remain active long after they are needed. Unlike production workloads, many non-production environments do not require continuous availability. Developers can address this through automated resource management.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled shutdowns:&lt;/strong&gt; Stop eligible development and testing resources outside working hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment expiration:&lt;/strong&gt; Assign expiry times to temporary environments created for testing or feature development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource tagging:&lt;/strong&gt; Identify ownership, application, environment, and cost allocation for deployed resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure cleanup:&lt;/strong&gt; Remove unused disks, snapshots, load balancers, and other resources left behind after deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infrastructure as Code tools can help teams create repeatable environments and remove them when testing is complete. Before automating shutdowns, teams should account for shared environments, scheduled jobs, data persistence, and dependencies. The goal is to prevent unused infrastructure from consuming resources simply because no one has been assigned to turn it off.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Bring Cloud Cost Visibility into Development Workflows
&lt;/h2&gt;

&lt;p&gt;Developers routinely monitor application performance, errors, and deployment health. Cloud spending deserves similar visibility. When cost information is available only through monthly billing reports, teams may discover inefficient resource usage long after it begins. Integrating cost awareness into engineering workflows helps identify problems earlier. A structured &lt;a href="https://www.securekloud.com/blog/cloudops-complexity-to-operational-clarity/" rel="noopener noreferrer"&gt;CloudOps approach&lt;/a&gt; also helps teams connect infrastructure monitoring, automation, and resource management with day-to-day operations.&lt;/p&gt;

&lt;p&gt;Practical approaches include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost estimates during infrastructure changes:&lt;/strong&gt; Review the expected cost impact of new compute, database, storage, and networking resources before deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget alerts:&lt;/strong&gt; Notify responsible teams when spending approaches defined limits or increases unexpectedly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-level visibility:&lt;/strong&gt; Use consistent tagging to associate cloud costs with applications, environments, and teams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-aware architecture reviews:&lt;/strong&gt; Consider infrastructure consumption alongside performance, security, and reliability when evaluating design changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular usage reviews:&lt;/strong&gt; Compare resource utilisation and spending trends after major releases or workload changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a new application feature may increase database queries or outbound data transfer. Monitoring those changes alongside deployment activity can help teams identify the source of higher costs. Cost visibility does not mean developers should always select the cheapest option. Engineering teams must balance spending with performance, resilience, security, and operational requirements. The objective is to make cloud cost a measurable part of engineering decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FinOps Matters for Development Teams
&lt;/h2&gt;

&lt;p&gt;These practices become more effective when developers, cloud operations teams, and finance teams work with shared cost information. This is the purpose of FinOps: helping organisations make informed decisions about cloud spending through collaboration, accountability, and continuous optimization. Developers contribute by understanding how application behaviour affects resource consumption. Cloud teams provide visibility into infrastructure usage and optimization opportunities. Finance teams help connect spending with budgets and business priorities. Together, these functions can identify cost inefficiencies without treating cloud optimization as a one-time exercise. FinOps works best when cost ownership is clear and engineering teams have access to information they can act on. &lt;a href="https://www.securekloud.com/blog/finops-maturity-model-crawl-walk-run/" rel="noopener noreferrer"&gt;Cloud cost maturity&lt;/a&gt; also depends on how consistently teams track spending, assign ownership, and act on optimization opportunities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SecureKloud Fits into Cloud Cost Optimization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.securekloud.com/" rel="noopener noreferrer"&gt;SecureKloud Technologies&lt;/a&gt; supports enterprises with cloud managed services, including infrastructure monitoring, cloud operations, security oversight, and cost optimization. Cloud cost management involves more than identifying underutilised resources. Organisations also need to evaluate workload performance, operational requirements, and opportunities to improve resource efficiency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.securekloud.com/cloud-managed-services" rel="noopener noreferrer"&gt;SecureKloud's cloud capabilities&lt;/a&gt; support enterprises in managing these priorities across their cloud environments. For development teams, this reinforces the value of connecting engineering decisions with ongoing cloud operations and cost visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Cloud cost optimization starts with understanding how applications consume infrastructure resources. Right-sizing helps reduce overprovisioning. Autoscaling aligns capacity with demand. Storage lifecycle policies control long-term data costs. Automated cleanup reduces idle infrastructure, while cost visibility helps developers identify inefficient resource usage earlier. These practices work best when they become part of regular engineering workflows. Developers do not need to manage every aspect of cloud billing. But they should understand how their architecture, deployment, and resource choices influence spending. Cost-efficient cloud engineering is about using the right resources for the workload, at the right time, without compromising reliability.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>aws</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>Building Reliable Document Processing Pipelines with LLMs</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:22:38 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/building-reliable-document-processing-pipelines-with-llms-2b96</link>
      <guid>https://dev.to/swathi_securekloud/building-reliable-document-processing-pipelines-with-llms-2b96</guid>
      <description>&lt;p&gt;&lt;a href="https://www.securekloud.com/generative-ai-services" rel="noopener noreferrer"&gt;Large Language Models (LLMs)&lt;/a&gt; have made it easier to extract structured information from unstructured documents. A developer can send an invoice, bank statement, or application form to a document-capable model and receive the extracted fields as JSON. A valid JSON response does not guarantee that the extracted information is accurate. The model may miss a field, misread an amount, or return information that looks valid but does not match the source document. These errors become significant when extracted data feeds financial systems, approval workflows, or enterprise applications.&lt;/p&gt;

&lt;p&gt;A reliable LLM document processing pipeline needs more than a prompt and an API call. It needs controlled in&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;gestion, structured extraction, validation, exception handling, and a way to review uncertain results.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a Controlled Document Ingestion Layer
&lt;/h2&gt;

&lt;p&gt;Document processing begins before the LLM receives any content. Enterprise documents arrive through email, APIs, file uploads, and connected systems. They may be digital PDFs, scanned images, photographs, or documents containing multiple pages and layouts.&lt;/p&gt;

&lt;p&gt;An ingestion layer should identify the file type, check whether the document can be processed, and prepare it for extraction. A typical ingestion workflow includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;File validation:&lt;/strong&gt; Check supported formats, file size, and document integrity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.docugenieai.com/" rel="noopener noreferrer"&gt;Document classification&lt;/a&gt;:&lt;/strong&gt; Identify whether the file is an invoice, bank statement, KYC document, or another supported type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preprocessing:&lt;/strong&gt; Apply image correction, page separation, or OCR when required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document tracking:&lt;/strong&gt; Assign an identifier to each document for processing and audit purposes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every document requires the same preprocessing. A digital PDF with an embedded text layer may follow a different path from a poorly scanned bank statement. The ingestion layer should preserve the original document so that extracted values can be checked against their source.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Extract Data into a Defined Schema
&lt;/h2&gt;

&lt;p&gt;LLMs can identify information from documents even when layouts vary. This makes them useful for extracting fields from semi-structured and unstructured content. However, extraction becomes difficult to manage when the output structure changes between requests.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;defined schema&lt;/strong&gt; helps downstream applications receive consistent data.&lt;/p&gt;

&lt;p&gt;Consider a simple invoice extraction example:&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;"invoice_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INV-1024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoice_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vendor_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Supplies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subtotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1800.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11800.00&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;The extraction request should specify the required fields, expected data types, and how missing information must be represented.&lt;/p&gt;

&lt;p&gt;For example, if the invoice date is not present, the model should return &lt;code&gt;null&lt;/code&gt; rather than generate a likely date. Where supported, structured-output APIs or schema-constrained generation can help enforce the required JSON structure. But a schema-conforming response is not necessarily factually correct. The values must still be checked against the document.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Validate the Extracted Information
&lt;/h2&gt;

&lt;p&gt;Validation is one of the most important stages in a production document extraction pipeline. It should happen after extraction and before the data is sent to downstream systems. There are three useful levels of validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema validation
&lt;/h3&gt;

&lt;p&gt;Check whether the response contains the required fields and whether their values match the expected data types.&lt;/p&gt;

&lt;p&gt;For instance, &lt;code&gt;total_amount&lt;/code&gt; should contain a numeric value, while &lt;code&gt;invoice_date&lt;/code&gt; should follow the accepted date format.&lt;/p&gt;

&lt;p&gt;A JSON Schema validator or a typed model can handle these checks without relying on another LLM response.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.securekloud.com/robotic-process-automation" rel="noopener noreferrer"&gt;Business rule validation&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Check whether the extracted values satisfy the rules of the document or workflow. For an invoice, one basic check is:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_invoice&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="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subtotal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tax_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="n"&gt;expected_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_total&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.01&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;This simplified example assumes there are no discounts, shipping charges, or other adjustments. A production validator must account for the actual invoice structure and rounding rules. The important principle is that financial calculations should use deterministic logic rather than asking an LLM to confirm its own arithmetic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-field and source validation
&lt;/h3&gt;

&lt;p&gt;Some fields must be consistent with other information. An invoice number may need to match a purchase order reference. A vendor identifier may need to match an approved supplier record. For financial documents, extracted amounts and transaction details may also need to be reconciled against source records. These checks help identify errors that a valid JSON structure cannot detect.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat Confidence as a Review Signal
&lt;/h2&gt;

&lt;p&gt;LLM responses can appear confident even when extracted values are incorrect. A well-formed JSON should not be interpreted as proof of extraction accuracy. Where confidence signals are available, they should be evaluated against real validation results. These may include field-level confidence from an OCR engine, extraction checks, source matching, or other measurable indicators.&lt;/p&gt;

&lt;p&gt;For example, a pipeline could apply review rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required field missing: send for review.&lt;/li&gt;
&lt;li&gt;Invoice total does not reconcile: flag as an exception.&lt;/li&gt;
&lt;li&gt;Vendor details conflict with the supplier master: request verification.&lt;/li&gt;
&lt;li&gt;Source evidence is insufficient: mark the field as unresolved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules are more useful than treating a model-generated confidence score as a reliable probability. If confidence thresholds are used, teams should calibrate them against labelled test documents and monitor their performance over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Build Exception Handling into the Pipeline
&lt;/h2&gt;

&lt;p&gt;Not every document will pass validation. A scanned invoice may contain unreadable figures. A bank statement may have missing pages. A document may use a layout that the extraction process has not handled correctly. The pipeline needs a defined response to these failures.&lt;/p&gt;

&lt;p&gt;A basic workflow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Received
        |
        v
File Validation &amp;amp; Classification
        |
        v
Preprocessing / OCR (if needed)
        |
        v
LLM-Based Extraction
        |
        v
Schema &amp;amp; Business Validation
        |
        +---- Valid ----&amp;gt; Approved Structured Data
        |
        +---- Invalid --&amp;gt; Exception Queue
                              |
                              v
                         Human Review
                              |
                              v
                       Corrected Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified architecture. In production, some failures may be resolved through automated retries or alternative extraction methods.&lt;/p&gt;

&lt;p&gt;Retries should address identifiable problems, such as a temporary API failure or an invalid output format. Repeating the same request indefinitely will not necessarily fix an incorrect extracted value.&lt;/p&gt;

&lt;p&gt;Each exception should include enough information for a reviewer to understand what failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep Human Review Focused on Exceptions
&lt;/h2&gt;

&lt;p&gt;Human-in-the-loop processing does not mean asking someone to verify every extracted field. It means directing uncertain or inconsistent information to the appropriate reviewer. A review interface should display the original document alongside the extracted values and validation findings. Reviewers should be able to correct fields, confirm exceptions, and submit the verified result. The system should record what was changed, who reviewed it, and when the correction occurred. This creates an audit trail and provides useful feedback for improving extraction performance. Over time, teams can examine recurring exceptions to identify problems with document quality, extraction instructions, or validation rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Design for Production Reliability
&lt;/h2&gt;

&lt;p&gt;A document processing pipeline must also operate reliably beyond individual extraction requests. Developers should consider several engineering requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous processing:&lt;/strong&gt; Use queues or background workers for large documents and high-volume workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency:&lt;/strong&gt; Prevent duplicate processing or downstream updates when requests are retried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version control:&lt;/strong&gt; Track changes to prompts, schemas, extraction models, and validation rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability:&lt;/strong&gt; Monitor processing latency, extraction failures, validation errors, exception rates, and operational costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data security:&lt;/strong&gt; Protect sensitive documents through access controls, encryption, appropriate retention policies, and secure handling of model inputs and outputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; Evaluate the pipeline using representative documents, including poor scans, missing fields, unusual layouts, and conflicting information.&lt;/p&gt;

&lt;p&gt;Production performance should be measured at the field and document levels. A successful API response alone is not a meaningful measure of extraction quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where DocuGenie.AI™ Fits into Enterprise Document Automation
&lt;/h2&gt;

&lt;p&gt;DocuGenie.AI™ focuses on intelligent document automation for enterprise workflows. The platform supports document ingestion, classification, data extraction, validation, and exception-based review across use cases such as &lt;a href="https://www.docugenieai.com/solution/bank-statement-analyzer" rel="noopener noreferrer"&gt;bank statement analysis&lt;/a&gt;, KYC processing, lending document checks, and invoice automation. Its approach reflects an important requirement in enterprise document processing: extracted information must be usable within business workflows, not simply returned as text or JSON. The architecture discussed in this article is a general approach to building LLM-enabled document processing systems. It does not imply that DocuGenie.AI™ uses a particular LLM architecture or implementation internally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;LLMs can simplify structured data extraction from documents, but reliable automation depends on the systems around them. Controlled ingestion prepares the input. Defined schemas make the output predictable. Validation checks the extracted information. Exception handling manages failures, while human review resolves cases that require judgment. For developers building enterprise document processing systems, these components are essential to moving from a working prototype to a dependable production workflow. The goal is not just to extract data from documents. It is to deliver validated, traceable information that downstream applications can use reliably.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>documentprocessing</category>
    </item>
    <item>
      <title>How AI bank statement analyzer enhances credit underwriting</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:51:31 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/how-ai-bank-statement-analyzer-enhances-credit-underwriting-4264</link>
      <guid>https://dev.to/swathi_securekloud/how-ai-bank-statement-analyzer-enhances-credit-underwriting-4264</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;An AI bank statement analyzer does four things a credit analyst used to do by hand. It reads the statement, understands each transaction, measures the borrower's financial behaviour, and checks the document for tampering.&lt;/p&gt;

&lt;p&gt;The lender gets an underwriting-ready view in minutes instead of hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with the analyst's desk
&lt;/h2&gt;

&lt;p&gt;Now, picture a credit analyst at a &lt;a href="https://www.docugenieai.com/industry/banking-and-financial-services" rel="noopener noreferrer"&gt;lending company&lt;/a&gt; on a Monday morning. 25 loan files are waiting. Each one has 12 months of bank statements.&lt;/p&gt;

&lt;p&gt;Some are clean PDFs, some are scans. One is a phone photo taken at an angle. For each file, she reads every line, copies figures into a spreadsheet, tags each transaction, totals the income, subtracts the EMIs, and looks for anything suspicious.&lt;/p&gt;

&lt;p&gt;She is good at it. But she is one person, and the queue does not stop. That desk is exactly what an AI bank statement analyzer is built to support. Here is what the "AI" actually does at each step.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. It reads any statement of any quality
&lt;/h2&gt;

&lt;p&gt;Older parsers work like a stencil. They fit one bank's layout and fail on the next.&lt;/p&gt;

&lt;p&gt;AI-based extraction works more like a person who has seen thousands of statements. It finds the transaction table on the page, works out which column is the date and which is the balance, joins narrations that wrap across lines, and reads scans and photos through OCR.&lt;/p&gt;

&lt;p&gt;The result: every statement, from any bank, becomes the same clean list of transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. It understands what each transaction means
&lt;/h2&gt;

&lt;p&gt;A line like &lt;code&gt;NEFT/N123456/ABC TRADERS/INV2231&lt;/code&gt; means nothing to a spreadsheet. To an experienced analyst, it is clearly a customer paying against an invoice.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://www.securekloud.com/robotic-process-automation" rel="noopener noreferrer"&gt;Natural Language Processing&lt;/a&gt; does its work. The analyzer reads each narration and labels it: salary, business receipt, EMI, rent, cash withdrawal, transfer to self, or bounced payment.&lt;/p&gt;

&lt;p&gt;Simple patterns are easy. The hard cases, such as a UPI transfer that is actually business income, are where trained models beat fixed rules. They learn from context like the amount, how often it repeats, and who the counterparty is.&lt;/p&gt;

&lt;p&gt;This step matters more than it looks. One mislabelled transfer can push income up or down and change how much a borrower is eligible for.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. It turns transactions into underwriting signals
&lt;/h2&gt;

&lt;p&gt;Once every transaction has a label, the analyzer can answer the questions an underwriter actually asks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Underwriting question&lt;/th&gt;
&lt;th&gt;What the analyzer measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is income regular?&lt;/td&gt;
&lt;td&gt;Months with salary or business credits, and how much they vary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How much is already committed?&lt;/td&gt;
&lt;td&gt;Monthly EMIs, rent and other recurring debits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the account have a cushion?&lt;/td&gt;
&lt;td&gt;Average balance, and how often it runs close to zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has the borrower missed payments?&lt;/td&gt;
&lt;td&gt;Bounced cheques and returned NACH or ECS debits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is cash flow healthy?&lt;/td&gt;
&lt;td&gt;Inflows against outflows, month by month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Instead of a 40-page statement, the analyst gets a short report of these signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. It checks whether the statement can be trusted
&lt;/h2&gt;

&lt;p&gt;A doctored statement can look perfect to the eye, especially late on a busy afternoon.&lt;/p&gt;

&lt;p&gt;An AI analyzer checks every file the same way, every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Balance continuity.&lt;/strong&gt; Each balance should equal the previous one plus credits minus debits. An edited amount breaks the chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document traces.&lt;/strong&gt; A PDF re-saved in an editing tool usually leaves different metadata from one generated by a bank's system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suspicious patterns.&lt;/strong&gt; Money moving in circles between related accounts, or large round deposits just before the loan application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is proof on its own. Together, they tell the analyst exactly where to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for the Lender
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed.&lt;/strong&gt; Hours of manual reading become minutes of review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency.&lt;/strong&gt; Two analysts looking at the same statement get the same income figure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale.&lt;/strong&gt; More applications can be processed without hiring in proportion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus.&lt;/strong&gt; The analyst's time goes to judgement calls, not data entry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lending decision stays with people. AI takes away the part of the job that was never really judgement.&lt;/p&gt;

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

&lt;p&gt;An &lt;a href="https://www.docugenieai.com/solution/bank-statement-analyzer" rel="noopener noreferrer"&gt;AI bank statement analyzer&lt;/a&gt; is not a black box that approves loans. It is four careful steps: read, understand, measure, verify. Each one removes a slow, error-prone manual task from the credit desk.&lt;/p&gt;

&lt;p&gt;If you would like to see these steps in action, here is a look at one &lt;a href="https://docugenieai.com/solution/bank-statement-analyzer" rel="noopener noreferrer"&gt;AI bank statement analyzer&lt;/a&gt; built for lenders.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>bankstatementanalysis</category>
      <category>automation</category>
      <category>creditunderwriting</category>
    </item>
    <item>
      <title>What actually happens when AI reads a bank statement for risk signals?</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sun, 27 Sep 2026 15:06:49 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/what-actually-happens-when-ai-reads-a-bank-statement-for-risk-signals-22mg</link>
      <guid>https://dev.to/swathi_securekloud/what-actually-happens-when-ai-reads-a-bank-statement-for-risk-signals-22mg</guid>
      <description>&lt;p&gt;“AI analyzes bank statements for risk” sounds fairly straightforward. But there is a lot that needs to happen between receiving a bank statement and identifying a risk signal that a credit team can actually use.&lt;/p&gt;

&lt;p&gt;The first challenge is the document itself. Bank statements do not always arrive as clean, digital PDFs. They can be scanned images, screenshots, photographs, or even scanned passbook pages. Formats vary between banks, and document quality can vary just as much.&lt;/p&gt;

&lt;p&gt;So, before any financial analysis begins, the system needs to identify what it is working with. The document has to be classified correctly before the relevant information can be extracted.&lt;/p&gt;

&lt;p&gt;This is also where OCR alone reaches its limit. OCR can recognize the characters printed on a page, but a bank statement is more than a collection of characters. A number could represent a debit, credit, balance, date, or something else entirely depending on where it appears and how the statement is structured.&lt;/p&gt;

&lt;p&gt;The system therefore needs to understand the layout and relationships within the document. It needs to connect a transaction date with its description, amount, transaction type, and corresponding balance. This is what turns extracted text into structured financial information.&lt;/p&gt;

&lt;p&gt;Once the transactions are structured, they can be categorized. &lt;a href="https://www.docugenieai.com/blog/transaction-categorization-bank-statement-analysis" rel="noopener noreferrer"&gt;Salary credits, EMI payments, UPI transfers, business inflows, reversals, bank charges&lt;/a&gt;, and other transactions begin to reveal patterns in financial behaviour.&lt;/p&gt;

&lt;p&gt;Bank statement analysis starts moving beyond extraction. The system can examine cash flow patterns, recurring obligations, income behaviour, unusual transactions, and other financial signals. But even then, a bank statement may tell only one part of the story. &lt;/p&gt;

&lt;p&gt;Some risk signals become visible only when information is compared across the borrower file. &lt;a href="https://www.docugenieai.com/solution/financial-statement-analyzer" rel="noopener noreferrer"&gt;Income reflected through banking activity may need to be checked against declared income or supporting financial documents&lt;/a&gt;. Information appearing in one document may need to be validated against another.&lt;/p&gt;

&lt;p&gt;Cross-validation matters because a document can look perfectly reasonable on its own while still contradicting information elsewhere in the application.&lt;/p&gt;

&lt;p&gt;AI can also help surface anomalies that deserve closer investigation. These could include unusual transaction patterns, sudden pre-application deposit spikes, possible round-tripping, balances that do not reconcile, duplicate transactions, or indicators of document tampering.&lt;/p&gt;

&lt;p&gt;An anomaly, however, is not automatically fraud. It is a signal that gives the credit or risk team a reason to look more closely.&lt;/p&gt;

&lt;p&gt;And that brings us to the final part of the process. The output should not simply say “risk detected.” It should give the person reviewing the application enough context to understand what was flagged and, where possible, trace that observation back to the underlying information.&lt;/p&gt;

&lt;p&gt;The entire process is connected. Poor document classification affects extraction. Poor extraction affects transaction categorization. Incorrect transaction data affects analysis, and cross-validation depends on reliable information being available across the borrower file.&lt;/p&gt;

&lt;p&gt;That is why bank statement analysis is much more than running OCR over a financial document. The difficult part is not simply reading what is printed on the statement. It is &lt;a href="https://www.docugenieai.com/blog/credit-appraisal-memorandum-cam-analysis-automation" rel="noopener noreferrer"&gt;turning that information into financial context that a credit team can investigate and use&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is also the approach behind capabilities such as &lt;a href="https://www.docugenieai.com/solution/bank-statement-analyzer" rel="noopener noreferrer"&gt;DocuGenie.AI™'s Bank Statement Analyzer&lt;/a&gt;, where document understanding, transaction analysis, validation, and risk signals come together as part of the lending workflow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fintech</category>
      <category>ocr</category>
      <category>automation</category>
    </item>
    <item>
      <title>Document Understanding vs Intelligent Document Processing—What’s the Difference?</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sun, 27 Sep 2026 14:41:20 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/document-understanding-vs-intelligent-document-processing-whats-the-difference-3i02</link>
      <guid>https://dev.to/swathi_securekloud/document-understanding-vs-intelligent-document-processing-whats-the-difference-3i02</guid>
      <description>&lt;p&gt;Document Understanding and Intelligent Document Processing often appear in the same conversation. Sometimes, they are even used as though they mean the same thing, but, they don't.&lt;/p&gt;

&lt;p&gt;Document Understanding helps AI interpret what a document contains and what that information means. Intelligent Document Processing, or IDP, takes that understanding further by using the information within a larger business process.&lt;/p&gt;

&lt;p&gt;The distinction matters because understanding a document is only one part of what enterprises need to do with it. A document may need to be identified, read, interpreted, validated, compared with other information, reviewed when something looks wrong, sent to another system, and eventually used to trigger an action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/swathi_securekloud/what-is-document-understanding-how-ai-learns-to-read-beyond-the-text-29e4"&gt;Document Understanding begins with the document itself&lt;/a&gt;. A machine does not naturally see a document the way a person does. To software, a page can initially be little more than pixels, characters, coordinates, and visual elements.&lt;/p&gt;

&lt;p&gt;OCR helps convert visible characters into machine-readable text, but recognizing text is not the same as understanding what that text represents.&lt;/p&gt;

&lt;p&gt;Say, we consider a bank statement. It may contain an account holder's details, account number, statement period, opening and closing balances, and hundreds of transactions arranged across rows and columns. Reading every word and number is useful, but the system also needs to recognize that a particular value represents a closing balance, another represents a debit, and a transaction description belongs to a specific date and amount.&lt;/p&gt;

&lt;p&gt;That is where Document Understanding comes in. It combines technologies such as OCR, computer vision, Natural Language Processing (NLP), &lt;a href="https://www.securekloud.com/ai-ml-services" rel="noopener noreferrer"&gt;machine learning&lt;/a&gt;, and layout analysis to interpret document structure and context. Modern approaches may also use multimodal models that work across textual and visual information.&lt;/p&gt;

&lt;p&gt;This allows a system to move beyond extracting characters from a page. It can classify the document, identify fields and entities, understand tables and sections, interpret the layout, and preserve relationships between different pieces of information.&lt;/p&gt;

&lt;p&gt;But an enterprise process rarely ends once the document has been understood.&lt;/p&gt;

&lt;p&gt;The extracted information may need to be validated against business rules or checked against information in another document or system. Low-confidence fields may need human verification. Validated information may need to move into an enterprise application, while an exception may need to follow a completely different route.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.docugenieai.com/blog/intelligent-document-automation-enterprise" rel="noopener noreferrer"&gt;This broader operational layer is where Intelligent Document Processing comes in&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A simplified IDP flow could look like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ingest → Classify → Understand → Extract → Validate → Review Exceptions → Integrate → Trigger Workflow&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Document Understanding sits within this larger journey. It provides the intelligence needed to make sense of the document, while IDP connects that intelligence with validation, exception handling, enterprise systems, and downstream workflows.&lt;/p&gt;

&lt;p&gt;This is also why Document Understanding and IDP are not competing technologies. An organization does not necessarily choose one instead of the other.&lt;/p&gt;

&lt;p&gt;A system may understand and extract information from complex documents extremely well but still leave employees with significant manual work after extraction. On the other hand, a highly automated workflow can still struggle if the underlying document technology depends on rigid templates or cannot reliably interpret changing document structures.&lt;/p&gt;

&lt;p&gt;End-to-end document automation needs both intelligence and orchestration. The system needs to understand documents reliably across formats and structures, but it also needs to know how to validate the resulting information, manage exceptions, connect with enterprise systems, and move the process forward.&lt;/p&gt;

&lt;p&gt;Human review remains part of that picture. Documents can arrive with missing information, poor scans, handwriting, ambiguous values, or inconsistencies with other records. Rather than forcing uncertain information through an automated workflow, IDP can route low-confidence or exceptional cases to a person for verification.&lt;/p&gt;

&lt;p&gt;This changes the role of human review. Instead of manually processing every document, people can focus on the cases where their judgment is actually needed.&lt;/p&gt;

&lt;p&gt;The distinction becomes even more relevant as Large Language Models and conversational AI enter document workflows. LLMs can make it easier to summarize documents, retrieve information, and interact with document collections using natural language. But those capabilities still depend on the quality of the information underneath.&lt;/p&gt;

&lt;p&gt;The system still needs to identify the correct document, understand its structure, preserve context, retrieve the relevant information, and connect an answer to reliable source information. Conversational AI therefore does not make Document Understanding or IDP irrelevant. It adds another layer to what those capabilities already make possible.&lt;/p&gt;

&lt;p&gt;As enterprise document AI evolves, the terminology around it will continue to overlap. But the distinction itself is fairly straightforward. Document Understanding makes sense of what is inside a document, while &lt;a href="https://www.docugenieai.com/" rel="noopener noreferrer"&gt;Intelligent Document Processing&lt;/a&gt; carries that understanding through the processes, validations, systems, and actions that follow.&lt;/p&gt;

&lt;p&gt;That is the difference between understanding a document and actually putting that understanding to work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>intelligentdocumentautomation</category>
      <category>automation</category>
    </item>
    <item>
      <title>What is document understanding? How AI learns to read beyond the text</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Fri, 25 Sep 2026 07:57:34 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/what-is-document-understanding-how-ai-learns-to-read-beyond-the-text-29e4</link>
      <guid>https://dev.to/swathi_securekloud/what-is-document-understanding-how-ai-learns-to-read-beyond-the-text-29e4</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical look at how AI moves beyond OCR to understand document structure, context, relationships, and meaning.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A human looking at an invoice does not simply read the words printed on it. We understand that a number at the top could be an invoice number. We recognize a table containing line items. We know that Total carries a different meaning from a random number elsewhere on the page. We also use the document's layout, labels, relationships, and context to understand what the information means. For software, that has traditionally been much harder.&lt;/p&gt;

&lt;p&gt;Optical Character Recognition (OCR) gave machines the ability to convert printed or handwritten text into machine-readable text. That solved an important problem, but recognizing text is not the same as understanding a document. Therefore, document understanding becomes vital.&lt;/p&gt;

&lt;p&gt;In this article, we will look at what document understanding means, how it differs from OCR and simple data extraction, how AI understands different document elements, and where it fits within &lt;a href="https://www.docugenieai.com/" rel="noopener noreferrer"&gt;Intelligent Document Processing (IDP).&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is document understanding?&lt;/strong&gt;&lt;br&gt;
Document understanding uses &lt;a href="https://www.securekloud.com/ai-ml-services" rel="noopener noreferrer"&gt;AI and machine learning&lt;/a&gt; to identify, interpret, and organize information contained within documents. The important word here is understanding.&lt;/p&gt;

&lt;p&gt;A document is more than a collection of words. It has structure. Information may appear in headings, paragraphs, tables, key-value pairs, checkboxes, signatures, images, or different sections of a page. The position and relationship between these elements can change their meaning.&lt;/p&gt;

&lt;p&gt;Consider a loan application containing the numbers ₹50,000, ₹25,000, and ₹10,000. Extracting those numbers alone tells us very little. The system needs to understand whether they represent monthly income, an existing EMI, account balance, loan amount, or something else entirely. Document understanding adds this context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR and document understanding aren’t the same&lt;/strong&gt;&lt;br&gt;
OCR and document understanding often appear in the same conversation, but they solve different parts of the problem. OCR focuses primarily on recognizing characters and converting visual text into machine-readable text.&lt;/p&gt;

&lt;p&gt;Say for example, an invoice contains: Invoice Number: INV-1045&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OCR can recognize the words and characters on the page.&lt;/li&gt;
&lt;li&gt;Document understanding goes further. It identifies that INV-1045 is the invoice number and understands its relationship with the label next to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference becomes even clearer with complex documents.&lt;/p&gt;

&lt;p&gt;A bank statement may contain account details, opening and closing balances, transaction tables, dates, descriptions, credits, debits, and running balances. Simply extracting every word and number does not create a useful understanding of the statement. The system needs to know what those elements represent and how they relate to each other. &lt;/p&gt;

&lt;p&gt;OCR gives machines the text. Document understanding helps machines interpret the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How document understanding works?&lt;/strong&gt;&lt;br&gt;
There is no single technique behind document understanding. Modern systems can combine OCR, computer vision, machine learning, Natural Language Processing (NLP), layout analysis, and increasingly, multimodal and generative AI models.&lt;/p&gt;

&lt;p&gt;The process usually begins by identifying the document and examining its structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Document classification:&lt;/strong&gt; Before extracting information, the system needs to know what kind of document it is processing. Is it an invoice, bank statement, passport, purchase order, medical record, or tax document?&lt;br&gt;
Classification matters because different documents contain different information and structures. Knowing the document type gives the system useful context for interpreting what follows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Text and layout recognition:&lt;/strong&gt; The system then identifies text and other visual elements on the page. But location matters too. A value next to “Invoice Date” has a different meaning from the same value inside a line-item table. A number in a bank statement's debit column means something different from one in its balance column. Layout analysis helps the system understand these spatial relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Entity and field identification:&lt;/strong&gt; Once the document structure becomes clearer, AI can identify meaningful entities and fields. Depending on the document, these could include names, addresses, account numbers, invoice numbers, dates, monetary values, tax information, transaction details, or product information. The objective is not simply to capture text. It is to associate that text with its meaning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Table understanding:&lt;/strong&gt; Tables are one of the harder parts of document processing because their meaning depends heavily on structure. A financial statement, bank statement, or invoice may contain hundreds of values arranged across rows and columns. Extracting those values without preserving their relationships can make the data far less useful.&lt;/p&gt;

&lt;p&gt;Document understanding helps identify rows, columns, headers, cells, and the relationships between them so that the table can be reconstructed as structured information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Context and relationships:&lt;/strong&gt; This is where document understanding becomes particularly useful. Information inside a document rarely exists in isolation. A field may depend on its label. A value in one section may relate to another value elsewhere. A transaction description can provide context for an amount. AI can use these relationships to interpret information more accurately instead of treating every extracted element independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured, Semi-Structured, and Unstructured Documents&lt;/strong&gt;&lt;br&gt;
Not every document follows the same layout.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Structured documents usually follow a predictable format. Standard forms are a simple example because fields tend to appear in known locations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Semi-structured documents contain recognizable information but allow the layout to vary. Invoices and bank statements often fall into this category. Two banks may provide the same underlying information in completely different layouts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unstructured documents have much less predictable organization. Contracts, reports, letters, and other free-form documents may contain important information across paragraphs, sections, tables, and pages.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This variation is one reason traditional template-based extraction can struggle at scale. Real enterprise documents rarely arrive in one clean, consistent format. Document understanding aims to interpret the information even when the presentation changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about poor-quality documents?&lt;/strong&gt;&lt;br&gt;
Enterprise documents are not always clean digital PDFs. They may arrive as scanned copies, mobile photographs, screenshots, old records, skewed pages, or documents with blurred and partially visible text. Handwritten information may appear alongside printed content. This makes document understanding more difficult because the system must first deal with the quality of the input before it can reliably interpret what the document contains.&lt;/p&gt;

&lt;p&gt;Modern document AI systems can combine image preprocessing, OCR, layout analysis, and machine learning to handle some of these variations. However, document quality still matters, and organizations need validation mechanisms when confidence is low or information is ambiguous. Understanding a perfect PDF is one problem. Understanding the documents businesses actually receive is another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does document understanding fit within IDP?&lt;/strong&gt;&lt;br&gt;
This distinction is important because document understanding and Intelligent Document Processing (IDP) are not interchangeable terms. Document understanding focuses on interpreting what a document contains and what that information means. End-to-end Intelligent Document Processing covers a broader workflow. An IDP process may receive documents from email, APIs, file uploads, enterprise systems, or other channels. It can classify the document, understand and extract its contents, validate the information, apply business rules, route exceptions for human review, and send approved data into downstream systems or workflows.&lt;/p&gt;

&lt;p&gt;A simplified flow could look like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ingest → Classify → Understand → Extract → Validate → Process → Trigger Workflow&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Document understanding sits inside this larger process. Think about an invoice. Understanding the invoice means recognizing the supplier, invoice number, date, line items, tax, and total amount and understanding the relationships between them. End-to-end IDP can take that information further by validating it, comparing it with a purchase order or goods receipt, flagging an exception, and sending approved information into an ERP or accounts payable workflow.&lt;/p&gt;

&lt;p&gt;Thus, the distinction is crystal as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Document understanding asks: “What does this document contain, and what does the information mean?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;End-to-end IDP asks: “How do we take this document from arrival to business action?”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where can enterprises use document understanding?&lt;/strong&gt;&lt;br&gt;
Document understanding applies wherever organizations depend on documents to run business processes.&lt;/p&gt;

&lt;p&gt;In banking and lending, AI can understand bank statements, KYC documents, income proofs, passbooks, financial statements, and property records. In insurance, it can help interpret claim forms, policies, invoices, medical records, and supporting documents. In logistics, it can work with proof of delivery documents, trip sheets, lorry receipts, freight invoices, and shipping records. &lt;/p&gt;

&lt;p&gt;In manufacturing, it can process purchase orders, invoices, Goods Receipt Notes (GRNs), inspection documents, and other operational records. &lt;/p&gt;

&lt;p&gt;Healthcare, legal, retail, government, and many other industries face the same underlying problem. Important business information often remains trapped inside documents that people must manually read and interpret. Document understanding helps turn that information into data that software and people can use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document understanding moves beyond extraction&lt;/strong&gt;&lt;br&gt;
The next phase is becoming even more interesting. Once AI can understand document structure and preserve context, users do not necessarily have to interact with documents only through extracted fields. They can begin asking questions about them. Instead of searching through a 100-page document for one piece of information, a user could ask a question in natural language and retrieve an answer from the relevant content. Across multiple documents, this creates another possibility. AI can retrieve related information, compare it, and help users explore what a collection of documents reveals rather than opening every file individually. &lt;/p&gt;

&lt;p&gt;This is where document understanding starts connecting with technologies such as &lt;a href="https://www.securekloud.com/generative-ai-services" rel="noopener noreferrer"&gt;Large Language Models (LLMs)&lt;/a&gt;, Retrieval-Augmented Generation (RAG), and conversational AI.&lt;/p&gt;

&lt;p&gt;However, the underlying document intelligence still matters. An LLM can produce a fluent answer, but if the source document was classified incorrectly, a table lost its structure, or the wrong information was retrieved, fluency does not make the answer reliable. Good answers still depend on good document understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why document understanding matters&lt;/strong&gt;&lt;br&gt;
Businesses have spent years digitizing documents, but making a document digital does not automatically make the information inside it usable. A scanned PDF sitting in a repository is digital, but someone may still need to open it, search through it, understand the context, copy information into another system, and decide what happens next.&lt;/p&gt;

&lt;p&gt;Document understanding helps close that gap. It moves document technology from simply seeing text toward interpreting information in context. When combined with validation, workflow automation, integrations, and human review where necessary, document understanding can become part of a much larger Intelligent Document Processing system. &lt;/p&gt;

&lt;p&gt;That distinction will matter even more as enterprises introduce generative AI into document-heavy workflows. Before we ask AI to answer questions across hundreds or thousands of documents, the systems underneath still need to identify, interpret, and preserve the information those documents contain. &lt;/p&gt;

&lt;p&gt;That is what makes document understanding an important foundation for the next generation of document AI.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>documentai</category>
      <category>automation</category>
    </item>
    <item>
      <title>What "Intelligent" actually means in Intelligent Document Automation (IDA)</title>
      <dc:creator>Swathi Rajagopal</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:45:08 +0000</pubDate>
      <link>https://dev.to/swathi_securekloud/what-does-intelligent-actually-means-in-intelligent-document-automation-ida-3600</link>
      <guid>https://dev.to/swathi_securekloud/what-does-intelligent-actually-means-in-intelligent-document-automation-ida-3600</guid>
      <description>&lt;p&gt;Every few years, document processing gets rebranded. OCR became "document AI." Document AI is now becoming "&lt;a href="https://www.docugenieai.com/blog/intelligent-document-automation-enterprise" rel="noopener noreferrer"&gt;intelligent document automation&lt;/a&gt;" (IDA). It's tempting to write this off as marketing repackaging, and honestly, sometimes it is. But there's a real architectural shift underneath the label change, and it's worth understanding if you are building anything that touches unstructured documents.&lt;/p&gt;

&lt;p&gt;Classic OCR does one job: convert pixels into characters. Feed it a scanned invoice, and it hands back a wall of text, no structure, no understanding of what any of it means.&lt;/p&gt;

&lt;p&gt;That's fine if your downstream task is "make this searchable." It falls apart the moment your task is "extract the total due, the vendor's name, and the payment terms, and validate them against our purchase order." OCR has no concept of fields, relationships, or context. A human still has to sit between the OCR output and any system that needs structured data.&lt;/p&gt;

&lt;p&gt;This is the ceiling most legacy document processing pipelines hit. And it's exactly where "intelligent" starts to mean something concrete rather than being a buzzword.&lt;/p&gt;

&lt;p&gt;A modern IDP/IDA pipeline typically adds three layers on top of raw text extraction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Document classification&lt;/strong&gt;: Before extraction even starts, the system needs to know what kind of document it's looking at, an invoice, a bank statement, a KYC form, a delivery receipt. This matters because extraction logic differs wildly by document type, and a template-free system needs to infer type from layout and content patterns, not a fixed template ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Contextual field extraction&lt;/strong&gt;: Instead of "find text near this pixel coordinate" (which breaks the moment a template shifts by a few pixels), modern extraction models reason about semantic role. Is this number a subtotal or a grand total? Is this string a person's name or a company name? This is where the "AI" part is doing real work, not just reading characters, but inferring meaning from position, formatting, and surrounding text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cross-document validation&lt;/strong&gt;: A single document rarely tells the whole story. Real workflows require checking one document's claims against another's, does the invoice total match the purchase order? Does the declared income on a loan application line up with the bank statement transaction history? This is a genuinely different problem than extraction, it's a graph-matching and consistency-checking problem layered on top of extracted data.&lt;/p&gt;

&lt;p&gt;The gap between "OCR" and "intelligent automation" gets a lot more consequential once you're operating at scale in a regulated industry.&lt;br&gt;
Take lending. A bank statement isn't just a document to digitize, it's a data source that has to survive an audit trail. Every categorized transaction, every flagged anomaly, needs to be explainable after the fact. That pushes the architecture toward systems that don't just extract, but expose why a categorization or flag happened, since "the model said so" doesn't satisfy a compliance reviewer.&lt;/p&gt;

&lt;p&gt;Insurance claims processing has a similar shape: a claim form, a medical report, and a set of bills all need to agree with each other and with the underlying policy terms before a payout gets approved. That's multi-document reasoning, not single-document extraction, and it's a meaningfully harder engineering problem.&lt;/p&gt;

&lt;p&gt;I have found &lt;a href="https://docugenieai.com/" rel="noopener noreferrer"&gt;DocuGenie.AI™'s&lt;/a&gt; breakdown of its own platform architecture a useful reference point here, they're explicit about separating classification, extraction, cross-document validation, and workflow orchestration into distinct stages rather than treating "document AI" as one monolithic black box, which maps closely to the layered approach described above.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth about intelligent document pipelines: they will produce a confident, structured, well-formatted output even when the input document was low-quality, a bad scan, a smudge, a folded corner covering a digit. Unlike a human reviewer, the system won't necessarily flag "I'm not sure about this," unless it's been explicitly engineered to surface uncertainty.&lt;/p&gt;

&lt;p&gt;This is why a lot of production-grade document automation platforms build in something like a tolerance matrix or confidence-scoring layer, extraction outputs below a certain confidence threshold get routed to human review instead of flowing straight downstream. It's a small architectural decision with outsized consequences: skip it, and you've just automated the production of wrong answers at scale.&lt;/p&gt;

&lt;p&gt;The next layer, and where a lot of current engineering effort is going, is workflow orchestration: not just extracting and validating data but automatically routing it into the next system (a loan origination system, a claims management platform, an ERP) based on what was found. That's the difference between "we digitized the document" and "we removed the document from the workflow entirely."&lt;/p&gt;

&lt;p&gt;If you are building in this space, or evaluating a platform, the questions worth asking are less about extraction accuracy claims (everyone quotes a good number) and more about: How does it handle documents it wasn't trained on? What happens when confidence is low? Can you actually trace a decision back to its source data? Those are the questions that separate genuinely intelligent document automation from OCR with better marketing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I write about AI, document automation, and enterprise workflows. If you are working on similar problems, it would be great to hear how your team is handling the confidence-scoring and validation layer, that's usually where the real engineering complexity lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>softwaredevelopment</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
