मशीन रीडेबल ज़ोन (MRZ) में नाम सबसे कम अनुमान लगाने लायक फ़ील्ड है। तारीख़ें छह अंक होती हैं, दस्तावेज़ संख्या एक चेक डिजिट के साथ नौ कैरेक्टर की होती है, और कोड एक तय सूची से आते हैं। नाम वह है जो किसी व्यक्ति को कहा जाता है, जिसे बड़े अक्षरों की एक तय संख्या में ठूँस दिया जाता है: न स्पेस, न एक्सेंट, न विराम चिह्न, और न कोई चेक डिजिट जो बताए कि कोई कैरेक्टर ग़लत पढ़ा गया।

यह पोस्ट बताती है कि ज़ोन नाम कैसे लिखता है, उसे वापस उपनाम और दिए गए नामों में कैसे बाँटें, लिप्यंतरण उसके साथ क्या करता है, कैसे पहचानें कि उसे छोटा किया गया हो सकता है, और उसे पेज पर छपे नाम से कैसे मिलाएँ। नीचे का हर नाम काल्पनिक है या ICAO का अपना नमूना, Anna Maria Eriksson; कोई वास्तविक व्यक्ति शामिल नहीं है।

यह doc.cheap का ब्लॉग है, जो एक पासपोर्ट और ID OCR API है। नीचे की किसी चीज़ को इसकी ज़रूरत नहीं; कोड की कोई डिपेंडेंसी नहीं है।

नाम कहाँ होता है

ICAO का Doc 9303, मशीन से पढ़े जाने वाले यात्रा दस्तावेज़ों का मानक, अपने तीनों लेआउट में नाम को तय चौड़ाई का एक फ़ील्ड देता है। यहाँ पोज़िशन 1 से गिनी गई हैं, जैसे मानक गिनता है:

फ़ॉर्मैट नाम कहाँ है चौड़ाई
TD3 (पासपोर्ट), 2 × 44 लाइन 1, पोज़िशन 6–44 39 कैरेक्टर
TD2, 2 × 36 लाइन 1, पोज़िशन 6–36 31 कैरेक्टर
TD1 (ID कार्ड), 3 × 30 लाइन 3, पूरी लाइन 30 कैरेक्टर

TD3 या TD2 की लाइन 1 के पहले पाँच कैरेक्टर दस्तावेज़ कोड और जारी करने वाला देश होते हैं; उनके बाद का सब कुछ नाम लेता है। TD1 नाम को उसकी अपनी लाइन देता है, फिर भी वह तीनों में सबसे सँकरा है। MRZ फ़ॉर्मैट पेज हर लेआउट को फ़ील्ड-दर-फ़ील्ड समझाता है।

TD3 नमूने में लाइन 1 है P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<। P<UTO हटा दें तो नाम फ़ील्ड ERIKSSON<<ANNA<MARIA है, जिसके बाद 39 कैरेक्टर तक फ़िलर आते हैं।

व्याकरण: <<, < और पैडिंग

ज़ोन की वर्णमाला A–Z, 0–9 और फ़िलर < है। नाम इसके ऊपर तीन परिपाटियाँ इस्तेमाल करता है:

  • प्राथमिक पहचानकर्ता पहले आता है। यह आम तौर पर उपनाम होता है।
  • दो फ़िलर, <<, प्राथमिक पहचानकर्ता को ख़त्म करते हैं। इसके बाद जो आता है वह द्वितीयक पहचानकर्ता है, आम तौर पर दिए गए नाम।
  • एक फ़िलर, <, किसी भी हिस्से के अंदर शब्दों को अलग करता है। ANNA<MARIA दो दिए गए नाम हैं; VAN<DER<BERG तीन शब्दों वाला उपनाम है।

नाम के बाद फ़ील्ड में जो बचता है, वह < से भरा जाता है। इसलिए नाम को वापस पढ़ना तीन कदम है: आख़िर के फ़िलर हटाएँ, पहले << पर बाँटें, और बचे हुए अकेले < को स्पेस में बदलें।

दो रूप अजीब दिखते हैं, पर ग़लती नहीं हैं। जिस व्यक्ति का एक ही नाम है, उसके पास सिर्फ़ प्राथमिक पहचानकर्ता होता है, इसलिए फ़ील्ड में नाम और फिर फ़िलर होते हैं, उनसे पहले कोई << नहीं: OKONKWO<<<<…। सेपरेटर के साथ लिखा गया और उसके बाद कुछ नहीं वाला फ़ील्ड, NAKAMURA<<<<…, आख़िर के फ़िलर हटाने के बाद बिल्कुल वैसे ही पढ़ा जाता है। दोनों ही मामलों में द्वितीयक पहचानकर्ता ख़ाली है, ग़ायब नहीं।

