Where you are. Fourteen lessons built every rail on the map: RTGS and the liquidity bill its immediacy runs up, the queues and the frontier they trade along, gridlock and the offsetting that cures it, batch rails netting a day down to differences, instant rails answering in seconds, the card stack with its interchange and chargebacks, and the wallets that ride on top of everything. Every one of those rails ran on your miniledger through the same clean call: submit, five typed arguments, nothing to interpret. Real rails are not called; they are messaged. The instruction arrives over a wire, formatted by the sending bank, and something at the receiving end must turn it into that clean call. This lesson is about that format: the fifty-year free-text inheritance, the structured standard retiring it, and the dates of the changeover. It is the last idea standing between you and the project.
Two messages, one payment
Monday morning, Alder’s payments desk. Two instructions sit on one screen, and both ask for the same thing: HOLT pays DOYLE 4,300. The first is five labelled fields:
{"debtor": "HOLT", "creditor": "DOYLE", "amount": 4300,
"currency": "GBP", "reference": "4821"}
One line of glue turns that into a call you have made all module: world.submit(4300, "Alder", "HOLT", "Birch", "DOYLE"). No judgement was involved; the fields say what they mean and sit where they belong. The second instruction carries the identical payment the way bank wires carried payments for half a century:
REF 4821 TRANSFER 4,300 FROM HOLT TO DOYLE
You read that correctly in two seconds, because you are a human with context. A program has to hunt, and the hunting goes wrong in ways worth naming: the first run of digits is 4821, which is the reference, not the amount; the amount has a thousands comma in it, so a digit-grabber that dodges the reference reads 4 and stops. The program either guesses - and sometimes guesses wrong without knowing it - or gives up and queues the message for a person. Now multiply by every payment crossing every rail you have built this module. The lesson’s question is the practical one: what does an instruction have to look like before a machine can be trusted to run it end to end?
The idea in one paragraph
ISO 20022 is an open international standard that gives payment messages a schema. Every fact a payment carries - who pays, who is paid, how much, in which currency, under what reference, from which address - has one named field with one type in one place, where the formats it replaces packed those facts into free text arranged in each bank’s house style. The consequences all point the same way: a structured message parses completely or fails loudly, so machines can run payments without a human interpreting the wire; screening software that knows which token is a name raises fewer false alarms, and those false alarms are where much cross-border delay hides; and a message relayed bank to bank arrives with its data intact instead of paraphrased. The exercise puts numbers on the gap: twenty stylised payments rendered both ways, where the structured renderings parse twenty for twenty and settle straight into the miniledger untouched, while the free-text renderings fail six times and - worse - parse wrong six times. Structure is the precondition for machine-run payments. That is the claim; the rest of the lesson is the evidence.
Fifty years of house styles
The formats ISO 20022 retires were born on the teletype, and they never stopped assuming a human reader. Swift - the network banks use to send each other payment messages; module 3 gives it a lesson of its own - carried them as the MT formats, MT for message type, with a numbered tag on every line: :20: introduces a reference, :32A: a date, a currency and an amount. From a distance that looks like structure. Up close, the structure runs out exactly where the risk begins: the fields naming the payer and the beneficiary are a few free-text lines of thirty-five characters each, and the standard says almost nothing about what goes on which line. Name, street, account, town, country - in any order, abbreviated any way the sending bank likes.
So every bank developed a house style, the way the telex operators’ B/O and BNF - by order of, and beneficiary - had been habits before they were conventions. And every receiving bank developed a parser for everybody else’s house styles: a pile of pattern-matches grown one incident at a time, each rule patched in after a message it misread. The exercise’s generator is a miniature of this world - six templates, six ways to phrase one payment - and your job there is to write the receiving side honestly, which means naively.
The arrangement held for fifty years because a human sat at the end of the line: an operator who read the blob, repaired it and rekeyed it. What broke it was volume. The rails you built in this module settle in seconds or run all night unattended, and a format that needs a person in the read path caps every one of them at the speed of its slowest human.
What structure buys
ISO 20022 - ISO because the standard comes from the international standards body, 20022 because that is simply its catalogue number - defines a dictionary of business elements and a family of messages assembled from them. The customer payment travels as a message called pacs.008, in which the debtor, the creditor, the amount and its currency, the end-to-end reference and every address are separate, typed elements; the postal address is itself structured, with street name, building number, town and country each in its own slot. The exercise’s structured rendering is that message stripped to the five fields your miniledger needs, and its parser is the whole argument in three lookups: read debtor, read creditor, read amount. No hunting, no order, no comma trap.
Wider than the screen; scroll it sideways.
What happens when a structured message is broken? It fails loudly. Delete a field from the dict and parse_structured raises KeyError on the spot: absence has a name. That trade - silent-wrong exchanged for loud-absent - is the property that lets a machine run unattended, because a machine can be trusted to stop on an exception in a way it cannot be trusted to notice a plausible-looking lie.
The second payoff sits further from the code but larger in the world. Banks must screen payments against sanctions lists - government blocklists of people, firms, vessels and places that money must not reach - and a screen pointed at free text has to treat every token in the blob as a possible name. A street named after a blocked person trips it; so can a word inside a reference. Every hit parks the payment in a queue for a human to clear, and most hits are false. Point the same screen at structured fields and the search space collapses: names are screened as names, streets as streets, references as references. Fewer false alarms is not a comfort metric; those review queues are where much of the delay you will meet in module 3 hides.
The third payoff is about distance. A domestic payment makes one hop, but module 3’s cross-border payments relay through chains of intermediary banks, and in the free-text world every relay was a lossy re-reading: a chance to truncate an address into a thirty-five-character line, abbreviate a name, rekey a digit. A structured message survives the chain because a relay forwards fields it never has to interpret. The data the first bank wrote is the data the last bank reads.
The migration, with dates
Nothing about ISO 20022 belongs to one rail. It is an open standard: a batch rail, an instant rail and a heavy-duty gross rail can all speak it, and the newest rails you met in lesson 9 were largely born speaking it. That is what the title means by one language for every rail - not that every rail has finished migrating, but that for the first time there is one schema they can all migrate to. The hardest stretch was the cross-border messaging network itself, because nobody can switch every bank on the planet at once.
Check yourself
1. Your free-text parser returns ("HOLT", "DOYLE", 4821) for the blob REF 4821 TRANSFER 4,300 FROM HOLT TO DOYLE - cleanly typed, no exception. Why does the exercise treat this as worse than the parses that return None?
Because it is invisible. A None announces that the message needs a human, and the payment waits: annoying, but safe. The false match hands downstream systems a well-typed lie - the reference where the amount should be. In the exercise the generator kept the truth beside every blob, so the counter catches it; a production parser has no truth column, and the error surfaces later, as a wrong amount settling or a reconciliation break, after the machine has confidently acted on it. Failure is a cost; false confidence is a risk.
2. parse_structured has no regex, no keyword hunt and no fallback, yet it scores twenty for twenty. What property of the format does the work, and what happens when a structured message is broken?
The schema promises one place and one meaning per field, so reading is lookup, not search: message["amount"] is the amount because the standard says nothing else may be. Break the message - delete a field - and the parser raises KeyError immediately: absence is loud, at parse time, naming the missing piece. The format converts silent-wrong into loud-absent, and that conversion is what makes unattended processing safe: a machine can be trusted to stop on an exception in a way it cannot be trusted to notice a plausible lie.
3. Sanctions screening raises false alarms against free text and fewer against ISO 20022, even though the screening lists are identical. What changed?
The search space. Against a blob, every token is a candidate name, so a street named after a blocked person, or a word inside a reference, can trip the screen - and each trip parks the payment for human review. Structured fields scope the check: name elements are screened as names, street elements as streets, references as references. The list did not change and neither did the payment; what changed is that the message now tells the screen what each token is, so it stops alarming on tokens that were never parties. Fewer false alarms means fewer queued payments, which is why so much settlement delay traces back to message format.
4. The migration ran for years with both formats accepted, and adoption still lagged when the deadline arrived. Why was a hard cutover date necessary - why not let the better format win on merit?
Because a reader that accepts both formats forever removes every writer’s reason to move. While coexistence lasts, a bank that keeps sending the old format loses nothing: receivers translate, the payment settles, and the cost lands downstream on everyone else as parsing, repair and review queues. That is a coordination failure, and merit does not fix coordination failures; deadlines do. Retiring the legacy read path makes the old format not late but broken - and the follow-on rule against unstructured addresses is the same lever, applied one field at a time.
Do this
Fifteen minutes, from module-02-domestic-rails, standard library only. Open code/message_parse.py. The generator is written: twenty stylised payments, seeded so every run is identical, each rendered twice - once as the five-field structured dict, once as a free-text blob in one of six house styles, two of which carry the traps you have already met. parse_structured also ships written: three lookups, no guessing. The TODO(you) is parse_free_text, and the brief is to keep it naive on purpose: take the first run of digits in the blob as the amount, the word after FROM as the payer, the word after TO as the payee, and return None when any of the three hunts misses. Resist the urge to harden it. The naivety is the measurement - it stands in for fifty years of best-effort parsers, and the comparison only means something if yours guesses the way real ones did.
python3 code/message_parse.py
The demo counts both parsers against the truth the generator kept, asserts the shape of the result - the structured side perfect, the free-text side failing and, worse, lying - then feeds the twenty structured messages straight into the miniledger and settles them with no human touch. Green looks like this:
source correct failures false matches
structured ISO 20 0 0
free text 8 6 6
machine run: 20 of 20 structured instructions settled untouched; free text failed 6 and lied 6
structure is the precondition for machine-run payments
The six failures are the two house styles with no FROM or TO to hunt for; the six false matches are the reference arriving before the amount and the thousands comma splitting it. If your false-match count comes out zero, your parser is too clever - you stripped the comma, or skipped past the reference - which is exactly the hardening the TODO asked you to resist; fifty years of production parsers were not that lucky. The completed version is solutions/message_parse.py; compare after you are green.
What you can now do. You can parse the two renderings of a payment and put numbers on the gap: a schema that reads twenty for twenty and settles untouched, against free text that fails when its keywords vanish and lies when its digits mislead. You can name what structure buys - loud failure instead of silent guessing, screening that alarms on names instead of street signs, data that survives relaying - and you can date the migration that delivered it: the coexistence period closed, the adoption that lagged it, the address rule still ahead. And you can say what structure does not buy: a wrong value in a perfect field is still wrong, which keeps checking what the fields say the rail’s job, exactly where your miniledger’s submit already does it. Next is the project - one synthetic payment set through the gross, batch and instant engines you built, measuring liquidity used against delay accepted and curing a gridlock on the way. Its instructions go straight into submit, already structured; you now know that is not a simplification of the real world but the state the real world spent a migration reaching for.