RO Processor
Transaction Register · Peru FIU / SBS
The problem
A currency exchange supervised by the Peruvian banking regulator must report every foreign-exchange operation of USD 5,000 or more each month, in an official 26-column format. The information lived in three places that did not talk to each other: a monthly PDF of invoices — 48 to 81 pages, one per page, in irregular format — the tax authority portal, where the legal representative details live because the invoice does not carry them, and an Excel template with rows already typed by hand that could be neither lost nor duplicated. The process was to open page by page, decide whether it clears the threshold, transcribe ten fields, go look up the legal representative, copy five more fields, fill in eleven fixed codes and, at the end, reorder by date and renumber the sequence.
Architecture
Monthly PDF (48–81 pages)
└─ pdfplumber ─────► text extraction, page by page
└─ valid invoice? ──no──► skipped, reason logged
└─ tolerant parser ──► 41 structural shapes
└─ ≥ USD 5,000? ──no──► discarded, amount logged
└─ InvoiceRecord
└─ headless lookup ──► legal representative
└─ idempotent merge + backfill
└─ sort by date + renumber
└─ FIU Excel · 26 columnsDecisions
The invoice format does not exist
The line describing the operation is typed by hand by whoever issues the invoice. Across the 386 pages there were 365 distinct lines that reduce to 41 structural shapes. There is no format — there is a distribution of improvisations. The parser covers all of them without guessing, including a typo by the issuer and an invoice that never says whether it is a purchase or a sale.
95 × PURCHASE OF N $ FX N N dominant shape
85 × PURCHASE $N AT FX N N
59 × PURCHASE OF N $ AT THE FX N N
27 × SALE OF N $ FX N N
4 × PURCHASE $N AT FX N DATED N SEPTEMBER N
2 × PURCHASE OF N $ FX N WITH DATE N/N/N N
1 × PURHCASE OF N $ FX N N issuer typo
1 × PURCHASE OF N EUROS AT FX N N another currency
1 × SALE $N ATFX N N missing space
1 × $N AT FX N N no PURCHASE or SALE
…and 25 more shapes appearing once eachExplicit degradation, never a silent guess
If the line says neither purchase nor sale, the parser assumes purchase and raises a named warning for human review. It prefers a flagged row to an invented one. Same with operations below the threshold: they are discarded, but they stay in the log with their amount. In an auditable report, a value that disappears without a trace is worse than a wrong value that is flagged.
[202510] OK E001-4628 | 13/10/2025 | PURCHASE USD 20,000.00 | TRANSPORTES ANDINA EXPRESS S.A.C. [202510] SKIPPED E001-4629 | USD 500.00 < 5,000 [202510] OK E001-4639 | 02/10/2025 | SALE USD 25,000.00 | COMERCIALIZADORA TEXTIL GAMARRA S.A.C. [202510] WARN E001-4642: purchase/sale not stated explicitly [202510] — assumed PURCHASE. Verify. [202510] WARN SKIPPED page 4: receipt number not found (pattern E###-####) [202510] Total invoices extracted: 18 [202510] Looking up legal representatives… [202510] Lookup [1/16]: tax ID 20902887752 [202510] Total rows in file: 23 (18 invoices + existing entries)
The key data is not in the document
Five of the 26 columns — the legal representative document type and number, surnames and given names — appear on no invoice: they exist only in the tax authority portal. They have to be fetched company by company. The lookup is cached and deduplicated, with a single browser session for the whole batch and a polite pause between queries: 148 rows resolved with 33 lookups.
Idempotence over previous human work
The template arrives with rows typed by hand that can be neither lost nor duplicated. Deduplication is by normalized receipt number, so E001-4801 and 4801-E001 are the same one: a month can be reprocessed as many times as needed without duplicating a single row. And if the template already had rows with a tax ID but no legal representative, the processor completes those too. It does not only add what is new: it repairs what was half done.
October template 5 rows (one incomplete) + 18 new invoices + backfill of the incomplete row + sort by date and renumber ───────────────────────────────── output 23 rows
The person who uses it does not open a terminal
The operator who files the report is not a developer. The pipeline lives behind a Tkinter desktop application: pick the folders, press a button, and the log appears in the same window with every page, every warning and every lookup in plain view. Without that layer, the automation would have gone right back to depending on someone from IT every month.
┌────────────────────────────────────────────────────────┐ │ Transaction Register Processor │ │ Currency exchange · Peru FIU / SBS format │ ├────────────────────────────────────────────────────────┤ │ PDF folder [ …/input ] [ Browse ] │ │ Excel template [ …/template ] [ Browse ] │ │ Output folder [ …/output ] [ Browse ] │ │ File name [ RO_202510.xlsx ] │ │ │ │ [ PROCESS ] │ ├────────────────────────────────────────────────────────┤ │ Processing log │ │ every page, warning and lookup is recorded │ │ │ │ [202510] OK E001-4628 | PURCHASE USD 20,000.00 │ │ [202510] SKIPPED E001-4629 | USD 500.00 < 5,000 │ │ [202510] WARN E001-4642: assumed PURCHASE. Verify. │ │ │ │ [ Open generated file ] [ Clear log ] Ready. │ └────────────────────────────────────────────────────────┘
The result, with its caveat
The volumes in this case are measured on the project files. The manual times are not: they are estimates from the person who used to do it, not stopwatch readings. I say so because the difference between measured and estimated is exactly the kind of distinction this project exists to respect.
manual automated
triage of 386 pages ≈ 1.5 h automatic
transcription of 148 × 26 fields ≈ 7 h automatic
33 legal-representative lookups ≈ 1 h ≈ 3–4 min
dedupe, sort by date, renumber ≈ 2.5 h automatic
────────────────────────────────────────────────────────────────
total ≈ 10–12 h ≈ 5 min
(1.5 work days) + 15 min reviewThe operation decides which way the money flows
In a purchase the foreign currency comes in and local currency goes out; in a sale it is the other way round. Inverting it produces a mirrored report — perfectly presentable and completely false. The from/to mapping is derived from the operation type, not from where the amounts sit on the invoice.
The product
The output
The output, for that same receipt
| Reg. no. | Date | Region | Province | District | Doc. type | Doc. no. | Surname 1 | Surname 2 | Given names | Country | Occupation | On behalf of | Tax ID | Surname 1 or company name | Surname 2 | Given names | Occupation | Fund type | Op. type | Source | Currency | Amount | Currency | Amount | Receipt no. |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 01/10/2025 | 15 | 01 | 22 | 001 | 43•••••• | BENITES | OCHOA | GABRIELA | PE | 004 | 003 | 20901996640 | CONSULTORA FINANCIERA ANDES S.A.C. | — | — | — | 001 | 003 | 099 | USD | 6000.00 | PEN | 20388.00 | B001-1242 |
| 2 | 01/10/2025 | 15 | 01 | 22 | 001 | 43•••••• | BENITES | OCHOA | GABRIELA | PE | 004 | 003 | 20901996640 | CONSULTORA FINANCIERA ANDES S.A.C. | — | — | — | 001 | 003 | 099 | USD | 21000.00 | PEN | 79569.00 | B001-1245 |
| 3 | 01/10/2025 | 15 | 01 | 22 | 001 | 44•••••• | HERRERA | VALDEZ | MONICA PATRICIA | PE | 004 | 003 | 20902114428 | COMERCIALIZADORA TEXTIL GAMARRA S.A.C. | — | — | — | 001 | 003 | 099 | USD | 6200.00 | PEN | 22605.20 | E001-4627 |
| 4 | 01/10/2025 | 15 | 01 | 22 | 001 | 07•••••• | PAREDES | CORDOVA | LUIS ALBERTO | PE | 004 | 003 | 20901224317 | AGENCIA DE ADUANAS MERIDIANO S.A. | — | — | — | 001 | 003 | 099 | USD | 5000.00 | PEN | 17100.00 | E001-4635 |
| 5 | 02/10/2025 | 15 | 01 | 22 | 001 | 44•••••• | HERRERA | VALDEZ | MONICA PATRICIA | PE | 004 | 003 | 20902114428 | COMERCIALIZADORA TEXTIL GAMARRA S.A.C. | — | — | — | 001 | 003 | 099 | PEN | 93450.00 | USD | 25000.00 | E001-4639 |
| 6 | 03/10/2025 | 15 | 01 | 22 | 001 | 46•••••• | SALAZAR | DUENAS | VERONICA | PE | 004 | 003 | 20903114428 | REPRESENTACIONES COMERCIALES ORIENTE S.A.C. | — | — | — | 001 | 003 | 099 | USD | 18000.00 | PEN | 62478.00 | E001-4613 |
| 7 | 04/10/2025 | 15 | 01 | 22 | 001 | 06•••••• | FLORES | AGUILAR | CESAR AUGUSTO | PE | 004 | 003 | 20900884751 | DISTRIBUIDORA COMERCIAL LOS OLIVOS S.A.C. | — | — | — | 001 | 003 | 099 | USD | 8500.00 | PEN | 30328.00 | E001-4602 |
| 8 | 05/10/2025 | 15 | 01 | 22 | 001 | 10•••••• | TAPIA | BUSTAMANTE | OSCAR RAUL | PE | 004 | 003 | 20902887752 | EXPORTACIONES CAFETALERAS CHANCHAMAYO E.I.R.L. | — | — | — | 001 | 003 | 099 | PEN | 33354.00 | USD | 9000.00 | E001-4596 |
8 of the 23 October rows. The five highlighted columns do not exist in the invoice: they came from a tax-authority lookup. Synthetic data — the tax IDs carry a deliberately invalid check digit and the identity documents are masked, as a compliance demo should.
Figures
- 386
- PDF pages processedBRIEF â measured on the project files
- 41
- structural shapes of the operation lineBRIEF â from 365 unique lines
- 148
- rows generated across 6 monthsBRIEF â 28/34/24/23/22/17
- 33
- lookups needed for 148 rowsBRIEF â deduplicated and cached
- 87
- invoices above the USD 5,000 thresholdBRIEF â measured
- ~1,500
- lines of codeBRIEF â measured
What it demonstrates
| Skill | Evidence |
|---|---|
| Real-world dirty data | 41 structural shapes covered without guessing, including an issuer typo and invoices with no explicit purchase/sale |
| Judgement in an auditable domain | Nothing is dropped in silence: every omission and every assumption is logged with its reason |
| Idempotence | Deduplication by normalized receipt number, plus backfill of incomplete hand-typed rows |
| Integrating what you cannot request | Five columns that exist only in the tax authority portal, resolved with cached scraping: 148 rows on 33 lookups |
| Encoded business rules | Threshold, from/to mapping by operation type and eleven fixed codes applied reproducibly |
Known technical debt
A case study that only lists wins does not survive a hard question.
- The scraper depends on the tax authority portal DOM: a redesign breaks it. It fails with a warning, without corrupting the output.
- The fixed codes are hard-wired for this company â region, occupation, fund type. For another exchange they would need to be parameterised.
- Invoices with no explicit purchase or sale are assumed to be purchases and flagged for manual validation.