How to Validate Extracted Invoice Data in Python
4 mins read

How to Validate Extracted Invoice Data in Python

Automated invoice parsing starts with finding the right structured data extraction tool (typically some OCR library).

Once that’s covered, we can shift our attention to the next pressing issue in that pipeline: is any of the data we extracted actually valid?

“Valid ” means something different for every workflow, but the high-level concept always stays the same. We need required data to be present, inconsistent formatting to be normalized, invalid or unsupported values to be rejected, and overall correctness to be (somehow) assured.

In simpler terms, we want to make sure the data we extract is usable by the application we’re supporting.

In this brief walkthrough, we’ll look at one way to build validation around extracted invoice data in Python.

We’ll use the Cloudmersive Document AI API to handle the extraction part, and we’ll handle the subsequent validation logic with standard Python string handling, some conditionals, and the built-in decimal module.

Installing dependencies

We’ll get started by installing the Cloudmersive Document AI Python client:

pip install cloudmersive-documentai-api-client

And that’s it for this part — everything else we need is already included with Python.

Configuring the Document AI client

We’ll now import the client and ready our API key from an environment variable:

import os

import cloudmersive_documentai_api_client
from cloudmersive_documentai_api_client.rest import ApiException

configuration = cloudmersive_documentai_api_client.Configuration()
configuration.api_key["Apikey"] = os.environ["CLOUDMERSIVE_API_KEY"]

extract_api = cloudmersive_documentai_api_client.ExtractApi(
cloudmersive_documentai_api_client.ApiClient(configuration)
)

This keeps the API key out of our source code while giving us a configured client we can reuse throughout the workflow.

Extracting the invoice fields

For this exaple, we’ll keep things intentionally simple. We’ll focus on five useful fields we might want to extract from an invoice:

  1. Invoice Number
  2. Invoice Date
  3. Vendor Name
  4. Currency
  5. Total Due

We can wrap the entire extraction step in a quick helper function:

def extract_invoice_fields(file_path):
"""Extract the invoice fields needed for validation."""

field_names = (
"Invoice Number,"
"Invoice Date,"
"Vendor Name,"
"Currency,"
"Total Due"
)

response = extract_api.extract_fields(
field_names=field_names,
recognition_mode="Advanced",
input_file=file_path
)

if response.successful is not True:
raise ValueError("Invoice field extraction was not successful.")

return {
result.field_name: result.field_string_value
for result in response.results
}

Now we have a dictionary of extracted invoice values that we can work with in our normal Python code.

For context, an example API response might look something like this:

{
"Invoice_Number": "INV-1048",
"Invoice_Date": "08/29/2026",
"Vendor_Name": "Example Supply Co.",
"Currency": "usd",
"Total_Due": "$1,482.75"
}

The important thing for our workflow is that we haven’t assumed those values are ready to use yet.

Normalizing the extracted values

Before we validate anything in our code, it helps to put our extracted invoice values in a predictable format.

For example, “usd” and “USD” should mean the same thing, and “$1,482.75” will be easier to work with once we convert it to an actual numeric value.

from decimal import Decimal, InvalidOperation


def parse_total(value):
"""Convert a formatted invoice total into a Decimal value."""

if not value:
return None

cleaned = (
value.replace("$", "")
.replace(",", "")
.strip()
)

try:
return Decimal(cleaned)
except InvalidOperation:
return None


def normalize_invoice_fields(fields):
"""Normalize extracted invoice values before validation."""

return {
"invoice_number": fields.get("Invoice_Number", "").strip(),
"invoice_date": fields.get("Invoice_Date", "").strip(),
"vendor_name": fields.get("Vendor_Name", "").strip(),
"currency": fields.get("Currency", "").strip().upper(),
"total_due": parse_total(fields.get("Total_Due"))
}

Bear in mind that we’re not trying to normalize every invoice format on Earth right now. We just want enough consistency in our workflow to make our validation rules dependable.

Validating the invoice

Now we can decide what makes an invoice record acceptable for our particular workflow.