जो नहीं बचता, वह है विराम चिह्न और ख़ुद स्पेस। हाइफ़न, एपॉस्ट्रॉफ़ी या स्पेस ज़ोन में नहीं लिखे जा सकते, इसलिए छपा हुआ Smith-Jones SMITH<JONES के रूप में आता है, जिसे दो शब्दों वाले उपनाम से अलग नहीं किया जा सकता। अगर आपको विराम चिह्न चाहिए, तो वे छपे हुए पेज पर हैं, ज़ोन में नहीं।

लिप्यंतरण: किसी भी लिपि से 26 अक्षरों तक

हर नाम को आख़िरकार A–Z में आना होता है। डायक्रिटिक वाले लैटिन अक्षरों में छपे नाम के लिए इसका मतलब है हर एक्सेंट वाले अक्षर को सादे अक्षरों से जोड़ना। यूनानी, अरबी, सिरिलिक, हान या किसी और लिपि में लिखे नाम के लिए इसका मतलब है पूरा नाम लैटिन अक्षरों में लिखना।

Doc 9303 का भाग 3, जो हर फ़ॉर्मैट में साझा है, दोनों मामलों के लिए अनुशंसित लिप्यंतरण देता है। लेकिन फ़ैसला जारी करने वाले देश का होता है, और सभी देश एक जैसा नहीं चुनते। कोड के लिए तीन नतीजे मायने रखते हैं:

  1. एक छपे नाम की ज़ोन में एक से ज़्यादा वर्तनी हो सकती है। छपा हुआ MÜLLER जारीकर्ता के नियमों के हिसाब से ज़ोन में MUELLER या MULLER के रूप में आ सकता है। जो कोड एक तय मैपिंग की उम्मीद करता है, वह सही ज़ोन को ग़लत बता देगा।
  2. मैपिंग उलटी नहीं जा सकती। ज़ोन में MULLER पेज पर MULLER, MÜLLER या MÚLLER से आ सकता है। ज़ोन आपको बताता है कि उसमें कौन-से अक्षर हैं, वह नाम नहीं जो व्यक्ति लिखता है।
  3. पेज पर छपा लैटिन नाम ज़ोन से अलग हो सकता है। राष्ट्रीय लिपि में छपा दस्तावेज़ अक्सर एक लैटिन रूप भी छापता है, और वह छपी लैटिन वर्तनी हमेशा अक्षरशः ज़ोन वाली नहीं होती। किसी अंतर को देखने लायक चीज़ मानें, ग़लत पढ़ने का सबूत नहीं।

व्यावहारिक नियम: ज़ोन के नाम की छपे नाम से तुलना दोनों को सिर्फ़ अक्षरों तक घटाने के बाद करें, उन वर्तनियों की गुंजाइश रखें जो आपके जारीकर्ता असल में इस्तेमाल करते हैं, और जो कुछ इंसान पढ़ता है उसके लिए छपी वर्तनी रखें।

काट-छाँट: जब नाम फ़िट न हो

लंबा नाम अगली लाइन पर नहीं जाता। उसे फ़ील्ड में फ़िट करने के लिए छोटा किया जाता है, और पूरा नाम छपे पेज पर रहता है। मानक नाम छोटा करने के नियम देता है, और छोटा किया गया नाम आम तौर पर अपने फ़ील्ड के आख़िरी कैरेक्टर तक जाता है।

इससे पार्सर को उसका इकलौता संकेत मिलता है: अगर नाम फ़ील्ड का आख़िरी कैरेक्टर < के बजाय कोई अक्षर है, तो नाम काटा गया हो सकता है। यह "हो सकता है" है, "काटा गया" नहीं: जो नाम ठीक फ़ील्ड जितना लंबा है, वह भी अक्षर पर ख़त्म होता है। लेकिन जो फ़ील्ड फ़िलर पर ख़त्म होता है, वह निश्चित रूप से नहीं काटा गया।

चौड़ाई मायने रखती है। एक काल्पनिक धारक लें, Konstantin Friedrich Hartmann Schoenebeck। 39 कैरेक्टर वाले TD3 फ़ील्ड में:

HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR

आख़िरी कैरेक्टर R है, एक अक्षर, इसलिए नाम काटा गया हो सकता है, और काटा गया है: FRIEDRICH के तीन अक्षर चले गए। वही नाम TD1 की 30 चौड़ाई पर काटा जाए तो सिर्फ़ HARTMANN<SCHOENEBECK<<KONSTANT बचता है। जो सिस्टम ज़ोन के नाम को धारक के नाम के रूप में सेव करता है, वह KONSTANT को पहले नाम के रूप में सेव करता है।

