El nombre es el campo menos predecible de una zona de lectura mecánica. Las fechas son seis dígitos, el número de documento son nueve caracteres con un dígito de control y los códigos salen de una lista cerrada. El nombre es como se llame cada persona, comprimido en un número fijo de letras mayúsculas, sin espacios, sin acentos, sin puntuación y sin ningún dígito de control que avise cuando un carácter se leyó mal.
Este artículo explica cómo escribe la zona un nombre, cómo volver a separarlo en apellido y nombres, qué le hace la transliteración, cómo saber que puede haberse recortado y cómo compararlo con el nombre impreso en la página. Todos los nombres de abajo son inventados o son el espécimen de la propia OACI, Anna Maria Eriksson; no aparece ninguna persona real.
Este es el blog de doc.cheap, una API de OCR para pasaportes y documentos de identidad. Nada de lo que sigue la necesita; el código no tiene dependencias.
Dónde está el nombre
El Doc 9303 de la OACI, la norma para documentos de viaje de lectura mecánica, da al nombre un campo de ancho fijo en cada uno de sus tres formatos. Aquí las posiciones empiezan en 1, como las cuenta la norma:
| Formato | Dónde está el nombre | Ancho |
|---|---|---|
| TD3 (pasaportes), 2 × 44 | línea 1, posiciones 6–44 | 39 caracteres |
| TD2, 2 × 36 | línea 1, posiciones 6–36 | 31 caracteres |
| TD1 (documentos de identidad), 3 × 30 | línea 3, la línea entera | 30 caracteres |
Los cinco primeros caracteres de la línea 1 de un TD3 o un TD2 son el código del documento y el Estado emisor; el nombre ocupa todo lo que viene después. TD1 le da al nombre una línea propia, y aun así es el más estrecho de los tres. La página de formatos MRZ recorre cada formato campo por campo.
En el espécimen TD3, la línea 1 es P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<. Si se quita P<UTO, el campo de nombre es ERIKSSON<<ANNA<MARIA seguido de caracteres de relleno hasta 39 caracteres.
La gramática: <<, < y el relleno
El alfabeto de la zona es A–Z, 0–9 y el carácter de relleno <. Un nombre añade tres convenciones:
- El identificador primario va primero. Normalmente es el apellido.
- Dos rellenos,
<<, cierran el identificador primario. Lo que sigue es el identificador secundario, normalmente los nombres de pila. - Un relleno,
<, separa las palabras dentro de cualquiera de las dos partes.ANNA<MARIAson dos nombres de pila;VAN<DER<BERGes un apellido de tres palabras.
Lo que queda del campo después del nombre se rellena con <. Así que leer un nombre son tres pasos: quitar el relleno final, separar en el primer << y convertir los < sueltos restantes en espacios.
Dos formas parecen raras y no son errores. Una persona con un solo nombre solo tiene identificador primario, así que el campo es el nombre y luego relleno, sin << delante: OKONKWO<<<<…. Un campo escrito con el separador y nada detrás, NAKAMURA<<<<…, se lee exactamente igual una vez quitado el relleno final. En ambos casos el identificador secundario está vacío, no ausente.
Lo que no sobrevive es la puntuación ni el propio espacio. Un guion, un apóstrofo o un espacio no se pueden escribir en la zona, así que un Smith-Jones impreso llega como SMITH<JONES, indistinguible de un apellido de dos palabras. Si necesita la puntuación, está en la página impresa, no en la zona.
Transliteración: de cualquier escritura a 26 letras
Todo nombre tiene que acabar en A–Z. Para un nombre impreso en letras latinas con diacríticos, eso significa convertir cada letra acentuada en letras simples. Para un nombre en griego, árabe, cirílico, caracteres han o cualquier otra escritura, significa escribir el nombre entero en letras latinas.
La Parte 3 del Doc 9303, la parte común a todos los formatos, recoge transliteraciones recomendadas para ambos casos. Pero la decisión es del Estado emisor, y no todos los Estados eligen igual. Tres consecuencias importan para el código:
- Un mismo nombre impreso puede tener más de una grafía en la zona. Un
MÜLLERimpreso puede aparecer en la zona comoMUELLERo comoMULLER, según las reglas del emisor. El código que espera una única correspondencia fija dará por errónea una zona correcta. - La correspondencia no tiene vuelta atrás.
MULLERen una zona puede venir deMULLER,MÜLLERoMÚLLERen la página. Una zona le dice qué letras contiene, no el nombre que la persona escribe. - El nombre latino de la página puede diferir del de la zona. Un documento impreso en una escritura nacional suele imprimir también una versión latina, y esa grafía latina impresa no siempre coincide letra por letra con la de la zona. Trate una diferencia como algo que revisar, no como prueba de una mala lectura.
La regla práctica: compare un nombre de la zona con un nombre impreso después de reducir ambos a letras desnudas, admita las grafías que sus emisores usan de verdad y conserve la grafía impresa para todo lo que lea una persona.
Truncamiento: cuando el nombre no cabe
Un nombre largo no pasa a otra línea. Se acorta para que quepa en el campo, y el nombre completo sigue en la página impresa. La norma da reglas sobre cómo acortar un nombre, y un nombre acortado normalmente llega hasta el último carácter de su campo.
Eso da la única señal que tiene un parser: si el último carácter del campo de nombre es una letra y no <, el nombre puede haberse truncado. Es "puede", no "se truncó": un nombre exactamente tan largo como el campo también termina en una letra. Pero un campo que termina en relleno seguro que no se recortó.
El ancho importa. Tome un titular inventado, Konstantin Friedrich Hartmann Schoenebeck. En un campo TD3 de 39 caracteres:
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR
El último carácter es R, una letra, así que el nombre puede estar recortado, y lo está: FRIEDRICH perdió tres letras. El mismo nombre recortado al ancho TD1 de 30 conserva solo HARTMANN<SCHOENEBECK<<KONSTANT. Un sistema que guarda el nombre de la zona como nombre del titular guarda KONSTANT como nombre de pila.
De ahí salen dos hábitos. No use nunca el nombre de la zona como nombre visible cuando el campo está lleno; use el impreso. Y cuando compare un nombre impreso completo con un nombre de la zona, un nombre impreso que empieza por el nombre de la zona es una coincidencia si el campo estaba lleno.
Un emisor también puede acortar un nombre de otras formas que no son cortar el final, por ejemplo reduciendo un segundo nombre de pila a su inicial. Una prueba de prefijo da ese nombre como no coincidente. Envíe ese caso a una persona en lugar de rechazar el documento.
Ningún dígito de control cubre el nombre
Los dígitos de control cubren el número de documento, las dos fechas, el número personal opcional del TD3 y un dígito compuesto sobre la mayor parte de la línea inferior. Ninguno cubre el nombre. En TD3 y TD2 el nombre está en la línea 1, que ningún dígito toca; en TD1 está en la línea 3, y el dígito compuesto termina en la línea 2.
Así que una mala lectura en un nombre pasa en silencio. Una M leída como N, o un relleno leído como letra, sigue dando una zona cuya aritmética cuadra. La única comprobación cruzada para un nombre es la página impresa: lea ambos y compárelos, que es para lo que sirve la función de comparación de más abajo. Cómo funcionan los dígitos de control de la MRZ explica la aritmética de los campos que sí están cubiertos.
Separar el campo en Python
Sin dependencias. La función recibe solo el campo de nombre, ya extraído de su línea (line1[5:44] en TD3, line1[5:36] en TD2, line3 en TD1).
import re
def words(part):
return " ".join(word for word in part.split("<") if word)
def read_mrz_name(field):
"""Split an MRZ name field into (primary, secondary, may_be_truncated)."""
if not re.fullmatch(r"[A-Z<]+", field):
raise ValueError("a name field holds only A-Z and the filler <")
may_be_truncated = not field.endswith("<")
body = field.rstrip("<")
primary, _separator, secondary = body.partition("<<")
return words(primary), words(secondary), may_be_truncated
partition separa solo en el primer << y devuelve una parte secundaria vacía cuando no lo hay, que es el caso de un solo nombre. El patrón rechaza los dígitos a propósito: un 0 en un campo de nombre es una O mal leída, y debe llegar a una persona en lugar de imprimirse como nombre.
La misma separación en JavaScript
export function readMrzName(field) {
if (!/^[A-Z<]+$/.test(field)) throw new Error("a name field holds only A-Z and the filler <");
const mayBeTruncated = !field.endsWith("<");
const body = field.replace(/<+$/, "");
const at = body.indexOf("<<");
const words = (part) => part.split("<").filter(Boolean).join(" ");
if (at < 0) return { primary: words(body), secondary: "", mayBeTruncated };
return { primary: words(body.slice(0, at)), secondary: words(body.slice(at + 2)), mayBeTruncated };
}
Comparar la zona con el nombre impreso
El nombre impreso viene de la zona visual, la parte de la página que lee una persona, con sus acentos y su puntuación. La función de abajo lo reduce a letras mayúsculas desnudas y lo compara con una parte del nombre de la zona. Devuelve una de tres palabras: match, mismatch, o unknown cuando el nombre impreso contiene una letra que no se reduce por sí sola a A–Z, como Ø, Æ o Ł.
import re
import unicodedata
# Las grafías que se prueban además de quitar el acento sin más. Estas tres
# son un ejemplo; ponga las que usan sus emisores.
EXPANSIONS = {"Ä": "AE", "Ö": "OE", "Ü": "UE"}
def compare_name(zone_part, printed_part, truncated=False, expansions=EXPANSIONS):
"""'match', 'mismatch' or 'unknown' for one name part: zone against printed."""
zone = zone_part.replace(" ", "")
upper = unicodedata.normalize("NFC", printed_part).upper()
candidates = {upper, "".join(expansions.get(c, c) for c in upper)}
verdict = "unknown"
for candidate in candidates:
letters = "".join(
c for c in unicodedata.normalize("NFKD", candidate)
if c.isalpha() and not unicodedata.combining(c)
)
if not re.fullmatch(r"[A-Z]*", letters):
continue # una letra sin forma simple en A-Z: que la revise una persona
if letters == zone or (truncated and letters.startswith(zone)):
return "match"
verdict = "mismatch"
return verdict
NFKD separa una letra acentuada en la letra simple y su acento, y el acento se descarta. Los espacios, guiones y apóstrofos también se descartan, porque la comparación es solo sobre letras: Smith-Jones y SMITH JONES se convierten ambos en SMITHJONES. Para un nombre impreso solo en una escritura no latina, compare la zona con la versión latina que imprime la página, no con la de la escritura nacional; la escritura nacional devolvería aquí unknown siempre.
Casos de prueba
Todos los nombres son inventados salvo el espécimen de la OACI. En la primera tabla, cada campo se rellena con < hasta el ancho TD3 de 39 (en Python, name.ljust(39, "<")), excepto la última fila, que ya tiene exactamente 39 caracteres.
| Campo antes del relleno | Primario | Secundario | Puede estar truncado |
|---|---|---|---|
ERIKSSON<<ANNA<MARIA |
ERIKSSON | ANNA MARIA | no |
VAN<DER<BERG<<PIETER |
VAN DER BERG | PIETER | no |
SMITH<JONES<<MARY |
SMITH JONES | MARY | no |
OKONKWO |
OKONKWO | (vacío) | no |
NAKAMURA<< |
NAKAMURA | (vacío) | no |
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR |
HARTMANN SCHOENEBECK | KONSTANTIN FRIEDR | sí |
Y para compare_name:
| Parte de la zona | Parte impresa | Truncado | Esperado |
|---|---|---|---|
ANNA MARIA |
Anna Maria |
no | match |
SMITH JONES |
Smith-Jones |
no | match |
MULLER |
Müller |
no | match |
MUELLER |
Müller |
no | match (por la expansión) |
MILLER |
Müller |
no | mismatch |
KONSTANTIN FRIEDR |
Konstantin Friedrich |
sí | match |
KONSTANTIN FRIEDR |
Konstantin Friedrich |
no | mismatch |
SORENSEN |
Sørensen |
no | unknown |
La última fila es deliberada. Si Ø pasó a ser O u OE es decisión del emisor, y una función que lo adivine se equivocará con alguien; añada a las expansiones la forma que usan sus emisores.
Qué hace nuestro parser de MRZ
El parser de MRZ de este sitio lee un nombre de la misma manera: quita el relleno final, separa en el primer << y convierte los rellenos sueltos en espacios; un campo sin << se lee como un apellido solo. Cuando el nombre llega al último carácter de su campo, muestra un aviso de que el nombre puede haberse truncado para caber, con las posiciones que ocupa. Se ejecuta en el navegador, así que puede pegar una zona sintética y comparar su lectura con su propio código.
Dónde encaja esto en un pipeline
Un servicio de reconocimiento alojado lee tanto la zona como la página impresa, así que la pregunta es qué nombre le entrega. En la respuesta de doc.cheap, holder.surname, holder.given_names y holder.full_name llevan la lectura latina combinada. Un nombre impreso en una escritura nacional es una entrada aparte de fields: cada lectura tiene su propia entrada, la latina por defecto tiene un id que termina en @0 y la de la escritura nacional indica su language, por ejemplo Greek. La zona en sí está en mrz.lines, exactamente como se leyó, para que pueda extraer el campo de nombre y aplicarle las dos funciones de arriba. La guía sobre escrituras no latinas muestra cómo agrupar las lecturas y guardar ambas grafías. Si todavía está eligiendo un servicio, la comparativa de APIs de OCR de pasaportes pone los precios publicados uno al lado del otro.
Si encuentra un nombre que el código de arriba lee mal, escriba a admin@doc.cheap.
Una pregunta para ti: ¿qué transliteración te sorprendió más al comparar una zona con el nombre impreso? Cuéntanoslo en los comentarios de abajo.