ব্যাংক স্টেটমেন্ট খোলা কারো জন্যই মজার কাজ নয়। এগুলো স্ক্যান করা PDF, CSV এক্সপোর্ট বা OFX-এর মতো রহস্যময় সংক্ষিপ্তনামে সজ্জিত XML ফাইল হিসেবে আসে। অ্যাকাউন্ট্যান্ট, বুককিপার এবং ফিনটেক নির্মাতাদের জন্য, এই নথিগুলোকে পরিচ্ছন্ন ও সুসংগঠিত ডেটাতে রূপান্তর করা একটি নিরন্তর মাথাব্যথার কারণ। যখন লার্জ ল্যাঙ্গুয়েজ মডেলগুলো (LLMs) দৃশ্যপটে এলো, তখন মনে হলো তারা যেন একটি মুক্তির পথ দেখাচ্ছে। শুধু মেশিনটিকে একটি PDF দিন এবং JSON চান। এতে ভুল কী হতে পারে?

ব্যাংক স্টেটমেন্টকে ব্যবহারযোগ্য ডেটাতে রূপান্তর করার জন্য তৈরি টুল StatementDecoder তৈরির সময় আমি ঠিক কী কী ভুল হতে পারে তা শিখতে পেরেছি। অনেক ডেভেলপারের মতো আমিও ধরে নিয়েছিলাম যে, সিস্টেমকে বিভিন্ন ধরণের ডকুমেন্টের লেআউট পড়তে শেখানোই হবে কঠিন কাজ। আমি ভুল ছিলাম। ডকুমেন্ট পড়া ছিল প্রায় অতি সামান্য কাজ। আসল দুঃস্বপ্ন ছিল যখন মেশিনটি নিঃশব্দে একটি সংখ্যা বানিয়ে ফেলত বা লেনদেনের পরিমাণের দুটি অঙ্ক অদলবদল করে ফেলত, তা শনাক্ত করা।

সেই ডেমো যা অতিরিক্ত ভালো কাজ করেছিল

আমার প্রথম প্রচেষ্টা ছিল অত্যন্ত সহজ ও আকর্ষণীয়। আমি সরাসরি একটি LLM-এ ব্যাংক স্টেটমেন্ট ইনপুট হিসেবে দিচ্ছিলাম এবং বিনিময়ে সুসংগঠিত JSON চাইছিলাম। ফলাফলগুলো জাদুর মতো মনে হচ্ছিল। মডেলটি সহজেই বিভিন্ন লেআউট সামলাচ্ছিল। এটি সেই সব স্ক্যান করা PDF পড়তে পারছিল যা সাধারণ পার্সারগুলো (parsers) সামলাতে হিমশিম খেত। কোনো সুনির্দিষ্ট নির্দেশনা ছাড়াই এটি টেবিল, হেডার এবং বহু-পৃষ্ঠার স্টেটমেন্ট বুঝতে পারছিল বলে মনে হচ্ছিল। কয়েক ঘণ্টা আমি ভেবেছিলাম সমস্যাটি সমাধান হয়ে গেছে।

তারপর আমি আসল গ্রাহকের ডেটার বিপরীতে এটি পরীক্ষা করলাম, এবং জাদুর রেশ মিলিয়ে গেল। যুক্তরাজ্যের প্রতিটি ব্যাংক তাদের নিজস্ব স্টেটমেন্ট ডিজাইন ব্যবহার করে এবং এই পার্থক্যগুলো কেবল বাহ্যিক নয়। Wise স্টেটমেন্টের নিজস্ব ফরম্যাটিং বৈশিষ্ট্য রয়েছে। Revolut CSV এক্সপোর্টগুলো দেখতে সহজ মনে হলেও, মাল্টি-কারেন্সি লেনদেন এবং মেটাডেটা ফিল্ডগুলো তারা কীভাবে হ্যান্ডেল করে তা লক্ষ্য করলে ভিন্ন কথা। পুরনো OFX ফাইলগুলো—যা দেখে সত্যিই মনে হয় যেন এটি ১৯৯০ সালের কোনো ফরম্যাট—আধুনিক মার্কআপ প্রত্যাশা করা যেকোনো পার্সারের সামনে প্রাচীন ট্যাগ স্ট্রাকচার এবং এনকোডিং সমস্যা ছুড়ে দেয়।

মডেলটি তবুও যেকোনো সাধারণ টেমপ্লেট সিস্টেমের চেয়ে অনেক ভালো ডেটা এক্সট্রাক্ট করতে পারছিল। কিন্তু যখন অর্থের বিষয় জড়িত থাকে, তখন 'অনেক ভালো' হওয়া যথেষ্ট নয়।

যখন ৯৯% নির্ভুলতাও একটি ব্যর্থতা

আর্থিক ডেটা এক্সট্রাকশনের জন্য AI ব্যবহারের মূল সমস্যাটি এখানেই। যদি একটি মডেল দুইশটি লেনদেনের সারি প্রসেস করে এবং তার মধ্যে একশ নিরানব্বইটি সঠিক হয়, তবে আউটপুটটি একদম নিখুঁত দেখাবে। JSON ফরম্যাটটি সঠিক থাকবে। কী (keys) এবং ভ্যালুগুলো (values) মিলে যাবে। সাধারণ পর্যালোচনায় কোনো সন্দেহজনক কিছু চোখে পড়বে না। তবুও যদি সেই একটি মাত্র ভুলের কারণে কোনো অ্যামাউন্টের দুটি অঙ্ক অদলবদল হয়, ডিপোজিটকে উইথড্রয়াল হিসেবে দেখায়, অথবা দশমিকের স্থান পরিবর্তন করে দেয়, তবে আপনার বুককিপিং নষ্ট হয়ে যাবে। সুসংগঠিত ডেটার স্তূপ শুধু চোখ বুলিয়ে আপনি এটি ধরতে পারবেন না।