इससे दो आदतें निकलती हैं। जब फ़ील्ड भरा हो, तो ज़ोन के नाम को कभी दिखाए जाने वाले नाम के रूप में इस्तेमाल न करें; छपा हुआ नाम इस्तेमाल करें। और जब आप पूरे छपे नाम को ज़ोन के नाम से मिलाते हैं, तो फ़ील्ड भरा होने पर ज़ोन के नाम से शुरू होने वाला छपा नाम मेल माना जाता है।

जारीकर्ता नाम को अंत काटने के अलावा दूसरे तरीक़ों से भी छोटा कर सकता है, जैसे दूसरे दिए गए नाम को उसके पहले अक्षर तक घटाकर। प्रीफ़िक्स टेस्ट ऐसे नाम को बेमेल बताता है। ऐसे मामले में दस्तावेज़ अस्वीकार करने के बजाय उसे किसी इंसान के पास भेजें।

नाम को कोई चेक डिजिट नहीं ढकता

चेक डिजिट दस्तावेज़ संख्या, दोनों तारीख़ों, TD3 पर वैकल्पिक व्यक्तिगत संख्या और निचली लाइन के ज़्यादातर हिस्से पर एक कंपोज़िट को ढकते हैं। उनमें से कोई भी नाम को नहीं ढकता। TD3 और TD2 पर नाम लाइन 1 में है, जिसे कोई डिजिट छूता ही नहीं; TD1 पर वह लाइन 3 में है, और कंपोज़िट लाइन 2 पर रुक जाता है।

इसलिए नाम में ग़लत पढ़ाई चुपचाप होती है। N के रूप में पढ़ा गया M, या अक्षर के रूप में पढ़ा गया फ़िलर, फिर भी ऐसा ज़ोन देता है जिसका हिसाब बराबर बैठता है। नाम के लिए इकलौती क्रॉस-चेक छपा पेज है: दोनों पढ़ें और उनकी तुलना करें, और नीचे वाला तुलना फ़ंक्शन इसी के लिए है। MRZ चेक डिजिट कैसे काम करते हैं में उन फ़ील्ड का गणित है जो ढके जाते हैं।

Python में फ़ील्ड बाँटना

कोई डिपेंडेंसी नहीं। फ़ंक्शन सिर्फ़ नाम फ़ील्ड लेता है, जो पहले ही अपनी लाइन से काटकर निकाला जा चुका है (TD3 पर line1[5:44], TD2 पर line1[5:36], TD1 पर line3)।

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 सिर्फ़ पहले << पर बाँटता है और जब कोई सेपरेटर न हो तो ख़ाली द्वितीयक हिस्सा लौटाता है, जो एक नाम वाला मामला है। पैटर्न जान-बूझकर अंकों को अस्वीकार करता है: नाम फ़ील्ड में 0 ग़लत पढ़ा गया O है, और उसे नाम के रूप में छपने के बजाय किसी इंसान तक पहुँचना चाहिए।

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 };
}

ज़ोन की छपे नाम से तुलना

छपा नाम विज़ुअल ज़ोन से आता है, यानी पेज का वह हिस्सा जिसे इंसान पढ़ता है, अपने एक्सेंट और विराम चिह्नों के साथ। नीचे वाला फ़ंक्शन उसे सिर्फ़ बड़े अक्षरों तक घटाता है और ज़ोन के नाम के एक हिस्से से तुलना करता है। यह तीन शब्दों में से एक लौटाता है: match, mismatch, या unknown जब छपे नाम में ऐसा अक्षर हो जो अपने-आप A–Z में नहीं घटता, जैसे Ø, Æ या Ł।

import re
import unicodedata

# The spellings to try besides plain removal of the accent. These three are
# an example; put in the ones your issuers use.
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  # a letter with no plain A-Z form: leave it to a person
        if letters == zone or (truncated and letters.startswith(zone)):
            return "match"
        verdict = "mismatch"
    return verdict

NFKD एक्सेंट वाले अक्षर को सादे अक्षर और उसके एक्सेंट में तोड़ता है, और एक्सेंट हटा दिया जाता है। स्पेस, हाइफ़न और एपॉस्ट्रॉफ़ी भी हटा दिए जाते हैं, क्योंकि तुलना सिर्फ़ अक्षरों पर होती है: Smith-Jones और SMITH JONES दोनों SMITHJONES बन जाते हैं। जो नाम सिर्फ़ गैर-लैटिन लिपि में छपा है, उसके लिए ज़ोन की तुलना पेज पर छपे लैटिन रूप से करें, राष्ट्रीय लिपि वाले रूप से नहीं; राष्ट्रीय लिपि यहाँ हर बार unknown लौटाएगी।

