DEV Community

Cover image for Building a Document Pipeline for Professional Credential Recognition (Apostille, Translation, Submission)
Diogo Heleno
Diogo Heleno

Posted on Originally published at m21global.com

Building a Document Pipeline for Professional Credential Recognition (Apostille, Translation, Submission)

Credential recognition workflows show up more often than you'd expect in internal tooling: HR platforms for relocating employees, immigration case management systems, university partnership portals, government digital services. If you've ever been asked to build or maintain one of these, you know the hard part isn't the UI. It's modeling a process that varies by country, by institution, and by document type, with almost no API coverage.

This post walks through how to think about that problem technically, using professional recognition for doctors and engineers in Portugal as a concrete case study. The source article on certified translation for medical and engineering boards covers the regulatory side well. This is the implementation side: how you'd model the document states, catch errors before submission, and avoid building a system that silently produces rejected applications.

Why this is a state machine problem, not a form problem

Most teams start by building a form: upload diploma, upload transcript, submit. That works until you realize each document independently moves through a sequence of states, and the sequence order itself is a variable.

For a Portuguese professional board application, a single document can pass through:

  • original
  • apostilled or legalised
  • translated
  • certified (lawyer/notary attestation)
  • submitted
  • accepted / returned_for_correction

The catch, directly from the source material: whether apostille happens before or after translation depends on the country of origin and the specific board. That's not an edge case, it's the normal case. If you hardcode a linear pipeline (translate → apostille → submit), you will build something that's wrong for a meaningful chunk of your users.

Model it instead as a directed graph per country/board combination, not a single pipeline:

# Simplified representation of per-country document flow
DOCUMENT_FLOWS = {
    ("BR", "Ordem dos Medicos"): ["original", "apostille", "translate", "certify", "submit"],
    ("AO", "Ordem dos Engenheiros"): ["original", "legalise", "translate", "certify", "submit"],
    ("FR", "Ordem dos Medicos"): ["original", "translate", "apostille", "certify", "submit"],
}

def get_flow(country_code: str, board: str) -> list[str]:
    flow = DOCUMENT_FLOWS.get((country_code, board))
    if not flow:
        raise LookupError(f"No confirmed flow for {country_code}/{board}. Flag for manual review.")
    return flow
Enter fullscreen mode Exit fullscreen mode

The LookupError branch matters as much as the happy path. The source article is explicit that step order should be confirmed directly with the board's secretariat. Your system should treat unconfirmed country/board combinations as a hard stop, not a default guess.

Catching terminology inconsistency before a human does

The article flags something that's easy to dismiss as a soft skill problem but is actually very solvable with tooling: inconsistent terminology across documents in the same file. "Strength of materials" vs "mechanics of materials" on an engineering transcript isn't a style nitpick, it can change an equivalence decision.

If you're running translation through any kind of managed workflow (even a human-in-the-loop one, since this is certified legal translation, not raw MT), you can still build a terminology consistency check as a pre-submission gate.

A basic approach:

  1. Maintain a per-case glossary (subject names, degree titles, institution names) as the source of truth.
  2. Extract candidate terms from each translated document (OCR + NER if working from PDFs, or direct text diffing if you control the translation memory).
  3. Flag any term that deviates from the glossary above a similarity threshold, rather than matching exactly.
from difflib import SequenceMatcher

def check_term_consistency(glossary: dict[str, str], extracted_terms: list[str], threshold=0.85):
    flags = []
    for term in extracted_terms:
        best_match = max(glossary.values(), key=lambda g: SequenceMatcher(None, term, g).ratio())
        score = SequenceMatcher(None, term, best_match).ratio()
        if 0 < score < threshold:
            flags.append((term, best_match, round(score, 2)))
    return flags
Enter fullscreen mode Exit fullscreen mode

This won't replace a linguist's review (and for high-stakes documents it shouldn't), but it's a cheap gate that catches the exact failure mode the source article describes, before a human reviewer or a board clerk does.

Name and date normalization across documents

Another flagged failure mode: inconsistent transliteration of a candidate's name across documents triggers clarification requests. This is a solved problem in data engineering, just apply it here.

  • Normalize all personal identifiers (name, DOB, document numbers) into a canonical record per applicant at intake.
  • Run every subsequently uploaded document's extracted metadata against that canonical record.
  • Diff mismatches and surface them before submission, not after a board returns the file.
def validate_identity_fields(canonical: dict, extracted: dict) -> list[str]:
    mismatches = []
    for field in ("full_name", "date_of_birth", "passport_number"):
        if canonical.get(field) != extracted.get(field):
            mismatches.append(field)
    return mismatches
Enter fullscreen mode Exit fullscreen mode

This is unglamorous, but it directly prevents one of the five recurring delay causes the source article lists.

A minimal checklist structure for the pipeline

If you're building this as an internal tool rather than a full product, you can get most of the value from a structured checklist model instead of a full workflow engine:

  • Document requirements table per board (medical vs engineering, since the lists differ)
  • Per-document status tracking (original / legalised / translated / certified)
  • A hard validation step before submission: every required document present, every annex and stamp included, consistent terminology, consistent identity fields
  • An explicit "confirm sequence with board secretariat" flag for any country/board pair not yet verified

This maps cleanly onto a relational schema if you want to persist it: applications, documents, document_states, glossary_terms, each scoped by (country, board).

Where this generalizes

None of this is specific to Portugal or to medicine and engineering. Any cross-border credential recognition system, legal document localization, or immigration case tool has the same shape: country-specific sequencing, terminology consistency requirements, and identity field validation. If you're building in this space, model the variability explicitly instead of assuming a single linear pipeline, and you'll avoid most of the failure modes that cause real-world applications to bounce back for correction.

For the regulatory detail on what each board actually requires and how certification works under Portuguese rules, the original article is worth reading in full: certified translation for medical and engineering boards.

Top comments (0)