rolodexter · v2.10.0 · python + typescript
Every export names the same field differently. Map them all to one schema.
Point RoloDexter at a CRM payload, a form post, or a CSV nobody has looked at since 2019, and get back clean canonical fields with a confidence score on every match.
from rolodexter import ContactMapper
mapper = ContactMapper()
result = mapper.map_payload({
"fname": "jane",
"surname": "doe",
"mobile": "+1-650-253-0000",
"employer": "Tech Corp",
"Column 1": "jane.doe@example.com",
})
# result.normalized
{
"first_name": "Jane",
"last_name": "Doe",
"phone": "+16502530000",
"company": "Tech Corp",
"email": "jane.doe@example.com"
}
// the problem
Nobody agrees on what to call a phone number.
Every CRM, email platform, and CSV export invents its own names for the same five pieces of information. Writing the mapping by hand works right up until the next integration.
| Service | First name | Phone | Company |
|---|---|---|---|
| HubSpot | firstname | mobilephone | company |
| Salesforce | FirstName | MobilePhone | Company |
| Mailchimp | FNAME | PHONE | COMPANY |
| Google CSV | Given Name | Phone 1 - Value | Organization 1 - Name |
| Random CSV | Column A | Column B | Column C |
| RoloDexter | first_name | phone | company |
Even Column A is recoverable. When a header carries no meaning at all, RoloDexter reads the value's shape instead, and an email address in an unnamed column still lands on email.
// four-layer matching
Four strategies, tried in order of certainty.
Every field walks the chain until something matches, and the strategy that caught it sets the confidence you get back.
Exact
An O(1) lookup against 600+ known aliases across 62 canonical fields. This is where most real payloads land.
confidence 1.0Normalized
Handles CamelCase, dot.path, spaces to underscores, and the rest of the casing zoo.
Fuzzy
Typos survive. phne_nmbr still resolves to phone, and you can see that it was a guess.
Heuristic
No usable header? It reads the data instead, detecting emails, phones, URLs, and postal codes by shape.
confidence 0.6Nothing is silent. Non-fatal issues come back as warnings rather than disappearing, and strict mode turns a low-confidence guess or an unparseable value into a loud failure instead of a quietly wrong row.
// value normalization
Matching the field is only half of it.
The value gets cleaned too, so what comes out the other side is ready to store rather than ready for another round of regexes.
- Phones to E.164 via libphonenumber, and phone numbers buried inside a free-text field can be extracted on their own.
-
Names title-cased with particle awareness, so
jane van der bergbecomesJane van der Berg, notJane Van Der Berg. - Emails lowercased and trimmed. Addresses collapsed and title-cased. Tags coerced to a real list.
-
Your own names too. Per-caller overrides map a vendor's
MMERGE6orcf_lead_scoreonto a canonical field without patching the alias table.
mapper.identify("fname")
FieldMatch(canonical='first_name',
confidence=1.0, strategy='exact')
mapper.identify("phne")
FieldMatch(canonical='phone',
confidence=0.85, strategy='fuzzy')
mapper.identify("Column X",
value="jane@test.com")
FieldMatch(canonical='email',
confidence=0.6, strategy='heuristic')
// at scale
One contact, or the whole export.
The same mapper handles a single webhook payload and a multi-gigabyte CSV, from a script, a notebook, or a terminal.
Batch and streaming
Map a list in one call, or stream a huge CSV or JSONL export in constant memory. There is a preview mode that reports import readiness without retaining a single mapped row.
DataFrames
Hand it a DataFrame and get the columns renamed to canonical fields with values normalized. Columns it does not recognise are kept, not dropped.
Command line
Map a CSV, JSON, or JSONL export straight from a shell, quarantine bad rows to a JSONL file instead of stopping, and ask it to explain exactly how one header resolved.
Compile once
Resolve a source's headers into a mapping profile a single time, then reuse it for every row that follows.
40 languages
Alias tables for 40 languages generate on demand and cache locally, with bounded network behaviour while they build.
Python and TypeScript
The Python package is the canonical implementation and owns the shared alias table. The npm package syncs that same table at build time, so both ecosystems agree.
// the schema
62 canonical fields, and one honest unknown.
Exposed as a string enum, so it drops straight into JSON without a conversion step.
// pick your ecosystem
Stop writing the same field map again.
Install it, hand it the payload nobody wants to look at, and read what comes back.