For this example, we’ll require the following every field we extract to be present (meaning it must contain some value).

def validate_invoice(invoice):
"""Validate normalized invoice fields and return any errors."""

errors = []

if not invoice["invoice_number"]:
errors.append("Invoice number is required.")

if not invoice["invoice_date"]:
errors.append("Invoice date is required.")

if not invoice["vendor_name"]:
errors.append("Vendor name is required.")

if invoice["currency"] not in {"USD", "EUR", "GBP"}:
errors.append("Currency is missing or unsupported.")

if invoice["total_due"] is None:
errors.append("Total due is not a valid number.")

elif invoice["total_due"] <= 0:
errors.append("Total due must be greater than zero.")

return {
"valid": len(errors) == 0,
"errors": errors
}

It’s tempting to end validation at the first missing value. We did, after all, say we require every field to be present.

The way we’ve handled validation here, however, provides a much more useful result when the application is called. We’ve collected all validation errors in an array and packaged that with a boolean set to “false” any time array length exceeds 0.

Now we’re telling applications two things: 1) “there’s data missing” and 2) “here’s what that data is” instead of just the former.

Putting the workflow together

With extraction, normalization, and validation separated into their own distinct steps, the main workflow stays pleasantly simple:

def process_invoice(file_path):
"""Extract, normalize, and validate an invoice."""

extracted = extract_invoice_fields(file_path)
normalized = normalize_invoice_fields(extracted)
validation = validate_invoice(normalized)

return {
"invoice": normalized,
"validation": validation
}


try:
result = process_invoice("invoice.pdf")

print(result)

except ApiException as error:
print(f"Document AI API error: {error}")

except (OSError, ValueError) as error:
print(f"Invoice processing error: {error}")

For a valid invoice, we might get a response like this:

{
"invoice": {
"invoice_number": "INV-1048",
"invoice_date": "08/29/2026",
"vendor_name": "Example Supply Co.",
"currency": "USD",
"total_due": Decimal("1482.75")
},
"validation": {
"valid": True,
"errors": []
}
}

And if the extraction succeeds but some of the returned values don’t meet our requirements, we still get a useful validation result:

{
"valid": False,
"errors": [
"Currency is missing or unsupported.",
"Total due is not a valid number."
]
}

The extraction step tells us what the document contains, while the validation step tells us whether those results are good enough for our application to trust and use.

Conclusion

Automated invoice data extraction saves a ton of time and effort, and AI-powered extraction generally increases the overall quality of data we can expect to extract. Without proper validation in our workflow, however, we can’t guarantee data quality.

Separating extraction, normalization, and validation steps results in a workflow that’s both easy to understand and easy to adapt. No matter which data extraction service we use for our invoice pipeline, all it takes is a few lines of simple Python code to keep control over what counts as a valid result.


How to Validate Extracted Invoice Data in Python was originally published in Stackademic on Medium, where people are continuing the conversation by highlighting and responding to this story.

PakarPBN

A Private Blog Network (PBN) is a collection of websites that are controlled by a single individual or organization and used primarily to build backlinks to a “money site” in order to influence its ranking in search engines such as Google. The core idea behind a PBN is based on the importance of backlinks in Google’s ranking algorithm. Since Google views backlinks as signals of authority and trust, some website owners attempt to artificially create these signals through a controlled network of sites.

In a typical PBN setup, the owner acquires expired or aged domains that already have existing authority, backlinks, and history. These domains are rebuilt with new content and hosted separately, often using different IP addresses, hosting providers, themes, and ownership details to make them appear unrelated. Within the content published on these sites, links are strategically placed that point to the main website the owner wants to rank higher. By doing this, the owner attempts to pass link equity (also known as “link juice”) from the PBN sites to the target website.

The purpose of a PBN is to give the impression that the target website is naturally earning links from multiple independent sources. If done effectively, this can temporarily improve keyword rankings, increase organic visibility, and drive more traffic from search results.

Streaming Film

Nonton Film gratis

Jasa Backlink

Download Anime Batch