Every "AI does the paperwork now" story eventually has to answer the question engineers actually care about: okay, but how? Not the marketing version. The real one, with the awkward edge cases and the design decisions nobody puts in a slide deck.
So let's open the hood on the MITS AI Letter Writer and walk through what genuinely happens between the moment someone uploads a .docx file and the moment a zip folder of finished letters lands in their downloads.
Step One: The Upload Isn't Trusted, Ever
The moment a file hits the server, it's treated as hostile until proven otherwise. Only .docx is accepted, capped at 10MB, and the file type is verified before a single byte of it is processed — no executing macros, no trusting embedded content, no exceptions. It's a small detail, but it's the difference between "internal HR tool" and "internal HR tool that got someone's resume turned into an attack vector."
Once it clears that gate, DocumentService.ExtractTextFromDocx() pulls the raw text out of the Word document using DocumentFormat.OpenXml — Microsoft's own library for reading and writing Office files at the XML level, rather than shelling out to Word itself.
Step Two: The First AI Call — Reading, Not Writing
This is where it gets interesting. The extracted text is handed to GPT-4o through Microsoft's Semantic Kernel framework, and the model's only job on this first call is extraction: candidate name, employee ID, designation, client name, employee group (strictly "BNP" or "MITS2" — the system is picky about this because it drives which template gets selected later), date of joining, address, and CTC.
The result lands in a strongly typed model called FirstCallResponse — not a loose dictionary of guesses, but a defined shape the rest of the application can trust. It even gets written to disk as firstCallResponse.json alongside the final output, purely so a human can audit exactly what the AI believed it read, months later, if a letter is ever questioned. That audit trail matters more than it sounds like it would — the day someone asks "why does this letter say ₹8,40,000," you want a paper trail, not a shrug.
Step Three: The Salary Engine Doesn't Ask the AI for Math
Here's a deliberate and, frankly, correct design choice: the CTC breakdown is never computed by the language model. Language models are excellent at reading a messy paragraph and pulling structured meaning out of it. They are not what you want doing payroll arithmetic that someone's take-home salary depends on.
Instead, SalaryService.CalculateSalaryBreakdown() runs pure, deterministic C# logic against a tiered table. Annual CTC up to ₹5,00,000 maps to a monthly Basic of ₹24,000. Up to ₹7,00,000, it's ₹28,000. The tiers step upward — ₹8L, ₹9L, ₹10L, ₹12L, all the way to ₹71L — and above that, Basic becomes a flat 40% of monthly CTC. HRA is 5% of Basic under the ₹5L threshold, 50% above it. Employer and Employee PF are each 12% of a ₹15,000 ceiling, so ₹1,800 a month, toggleable per candidate. Gratuity runs 4.81% of Basic. Mediclaim is a flat ₹650. Professional Tax, ₹200, always included, no toggle — it's a compliance line item that doesn't get to be optional. Whatever's left over after all of that gets absorbed into an Executive Allowance, calculated last, so the total always lands exactly on target CTC.
No hallucination risk. No "the AI rounded weird." Just a lookup table and arithmetic, which is exactly what payroll should be.
Step Four: The Second AI Call — Writing, Not Reading
With the structured data extracted and the salary math done, the AI comes back for a second, narrower job: turning roles-and-responsibilities notes into properly formatted letter content, and detecting which of the optional letters — Joining Bonus, Conveyance Reimbursement, Relocation Reimbursement, Security Documents — actually apply, based on what it finds in the source text. This is the "does this candidate need a joining bonus letter" judgment call that used to live entirely in an HR person's head.
Step Five: Templates, Not Generation from Scratch
This is the part people are usually surprised by: the AI does not write the letters from a blank page. MITS maintains real Word templates — BNP_OfferLetter_Template.docx, MITS2_OfferLetter_Template.docx, and a set of shared templates for contract, joining bonus, relieving, and FNF letters — each containing placeholders in a strict {{PascalCase}} format like {{CandidateName}} or {{AnnualCTC}}. DocumentService.GenerateLetterFromTemplate() walks the template and swaps every placeholder for real data.
This matters more than it looks like it should. It means legal, branding, and formatting stay entirely in human hands, locked into a Word file HR can open and edit directly. The AI never touches letterhead, never touches legal phrasing, never improvises language in a document with someone's signature on it. It fills in blanks. That's it. It's a small architectural decision that quietly removes an entire category of risk.
Two small helpers do the finishing work: DateHelper renders dates with proper superscript ordinals — 15th actually rendered with a raised "th," the way a human typist would do it — and AddressHelper preserves genuine line breaks in a multi-line address while still letting long lines wrap naturally.
Step Six: Everything Comes Out in One ZIP
Finally, every generated letter for that candidate gets bundled into a single zip, streamed straight to a MemoryStream rather than written to disk twice, and saved under a folder named for the pattern {EmployeeGroup}_{CandidateName}_{Timestamp} — so six months later, nobody has to guess which run produced which letter.
Two AI calls. One deterministic salary engine. A library of real Word templates. That's the entire trick. It's not magic — it's just each part of the problem handed to the tool that's actually good at it.
MITS AI Letter Writer · part 2 of 5Second in a five-part series. Next: the salary table nobody wants to get wrong, and why ₹1 either side of a bracket boundary matters more than you'd think.