टेस्ट वेक्टर

ICAO के नमूने को छोड़कर हर नाम काल्पनिक है। पहली तालिका में हर फ़ील्ड को < से TD3 की 39 चौड़ाई तक भरा जाता है (Python में name.ljust(39, "<")), सिवाय आख़िरी पंक्ति के, जो पहले से ठीक 39 कैरेक्टर की है।

पैडिंग से पहले फ़ील्ड प्राथमिक द्वितीयक काटा गया हो सकता है
ERIKSSON<<ANNA<MARIA ERIKSSON ANNA MARIA नहीं
VAN<DER<BERG<<PIETER VAN DER BERG PIETER नहीं
SMITH<JONES<<MARY SMITH JONES MARY नहीं
OKONKWO OKONKWO (ख़ाली) नहीं
NAKAMURA<< NAKAMURA (ख़ाली) नहीं
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR HARTMANN SCHOENEBECK KONSTANTIN FRIEDR हाँ

और compare_name के लिए:

ज़ोन का हिस्सा छपा हिस्सा काटा गया अपेक्षित
ANNA MARIA Anna Maria नहीं match
SMITH JONES Smith-Jones नहीं match
MULLER Müller नहीं match
MUELLER Müller नहीं match (एक्सपैंशन के ज़रिए)
MILLER Müller नहीं mismatch
KONSTANTIN FRIEDR Konstantin Friedrich हाँ match
KONSTANTIN FRIEDR Konstantin Friedrich नहीं mismatch
SORENSEN Sørensen नहीं unknown

आख़िरी पंक्ति जान-बूझकर है। Ø O बना या OE, यह जारीकर्ता का चुनाव है, और अंदाज़ा लगाने वाला फ़ंक्शन किसी न किसी के लिए ग़लत होगा; इसके बजाय अपने जारीकर्ताओं का इस्तेमाल किया गया रूप एक्सपैंशन में जोड़ें।

हमारा MRZ पार्सर क्या करता है

इस साइट का MRZ पार्सर नाम को इसी तरह पढ़ता है: आख़िर के फ़िलर हटाता है, पहले << पर बाँटता है, और अकेले फ़िलर को स्पेस में बदलता है; बिना << वाला फ़ील्ड सिर्फ़ उपनाम के रूप में पढ़ा जाता है। जब नाम अपने फ़ील्ड के आख़िरी कैरेक्टर तक जाता है, तो यह चेतावनी दिखाता है कि नाम फ़िट करने के लिए काटा गया हो सकता है, साथ में वे पोज़िशन जो वह भरता है। यह ब्राउज़र में चलता है, इसलिए आप एक सिंथेटिक ज़ोन पेस्ट करके उसकी रीडिंग की तुलना अपने कोड से कर सकते हैं।

पाइपलाइन में यह कहाँ आता है

होस्ट की गई पहचान सेवा ज़ोन और छपे पेज दोनों को पढ़ती है, इसलिए सवाल यह है कि वह आपको कौन-सा नाम देती है। doc.cheap रिस्पॉन्स में holder.surname, holder.given_names और holder.full_name मिली-जुली लैटिन रीडिंग रखते हैं। राष्ट्रीय लिपि में छपा नाम fields की एक अलग एंट्री है: हर रीडिंग की अपनी एंट्री होती है, डिफ़ॉल्ट लैटिन रीडिंग की id @0 पर ख़त्म होती है, और राष्ट्रीय लिपि वाली रीडिंग अपनी language बताती है, जैसे Greek। ज़ोन ख़ुद mrz.lines में है, ठीक वैसा जैसा पढ़ा गया, ताकि आप नाम फ़ील्ड काटकर उस पर ऊपर के दोनों फ़ंक्शन चला सकें। गैर-लैटिन लिपियों वाली गाइड दिखाती है कि रीडिंग को कैसे समूहित करें और दोनों वर्तनियाँ कैसे सहेजें। अगर आप अभी सेवा चुन रहे हैं, तो पासपोर्ट OCR API तुलना प्रकाशित क़ीमतों को आमने-सामने रखती है।

अगर आपको कोई ऐसा नाम मिले जिसे ऊपर का कोड ग़लत पढ़ता है, तो admin@doc.cheap पर लिखें।

आपसे एक सवाल: ज़ोन की छपे नाम से तुलना करते समय किस लिप्यंतरण ने आपको सबसे ज़्यादा चौंकाया? नीचे कमेंट में बताइए।