unit U5 — 5 of 5
Canonical forms
one spelling per concept, normalised on the way in
The same concept arrives spelled five ways. A phone number comes as `+44 7700 900123`, `07700900123`, and `(0)7700 900-123`. A country is `GB`, `UK`, `United Kingdom` or `Great Britain`. An email arrives with capitals and a trailing space. If you store these as they arrive, deduplication fails, lookups miss, and the client ends up with four records for one person.
Normalisation is deciding the one canonical form and converting everything to it on the way in. Lowercase and trim emails. Store phone numbers in a single international format. Map country variants through an explicit lookup to ISO codes — an explicit table, not a clever guess, because guessing is how `GE` becomes Germany instead of Georgia. Do it once, at the boundary, right after validation, so every step downstream compares like with like.
Where it breaks: normalising at comparison time instead of at write time. A dedup check that lowercases both sides works, but the stored records stay inconsistent, so the next query written by someone else misses them again. Normalise once on the way in and the property holds for every consumer forever.
worked example
Canonical forms applied at the boundary, so every downstream step compares like with like.
const COUNTRY = {
gb: 'GB', uk: 'GB', 'united kingdom': 'GB', 'great britain': 'GB',
de: 'DE', germany: 'DE',
ge: 'GE', georgia: 'GE', // explicit, because guessing gets this one wrong
};
export const code = async (inputs) => {
const r = inputs.record;
const country = COUNTRY[String(r?.country ?? '').trim().toLowerCase()] ?? null;
return {
// one canonical form each, decided here and nowhere else
email: String(r?.email ?? '').trim().toLowerCase(),
phone: String(r?.phone ?? '').replace(/[^0-9+]/g, ''),
country,
// keep what arrived, so a bad mapping is diagnosable later
rawCountry: r?.country ?? null,
};
};field checklist
- Pick one canonical form per field and convert on the way in.
- Lowercase and trim every email before storing or comparing it.
- Map country and currency variants through an explicit lookup table.
- Keep the raw value alongside the normalised one for diagnosis.
- Normalise at write time, not at comparison time.
common failure — Four records for one person
Leads arrived from three sources with emails in mixed case and trailing spaces. Dedup compared the stored strings directly, so `[email protected] `, `[email protected]` and `[email protected]` became three contacts, and a fourth appeared when a phone-only record could not be matched at all. Normalise to one canonical form at the boundary — lowercase, trimmed, single phone format — and the dedup property holds for every consumer without anyone remembering to lowercase at query time.
check your understanding
Your dedup query lowercases both sides at comparison time and works correctly. Why is normalising at write time still the better fix?
sandbox validation
The check above confirms you followed the unit. Marking the module COMPLETED takes more: build the automation in your own engine and submit the exported flow and its run evidence, signed, to your unique validation URL. See the module page for that spec.