Clear the backlog: 10 Codex prompts for your own files

duplicate-finder

find the duplicate customers and rows hiding in your own data

How to use it

Codex only. Nothing else to install. Save the prompt as a file and run it, or paste it straight into Codex.

codex exec -C "your folder" - < the-prompt.md

It does the whole job in one pass on your own machine and writes the result into the folder you pointed it at.

The prompt

---
name: duplicate-finder
description: Finds records that are the same customer or the same product written differently, groups them, and proposes one record to keep per group. Nothing is merged and nothing is deleted until you approve.
---

# Find the duplicate customers and rows hiding in your own data

In: one export of your customers, suppliers or products as a CSV or spreadsheet. Out: a review file listing every group of rows that look like the same thing, with one proposed survivor per group, and not a single row changed.

Run it from the folder holding the export:

```
codex exec -C "the folder" - < the-prompt.md
```

## What it does

1. **Copy the export before opening anything for writing:** make a folder called `duplicate-review` and work only inside it. The original file is never re-saved, never gains a column, and never has a row removed. Record its size in bytes and its modification date at the start, because check 2 compares against those two numbers at the end.

2. **Do one kind of record per run and name the identifying columns:** customers in one run, products in another, never both, because the fields that prove two customers are the same prove nothing about two products. Pick the name column plus whichever of email, phone, postcode, address line 1, VAT number and account number actually exist. Columns that describe behaviour rather than identity, such as last order date or total spent, are not compared but are kept, because step 8 uses them to choose which row survives.

3. **Reduce every row to a fingerprint before comparing anything:** apply OpenRefine's fingerprint recipe in its documented order: remove leading and trailing whitespace, change all characters to lower case, remove all punctuation and control characters, normalise extended western characters to their ASCII form, split into whitespace-separated tokens, sort the tokens and remove duplicates, then join them back. Sorting the tokens is the part that matters most in practice: it puts "Smith John" and "John Smith" on the same key. Then strip trailing company forms, Ltd, Limited, PLC, LLP, Co and Sons, reduce phone numbers to digits only, and lower case emails while keeping the domain as a separate value.

4. **Block before you compare, because comparing every pair is not possible:** the Python Record Linkage Toolkit's own warning about a full index is that "the number of comparisons scales quadratic", so a 50,000 row customer list is over a billion pairs. Blocking "returns all record pairs that agree on the given variable(s)". Use at least three blocking keys so a row with one field missing still gets a chance to meet its twin: the first 6 characters of the fingerprint (6 is OpenRefine's default blocking size), the postcode outward code, the email domain, and the last 7 digits of the phone number. Report how many pairs blocking produced against how many a full comparison would have needed.

5. **Use the right comparison for each field, not one setting for all of them:** Jaro-Winkler for personal and company names, because it gives extra weight to matching prefixes, up to a maximum of four characters, with a prefix scale factor of 0.1, and names are typed most reliably at the start. Set the name threshold at 0.8, which is the figure Splink's own guidance recommends, and which it justifies by pointing out that dropping to 0.7 starts scoring plainly different names as though they matched. Use Levenshtein with a distance of 2 for addresses and product descriptions, which covers ordinary miskeys, and Damerau-Levenshtein where two adjacent characters have been swapped. Email, phone digits, VAT number and account number are compared exactly and never fuzzily: a one-character difference in an email address is a different mailbox.

6. **Sort into three buckets, using two thresholds, never one:** at or above 0.8 on the name plus one other identifying field agreeing exactly goes to PROPOSE MERGING. A high name score with nothing else supporting it, or a score in the band just below the threshold, goes to NEEDS YOUR EYES. Everything else is left alone. The middle band exists because the government's own data linkage guidance finds human review is "more useful for estimating precision (by identifying false links) than for estimating recall", so a person is worth spending on the doubtful pairs and wasted on the obvious ones. Print both thresholds at the top of the report and treat them as settings the owner can change and re-run.

7. **Group the surviving pairs into clusters and distrust long chains:** if A matches B and B matches C, they become one group of three even though A and C were never compared. Flag any group that only holds together through a chain of 3 or more hops, and any group with more than 5 members, into NEEDS YOUR EYES. One loose match on a common surname is how eleven unrelated customers end up in a single pile.

8. **Propose one survivor per group and show where every value came from:** rank the rows by how many identifying fields are filled in, then by most recent activity, then by lowest original row number as the tie break. Then build the proposed record field by field, taking the best available value for each field and naming the original row it came from, so the survivor can keep the good phone number from one row and the good address from another. Never silently prefer the newest row: a record created last week by a customer typing their own name often has less in it than the one your bookkeeper built over three years.

9. **Write two files and stop:** `duplicate-review.csv`, one line per original row carrying its original row number, its group number, whether it is the proposed survivor, the score that put it in the group and the bucket it landed in; and `duplicate-review.md`, a plain summary saying how many rows were read, how many groups were found, how many rows those groups cover, both thresholds, and the ten largest groups written out in full so they can be read without opening the CSV. Merge nothing. Delete nothing. Edit nothing.

