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.

Free & MIT-licensed 600+ aliases 62 canonical fields 40 languages, on demand
import.py
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.

ServiceFirst namePhoneCompany
HubSpotfirstnamemobilephonecompany
SalesforceFirstNameMobilePhoneCompany
MailchimpFNAMEPHONECOMPANY
Google CSVGiven NamePhone 1 - ValueOrganization 1 - Name
Random CSVColumn AColumn BColumn C
RoloDexterfirst_namephonecompany

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.

1

Exact

An O(1) lookup against 600+ known aliases across 62 canonical fields. This is where most real payloads land.

confidence 1.0
2

Normalized

Handles CamelCase, dot.path, spaces to underscores, and the rest of the casing zoo.

high confidence
3

Fuzzy

Typos survive. phne_nmbr still resolves to phone, and you can see that it was a guess.

confidence 0.85
4

Heuristic

No usable header? It reads the data instead, detecting emails, phones, URLs, and postal codes by shape.

confidence 0.6

Nothing 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 berg becomes Jane van der Berg, not Jane 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 MMERGE6 or cf_lead_score onto a canonical field without patching the alias table.
why it chose that
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.

TS

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.

first_namelast_namefull_namemiddle_namenicknameprefixsuffixemailphonehome_phonework_phonefaxwhatsappwebsitecompanyjob_titledepartmentindustryaddress_line1address_line2citystatepostal_codecountryfull_addresslinkedintwitterfacebookinstagramgithubyoutubetiktokdiscordtelegramlead_statuslifecycle_stageemail_opt_outtagssourceutm_parametersscoreownerbirthdayagecreated_atupdated_atlast_contactedrevenuecurrencymessagesubjectcompany_sizenotesmetadatagendertimezonelanguage_preferencereferrer_urlsource_idsource_servicesubscribedverifiedunknown

// 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.