DEV Community

Cover image for Building a Document Validation Checker for Multi-Country Compliance Rules (EU Driving Licence Case Study)
Diogo Heleno
Diogo Heleno

Posted on Originally published at m21global.com

Building a Document Validation Checker for Multi-Country Compliance Rules (EU Driving Licence Case Study)

If you've ever built an onboarding flow, KYC system, or any product that deals with government documents across multiple countries, you know the real problem isn't storing the document. It's validating that it meets the destination country's formal requirements before a human ever looks at it.

The M21Global article on certified translation for EU driving licence exchange lays out a good example of this: EU driving licences follow a standardized format under Directive 2006/126/EC, yet each member state has different rules about whether a translation is required, what language it must be in, and how it must be certified. The document itself is fine. The failure mode is purely a rules-engine problem.

This is a pattern that shows up constantly in software: a well-structured source document, inconsistent downstream validation rules, and a process that fails silently until a human rejects it weeks later. Let's talk about how to model and automate this instead of discovering requirements manually every time.

Why this is a data modeling problem, not a translation problem

The source article's breakdown is basically a requirements matrix:

  • Is translation required at all?
  • What target language applies (and does the country have multiple official languages)?
  • What certification form is accepted (letterhead declaration vs. notarized vs. registered translator)?
  • What supporting documents are needed?

That's four independent variables per country, each with its own edge cases. If you're building any kind of relocation, immigration, or compliance tooling, this maps cleanly onto a rules table rather than hardcoded logic.

# requirements_schema.py
from dataclasses import dataclass
from enum import Enum

class CertificationType(Enum):
    NONE = "none"
    CERTIFIED = "certified"          # translator/agency declaration
    SWORN = "sworn"                  # notarized or court-registered translator

@dataclass
class CountryRequirement:
    country_code: str
    requires_translation: bool
    target_languages: list[str]       # some countries have >1 official language
    certification_type: CertificationType
    requires_driving_record: bool
    requires_certified_true_copy: bool
    authority_name: str

REQUIREMENTS = {
    "DE": CountryRequirement("DE", True, ["de"], CertificationType.CERTIFIED, True, False, "Kraftfahrt-Bundesamt"),
    "FR": CountryRequirement("FR", True, ["fr"], CertificationType.CERTIFIED, False, False, "Préfecture"),
    "ES": CountryRequirement("ES", False, ["es"], CertificationType.CERTIFIED, False, False, "DGT"),
}
Enter fullscreen mode Exit fullscreen mode

This is deliberately simplified, but the point stands: once you encode this as data instead of institutional knowledge sitting in someone's head (or in a vendor's blog post), you can validate submissions programmatically before they're sent anywhere.

Building a pre-submission validator

The mistakes listed in the source article are almost all things a script can catch before a document reaches a case officer:

  • Missing certification declaration
  • Declaration in the wrong language
  • Omitted fields (restriction codes, expiry dates)
  • Photocopy submitted where a certified true copy is required

Here's a minimal validator shape:

def validate_submission(doc, country_code):
    req = REQUIREMENTS[country_code]
    errors = []

    if req.requires_translation and not doc.has_translation:
        errors.append("Translation required but missing")

    if doc.has_translation and doc.translation_language not in req.target_languages:
        errors.append(
            f"Translation language '{doc.translation_language}' not accepted; "
            f"expected one of {req.target_languages}"
        )

    if doc.has_translation and doc.certification_type != req.certification_type:
        errors.append(
            f"Certification type mismatch: got {doc.certification_type}, "
            f"expected {req.certification_type}"
        )

    missing_fields = req_fields_missing(doc, country_code)
    if missing_fields:
        errors.append(f"Missing required fields in translation: {missing_fields}")

    if req.requires_certified_true_copy and not doc.is_certified_true_copy:
        errors.append("Certified true copy required, plain photocopy submitted")

    return errors
Enter fullscreen mode Exit fullscreen mode

This isn't glamorous code. That's the point. Most compliance tooling failures aren't algorithmic, they're from nobody bothering to write down the rules in a structured, testable way.

Keeping the rules table current

The hard part isn't the schema, it's maintenance. Regulatory requirements change, and unlike a software API there's no changelog or versioned spec you can poll. Some practical approaches:

  • Treat it like a dependency. Store the requirements table in version control, review changes via PR, and require a source citation (official gov page, embassy notice, etc.) in the commit message.
  • Add a last_verified timestamp per country entry. Flag anything older than 6-12 months for manual re-check rather than silently trusting stale data.
  • Separate "hard requirements" from "commonly requested extras." The article mentions Germany's transport authority may request a driving history. Model that as optional-but-flagged rather than mandatory, so your validator doesn't produce false positives.
@dataclass
class CountryRequirement:
    # ...
    last_verified: str  # ISO date
    source_url: str
Enter fullscreen mode Exit fullscreen mode

If you're doing this at any scale (more than a handful of countries), it's worth treating the requirements table the same way you'd treat a tax-rate table or shipping-rules table: externalized, auditable, and owned by someone whose job is literally to keep it correct.

Where automation stops being appropriate

It's worth being honest about the limits here. A validator can catch structural problems (missing language, missing field, wrong certification type) but it cannot assess translation quality or legal sufficiency. The distinction the source article draws between certified and sworn translation is a legal one, not a formatting one, and getting that wrong has real consequences for someone's relocation timeline.

  So the realistic scope for this kind of tooling is: catch the mechanical failures before submission, surface the right requirements early, and route anything ambiguous (mixed jurisdictions, court-related documents, multiple official languages) to a human who actually knows the law. Automating 80% of the checklist still saves real time, it just shouldn't be mistaken for full compliance automation.
Enter fullscreen mode Exit fullscreen mode




Takeaway

If your product touches document exchange across jurisdictions, don't wait for users to hit rejection emails to learn the rules. Model the requirements as structured, versioned data from day one, build a pre-submission validator against it, and keep a clear line between what the code checks and what still needs a human sign-off.

Top comments (0)