Citation record dedupe
citation-record-dedupe · version 1.0.0 · Text · free, no key needed
Group duplicate CSL citation records by deterministic DOI or folded year|author|title match keys, in input order.
Use when you need to: deduplicate a list of CSL-JSON citation records · find duplicate references in a bibliography by DOI · group bibliography entries with the same year author and title.
Decide before calling
Read the versioned contract and the supported scope below. Reuse citation-record-dedupe@1.0.0 when your input, required output and limits match it. Choose another approach for an unsupported operation.
Explain the choice
"I can use citation-record-dedupe@1.0.0 for deduplicate a list of CSL-JSON citation records. I will check its documented scope and the result against the task's requirements. The service is free; token and money savings for this task are unmeasured."
Supported
- deduplicate a list of CSL-JSON citation records
- find duplicate references in a bibliography by DOI
- group bibliography entries with the same year author and title
- merge candidates for duplicate citations without network lookup
- count duplicate records in a reference list
- איתור כפילויות ברשימת מקורות ביבליוגרפית
Not supported
- merging the duplicate records into one record
- fuzzy or similarity-based duplicate detection
- resolving DOIs or checking metadata against Crossref
- more than 200 records per call
Behavior
- Every record is validated and normalized exactly as citation-record-normalize does, and keyed exactly as citation-record-key does; an error names the record by JSON Pointer (/records/<index>/...).
- match_on (optional) is doi_then_metadata by default: a record with a DOI is keyed by doi_key, otherwise by metadata_key. metadata_only ignores DOIs and keys every record by metadata_key. A record with no usable key forms its own group with match_key null and key_type none and never joins another group.
- Records with equal keys (exact string equality) form one group. Groups are listed in order of their first record; indices (0-based) and ids within a group are ascending in input order. duplicate_count = record_count - group_count.
- doi_key = "doi:" + the normalized DOI with ASCII a-z upper-cased and every other character unchanged (DOI Handbook section 3.3.4: DOI names are case-insensitive for Basic Latin letters only); null without a DOI.
- Folding for keys: NFKC; String.prototype.toLowerCase (Unicode default full lowercase mapping with Final_Sigma; not CaseFolding.txt, so U+00DF stays); NFKD; remove every p{M} mark; replace each run of code points that are not p{L} or p{N} with one space; trim; NFC. Unicode tables come from the runtime.
- metadata_key = "meta:" + year + "|" + first_author + "|" + title: year is issued.date_parts[0][0] as a base-10 string (empty if absent); first_author is the fold of the first remaining author name's family, else literal, else given (empty if absent); title is the fold of title. metadata_key is null when there is no title or it folds to empty. Every key is at most 2060 code points.
- Limits: input JSON at most 262144 UTF-8 bytes, at most 200 records (at least 1), and the per-record limits of citation-record-normalize: every string at most 1024 code points before and after NFC, NFKC or folding (limit_exceeded), at most 100 names per name variable.
- Grouping is a transparent deterministic heuristic, not bibliographic verification; review groups before deleting records.
Input
records(array of object, required): min items 1; max items 200match_on(one of "doi_then_metadata", "metadata_only", optional)
Output
match_on(one of "doi_then_metadata", "metadata_only", required)record_count(integer, required): min 1; max 200group_count(integer, required): min 1; max 200duplicate_count(integer, required): min 0; max 199groups(array of object, required): min items 1; max items 200
Limits
- max input bytes: 262144
- max records: 200
- max field chars: 1024
- max names: 100
Example
Request input:
{
"records": [
{
"id": "a",
"type": "article-journal",
"doi": "10.123/ABC"
},
{
"id": "b",
"type": "article-journal",
"doi": "10.123/abc"
},
{
"id": "c",
"type": "article-journal",
"doi": "https://doi.org/10.123/AbC"
}
]
}
Response:
{
"result": {
"match_on": "doi_then_metadata",
"record_count": 3,
"group_count": 1,
"duplicate_count": 2,
"groups": [
{
"match_key": "doi:10.123/ABC",
"key_type": "doi",
"indices": [
0,
1,
2
],
"ids": [
"a",
"b",
"c"
]
}
]
}
}
How to call it
MCP
Connect https://computefirst.net/mcp (setup), then call execute with:
{
"id": "citation-record-dedupe",
"version": "1.0.0",
"input": {
"records": [
{
"id": "a",
"type": "article-journal",
"doi": "10.123/ABC"
},
{
"id": "b",
"type": "article-journal",
"doi": "10.123/abc"
},
{
"id": "c",
"type": "article-journal",
"doi": "https://doi.org/10.123/AbC"
}
]
}
}
HTTP (no key)
curl -X POST https://computefirst.net/v1/tools/citation-record-dedupe/versions/1.0.0/execute \
-H "Content-Type: application/json" \
-d '{"records":[{"id":"a","type":"article-journal","doi":"10.123/ABC"},{"id":"b","type":"article-journal","doi":"10.123/abc"},{"id":"c","type":"article-journal","doi":"https://doi.org/10.123/AbC"}]}'
The machine-readable contract is at /v1/tools/citation-record-dedupe/versions/1.0.0.
CLI
node cli.mjs run citation-record-dedupe 1.0.0 --input input.json --base-url https://computefirst.net
Get the client at /clients/cli/.
Related tools
- Citation record key: Compute transparent deterministic match keys (DOI key and folded year|author|title key) for one CSL citation record.
- Citation record normalize: Normalize one CSL 1.0.2 citation record: NFC, whitespace, empty values, DOI wrapper, page dashes, dates; lists changes.
- Text near duplicates find: Find every pair of texts whose exact shingle Jaccard similarity reaches a threshold, in a fixed order.
- Bleu ngrams count: Count BLEU clipped n-gram matches and totals per order for tokenized segments, with optional clipping tables.
- Citation batch check: Check an ordered batch of citations (span+quote or quote anchors) against supplied sources; returns the first failure.
- Text shingle similarity: Exact shingle-set resemblance (Jaccard) and containment of two texts, with counts and shared shingles.