## Then it checks

1. Every row of the original export appears exactly once in `duplicate-review.csv`. A row appearing twice, or not at all, stops the run.
2. The original export is byte for byte as it was: same size and same modification date as recorded in step 1.
3. No group in PROPOSE MERGING contains two rows with different non-empty email addresses, different non-empty phone numbers, or different non-empty VAT numbers. Any that does moves to NEEDS YOUR EYES.
4. Run the fingerprint step twice over ten random rows and confirm it produces the same key both times, so the grouping is repeatable rather than depending on the order rows were read.
5. Every value in every proposed survivor names the original row number it came from, and that row is a member of that group.
6. The two thresholds printed in the report are the numbers the run actually used, read back from the code that scored the pairs rather than retyped into the report.

Any check fails: name it, redo that step once. Failed twice: say what is wrong and stop.

## Rules
- Public information only.
- Never invent a fact, a number or a quote.
- Never merge, never delete, never overwrite and never edit the original export. The output of this skill is a proposal for a person to accept or reject, and a proposal is not permission to act on it.
- Two records that share only a name are not a duplicate. A second field must agree before anything is proposed for merging.
- Customer and supplier records are personal data. UK GDPR Article 5(1)(d) requires personal data to be "accurate and, where necessary, kept up to date", and Article 5(2) requires the controller to "be responsible for, and be able to demonstrate compliance with" that. Keep the review file as the written record of what was proposed and why. This prepares the work for a person to check and is not data protection advice.
- Everything happens on the owner's own machine against their own files. The data is never sent to a website, an API or a third party for matching.

## Built from
- Splink, choosing comparators guidance, https://moj-analytical-services.github.io/splink/topic_guides/comparisons/choosing_comparators.html, repository moj-analytical-services/splink, 2,388 stars read from api.github.com on 7 September 2026: the Jaro-Winkler name threshold of 0.8 in step 5 and the reason it is not set lower, plus the Levenshtein distance of 2 for typos.
- Splink, comparators reference, https://moj-analytical-services.github.io/splink/topic_guides/comparisons/comparators.html, same repository and star count: Jaro-Winkler weights matching prefixes up to a maximum of four characters with a prefix scale factor of 0.1, Levenshtein is for character miskeys, Damerau-Levenshtein adds transpositions. That split is what step 5 applies field by field.
- OpenRefine, clustering in depth, https://openrefine.org/docs/technical-reference/clustering-in-depth, repository OpenRefine/OpenRefine, 11,986 stars read from api.github.com on 7 September 2026: the seven-step fingerprint recipe used verbatim in step 3, and the blocking size that "defaults to 6 chars in OpenRefine", which is the 6 characters in step 4.
- Python Record Linkage Toolkit, indexing reference, https://recordlinkage.readthedocs.io/en/latest/ref-index.html, repository J535D165/recordlinkage, 1,062 stars read from api.github.com on 7 September 2026: the warning that a full index means "the number of comparisons scales quadratic", and that blocking "returns all record pairs that agree on the given variable(s)". That is the whole argument for step 4.
- Quality assessment in data linkage, Office for National Statistics and the Government Analysis Function, published 25 August 2020, https://www.gov.uk/government/publications/joined-up-data-in-government-the-future-of-data-linking-methods/quality-assessment-in-data-linkage: the three-bucket shape in step 6, from its finding that clerical review is "more useful for estimating precision (by identifying false links) than for estimating recall", and its requirement to report link-level match quality rather than a bare yes or no.

Built from the best public work on this

Sources for duplicate-finder

Every source below was opened and read on 7 September 2026. Every star count was read from

`https://api.github.com/repos/<owner>/<repo>` on that date and is printed exactly as returned.

Splink, choosing comparators

https://moj-analytical-services.github.io/splink/topic_guides/comparisons/choosing_comparators.html

Repository: moj-analytical-services/splink, 2,388 stars.

Splink is the record linkage library built and published by the UK Ministry of Justice's analytical

services team, and it is used inside government to link administrative datasets at national scale.

This page is its practical guide to picking a string comparison and a cut-off for it. Read for one

thing: whether anyone credible publishes an actual number rather than "tune it to your data".

It does. The page states that for names "a good choice of metric could be Jaro-Winkler with a

threshold of 0.8", and it explains the number rather than asserting it: lowering the threshold to

0.7 begins to score obviously different names as strong evidence of a match. It also shows a

Levenshtein level with a distance of 2 for typos.

Decision it produced: the 0.8 name threshold in step 5 of the skill, the Levenshtein distance of 2

for addresses and product descriptions in the same step, and the instruction in step 6 to print both

thresholds in the report as settings the owner can change and re-run rather than as fixed truths.

Splink, comparators reference

https://moj-analytical-services.github.io/splink/topic_guides/comparisons/comparators.html

Repository: moj-analytical-services/splink, 2,388 stars.

The reference page for each string comparator. Read to work out which comparison belongs to which