একজন মানুষ যখন র (raw) JSON রিভিউ করেন, তখন লেনদেনের পরিমাণের অদলবদল হওয়া অঙ্ক খুব কমই চোখে পড়ে। ফরম্যাটিং নিখুঁত হওয়ার কারণে এটি আপাতবিরোধীভাবে ভুলটিকে আরও বিপজ্জনক করে তোলে। আপনি এমন কোনো আর্থিক টুল বাজারে ছাড়তে পারেন না যা বেশিরভাগ সময় সঠিক হয়। এটিকে অবশ্যই সঠিক হতে হবে, অথবা এটি নিশ্চিত না হলে স্পষ্টভাবে তা জানিয়ে দিতে হবে।

আমার প্রাথমিক প্রতিক্রিয়া ছিল অনুমেয়। আমি আরও উন্নত প্রম্পট তৈরি করলাম। আরও শক্তিশালী মডেল ব্যবহার করলাম। মডেলটি কীভাবে কাজ করছে তা দেখানোর জন্য 'chain-of-thought reasoning' নিয়ে পরীক্ষা করলাম। এর কোনোটিই মূল সমস্যা সমাধান করতে পারেনি। আমি একই প্রোবাবিলিস্টিক (probabilistic) সিস্টেমকে একটি উত্তর তৈরি করতে বলছিলাম এবং তারপর সেই একই সিস্টেমকে সেই উত্তরটি সঠিক কিনা তা নিশ্চিত করতে বলছিলাম। এটি যাচাইকরণ (verification) নয়; এটি কেবল আত্ম-সামঞ্জস্যতা প্রদর্শনের একটি নাটক (self-consistency theater)।

গণিতকে সিদ্ধান্ত নিতে দিন

ব্যাংক স্টেটমেন্টের এমন একটি বৈশিষ্ট্য আছে যা বেশিরভাগ ডকুমেন্টে থাকে না: বিল্ট-ইন গাণিতিক সীমাবদ্ধতা (arithmetic constraints)। ওপেনিং ব্যালেন্স এবং সমস্ত লেনদেনের যোগফল অবশ্যই ক্লোজিং ব্যালেন্সের সমান হতে হবে। যদি রানিং ব্যালেন্স থাকে, তবে তা প্রতিটি সারি অনুযায়ী মিলতে হবে। এগুলো কোনো শৈল্পিক পছন্দ নয়; এগুলো কঠোর নিয়ম।

আমি এই ধারণাটির ওপর ভিত্তি করে আর্কিটেকচারটি পুনরায় তৈরি করেছি। এখন, উৎস যাই হোক না কেন, প্রতিটি এক্সট্রাকশন ব্যবহারকারীর কাছে পৌঁছানোর আগে একটি ভ্যালিডেশন লেয়ারের (validation layer) মধ্য দিয়ে যায়। ডেটাটি একটি অস্পষ্ট PDF ব্যাখ্যা করা LLM থেকে আসুক, একটি স্ক্যান করা পেজ পড়া OCR ইঞ্জিন থেকে আসুক, বা সরাসরি CSV পার্স থেকে আসুক—তাতে কিছু যায় আসে না। ভ্যালিডেটর সব উৎসকেই সমানভাবে সন্দেহজনক হিসেবে বিবেচনা করে।

এই পরীক্ষাটি অত্যন্ত সহজ। ওপেনিং ব্যালেন্সের সাথে প্রতিটি লেনদেন যোগ করুন। প্রাপ্ত ফলাফলটি উল্লিখিত ক্লোজিং ব্যালেন্সের সাথে তুলনা করুন। যদি সংখ্যাগুলো না মিলে, তবে বুঝতে হবে কিছু ভুল হয়েছে। স্টেটমেন্টটিকে পর্যালোচনার জন্য ফ্ল্যাগ (flag) করুন। এক্সট্রাকশনটি বাতিল করুন। এটি ব্যবহারকারীর কাছে পৌঁছাতে দেবেন না।

এই একটি পরিবর্তন পণ্যের পুরো চরিত্র বদলে দিয়েছে। ল্যাঙ্গুয়েজ মডেলটিকে আর নিখুঁত হওয়ার প্রয়োজন নেই। এটিকে কেবল এমন আউটপুট দেওয়ার জন্য যথেষ্ট ভালো হতে হবে যা একটি গাণিতিক পরীক্ষায় টিকে যেতে পারে। এর ফলে চাপের জায়গাটি বদলে গেছে—অসংজ্ঞায়িত কোনো ক্ষেত্রে অসম্ভব নির্ভুলতা অর্জন করার পরিবর্তে এখন লক্ষ্য হলো জেনারেশন এবং ভেরিফিকেশনের মধ্যে একটি শক্তিশালী ফিডব্যাক লুপ তৈরি করা।

The validator also exposed patterns in the errors. Certain document types consistently failed the math check, which told me exactly where to invest effort. Instead of blindly improving prompt engineering across the board, I could see that specific bank layouts caused systematic mistakes.

Code Where Code Belongs, AI Where It Shines

Perhaps the most humbling lesson was realizing how much of the pipeline did not need AI at all. When I encountered messy Australian OFX files, my instinct was to throw tokens at the problem. I briefly considered feeding the broken XML to the model and asking it to repair the structure before parsing. Instead, I wrote twenty lines of deterministic code. It fixed the encoding quirks and malformed tags instantly, with zero cost per file and perfect reproducibility.

That experience crystallized how extraction pipelines should be organized. There are three distinct jobs, and they should not be mixed together.

  • The model understands messy documents. Scanned PDFs with warped tables, mixed fonts, and handwritten