Company records: 275,832 moved, not one overwritten
A Belgian staffing agency had left its legacy applicant-tracking system for Odoo and lost part of its company history on the way. Sensirel extracted all 275,832 company records from the old system, matched 94.6% of them to the Odoo base automatically, and filled only the fields Odoo was missing, without touching a single value the team had already corrected by hand.
275,832
Companies extracted
Zero failures on our side
94.6%
Matched automatically
VAT, then email, then name and city
0
Values overwritten
Only empty fields were ever filled
4 GB
Documents moved
Against an initial 284 GB projection
Executive summary
A Belgian staffing agency had moved from its legacy applicant-tracking system to Odoo and lost part of its company history on the way: contacts, notes, documents. We extracted all 275,832 company records from the old system, matched them to the Odoo base, and filled only what Odoo was missing, without touching a value the team had already corrected.
The challenge
The old system held 275,832 companies, of which 98.8% were unflagged leads. Nobody knew how many of them existed in Odoo, under which name, or with which VAT number. The one thing everybody knew was that the previous migration had lost the follow-up history, and that the sales team was working half blind.
The source was not clean either. 14,014 VAT numbers appeared on more than one record, one of them on 58, and thousands of names were placeholders like a dot or an asterisk. Copying that into Odoo would have reintroduced the duplicates the team had spent weeks removing by hand.
Odoo had its own scars. A previous import had cut the last two digits off a batch of postcodes, and a botched country-name replacement had inserted the word Belgique into the middle of street names. Any automatic overwrite would have spread those errors or erased the manual fixes.
What we built
A full extraction that survives the source
Twelve read endpoints per company plus the web session for the follow-up feed and the documents, run on five threads. The old system answers a 404 with a sentence when a company has no schedule; the extractor treats that as empty instead of an error. 275,832 companies came out in 30.6 hours with zero failures on our side.
Four-tier matching
VAT number first, normalised and country-prefix insensitive, then email, then name plus city, then name alone. On a 1,000-company sample: 84.9% by VAT, 2.3% by email, 6.9% by name and city, 0.5% by name. 94.6% matched. The 5.4% left over were not created, on the client's instruction, because their team had deduplicated Odoo by hand and did not want the old duplicates back.
A field-by-field comparison before any write
Every matched company got a side-by-side report: equal, format-only difference, conflict, Odoo-only, or fillable. The report surfaced the 231 truncated postcodes and 17 corrupted street names, and showed that 739 phone conflicts were 99% formatting. Only the fillable fields, empty in Odoo and present in the source, were ever written.
History and documents, without erasing anything
Follow-up notes were posted as new messages on the matched record, each tagged with its origin and original ID, and the existing Odoo notes were never touched. Documents were attached unless one with the same filename already existed on that company.
The stack
Python does the extraction and the matching, Odoo's XML-RPC API does the writes, and every writer script defaults to a dry run that prints what it would change. Claude Code wrote and ran the tooling under review.
Results
275,832 companies extracted with 0 failures. 4,197 documents, about 4 GB, against an initial projection of 284 GB: most of the leads had no documents at all, which the sample run revealed before anyone bought storage for it.
94.6% of companies matched to their Odoo record automatically. In the validated sample, 909 of 1,000 companies had a tax-exemption rate the source knew and Odoo did not, which is the kind of gap the fill-only policy was designed for.
Not one Odoo value was overwritten. The 231 broken postcodes and 17 corrupted addresses were reported to the team with the correct value from the source, rather than silently fixed.
What we learned
- Run the whole pipeline on 1,000 records before you run it on 275,000. The sample changed the storage estimate by a factor of seventy and killed two endpoints that returned nothing.
- A format difference is not a conflict. Treating a phone number written two ways as a disagreement would have generated 739 false alarms.
- The source's duplicates were mostly empty placeholders. Measured on the top 50 duplicate groups, they would have added 236 notes and 6 documents, so merging them onto one record was safe.
- When the client says do not create, do not create. Their team had cleaned the base by hand; the migration's job was to fill gaps, not to reopen them.
- Dry run by default. Every script prints its plan and refuses to write without an explicit flag, so a mistake shows up in a report instead of in the CRM.
How it works
Extract everything
Every endpoint, every document, with the source's quirks handled.
Match in four tiers
VAT, then email, then name and city, then name.
Compare field by field
Report equal, format-only, conflict and fillable before writing.
Fill only what is empty
Post the history, attach the documents, overwrite nothing.
See what your team
can hand to AI
A free 30-minute call. We map one workflow together and you leave with a plan you can use, with or without us.
Book a Free Strategy Call