kind of field, since the common mistake is applying one similarity setting to every column.

It says Jaro-Winkler is "particularly useful for names" because it gives extra weight to matching

prefixes, applied to a maximum of four characters with a standard prefix scale factor of 0.1. It

labels Levenshtein as useful for "data entry errors e.g. character miskeys" and Damerau-Levenshtein

for "character transpositions and miskeys". Notably it publishes no thresholds itself and sends the

reader to the page above for them, which is why both pages are cited rather than one.

Decision it produced: the field-by-field split in step 5, Jaro-Winkler for names, Levenshtein for

addresses and descriptions, Damerau-Levenshtein where two characters have been swapped, and the

statement in the skill that exact fields such as email and VAT number are never compared fuzzily.

OpenRefine, clustering in depth

https://openrefine.org/docs/technical-reference/clustering-in-depth

Repository: OpenRefine/OpenRefine, 11,986 stars.

OpenRefine is the long-standing open source tool for cleaning messy tabular data, and this page is

its technical reference for how its clustering actually works. Read for a normalisation recipe that

does not need a machine learning model behind it, because this skill has to run in one pass on an

owner's own machine.

It gives the fingerprint key collision method as an ordered list: remove leading and trailing

whitespace, change all characters to lower case, remove all punctuation and control characters,

normalise extended western characters to their ASCII representation, split into whitespace-separated

tokens, sort the tokens and remove duplicates, join the tokens back together. It also describes

blocking as a hybrid between key collision and nearest neighbour, working on blocks "in which all

strings share a substring of a given 'blocking size' (which defaults to 6 chars in OpenRefine)". It

warns separately that the n-gram variant produces many false positives, which is why this skill does

not use it.

Decision it produced: the whole of step 3, in that documented order, and the 6 character blocking key

in step 4.

Python Record Linkage Toolkit, indexing reference

https://recordlinkage.readthedocs.io/en/latest/ref-index.html

Repository: J535D165/recordlinkage, 1,062 stars.

The documentation for the indexing step of a widely used Python record linkage toolkit. Read to

confirm the size of the problem before writing a step that tells an owner not to compare everything.

It confirms that the full index generates "all possible combinations of record pairs", the upper

triangular matrix when deduplicating a single file, and warns that "the number of comparisons scales

quadratic". It describes blocking as returning "all record pairs that agree on the given variable(s)"

and as "an effective way to make a subset of the record space", and offers sorted neighbourhood as

the middle ground "when there is relatively large amount of spelling mistakes".

Decision it produced: step 4 in full, including the instruction to use several blocking keys rather

than one, and the requirement to report how many pairs blocking produced against how many a full

comparison would have needed, so the owner can see what was skipped.

Quality assessment in data linkage

Office for National Statistics and the Government Analysis Function, published 25 August 2020

https://www.gov.uk/government/publications/joined-up-data-in-government-the-future-of-data-linking-methods/quality-assessment-in-data-linkage

A peer-reviewed article in the ONS collection on the future of data linking methods. Read because

this is the question the skill actually has to answer: what do you do with the pairs the machine is

unsure about.

It separates false matches from missed matches, and finds that clerical review is "more useful for

estimating precision (by identifying false links) than for estimating recall". It notes that where

matching data are insufficient "neither human nor algorithm will be able to accurately classify match

status", which is why the skill refuses to propose a merge on a name alone. It sets out minimum

outputs for a linkage job, including "link-level information about match quality (e.g. pattern of

agreement, match rank, match rule, match weight or probability)" rather than a bare yes or no.

Decision it produced: the three buckets in step 6 instead of a single cut-off, sending the doubtful

band to a person rather than guessing; the rule that a name match with no supporting field never

reaches PROPOSE MERGING; and the requirement in step 9 that the review file carries the score and the

bucket for every row, not just the verdict.

Also read, and what was taken from it

dedupeio/dedupe, https://github.com/dedupeio/dedupe, 4,510 stars read from api.github.com on

7 September 2026. Its README describes a library that "uses machine learning to perform fuzzy

matching, deduplication and entity resolution quickly on structured data" and that "takes in human

training data and comes up with the best rules for your dataset". It is the best known tool in this

space, and the reason it is not a source for a step is that its accuracy depends on a human labelling

session before it can run, which does not fit a job that has to finish in one pass on an owner's own

files. What was taken is the principle rather than the mechanism: a person confirms, the software

proposes.

Best public prompt we found for this job

Splink's choosing comparators guide, https://moj-analytical-services.github.io/splink/topic_guides/comparisons/choosing_comparators.html.

It is the only source read here that publishes a defensible number and then argues for it, which is

exactly what a small business owner needs when a tool asks them how similar is similar enough.

The line worth copying verbatim into any deduplication prompt:

"a good choice of metric could be Jaro-Winkler with a threshold of 0.8"

Want this running in your business?

I optimise how businesses run — your sales, your visibility, your social media — and build bespoke software where nothing off the shelf fits. The first conversation is free. Work starts from £150 a day.