Commands¶
usage: cpplink <command> [options]
commands:
inspect load a parquet file and report cardinality and memory
profile what the columns can be worth, what a matching pair will
score, and which pairs of them are the same evidence twice
levels check the schema's fuzzy thresholds against the column they
run on, and propose better ones
simplify merge the levels a scored stream cannot tell apart
explain show the levels a single pair lands on, and with a model
the waterfall of bits behind its score
explain-blocking price every blocking source without enumerating
recall measure what fraction of known pairs blocking reaches,
and with --why, diagnose the ones it does not
estimate learn m, u and lambda and write the model
completeness estimate blocking recall with no known pairs at all
predict score the candidate pairs and write the edges above a threshold
cluster join the scored edges into duplicate clusters
rescore re-score a spilled run under a new model, without comparing again
gen-sample write a sample parquet file with planted duplicates
options:
-h, --help show this message and exit
-v, --version show the version and exit
Every command that reads data takes one or more parquet files. They are read in order into one store and each becomes a dataset, so two files mean linking rather than deduplicating — see linking two files.
What each one is for¶
| Command | Answers | Reads | Writes |
|---|---|---|---|
inspect |
Did the data load as I expected, and what does it cost in memory? | parquet + schema | stdout |
profile |
What can these columns be worth, what will a matching pair score, and which pairs of them are the same evidence twice? | parquet + schema | stdout |
levels |
Are my fuzzy thresholds anywhere near right for this column? | parquet + schema | stdout, optionally a schema |
simplify |
Which levels is this run too small to tell apart, and what does dropping them save? | parquet + schema + model | stdout, optionally a schema |
explain |
Why did this pair get that pattern, and with --model, that score? |
parquet + schema (+ model) | stdout |
explain-blocking |
How many candidate pairs will this plan cost me? | parquet + schema | stdout |
recall |
What fraction of true matches does blocking even reach? | parquet + schema + truth | stdout |
estimate |
What are m, u and λ? |
parquet + schema | model.json |
completeness |
What fraction of true matches does blocking reach, with no truth file? | parquet + schema + model | stdout |
predict |
Which pairs score above the threshold? | parquet + schema + model | edge shards |
rescore |
What would a different model have scored? | parquet + schema + model + spill | edge shards |
cluster |
Which records are the same entity? | parquet + schema + edges | clusters.csv |
gen-sample |
Give me realistic data with known answers. | — | parquet + truth csv |
The order you actually run them¶
gen-sample ──▶ inspect ──▶ profile ──▶ levels ──▶ explain-blocking ──▶ recall ──▶┐
(or your sanity is there are the is the plan does the │
own data) check anything thresholds affordable? plan reach│
to find? right? the matches?
│
┌──────────────────────────────────────────────────────────────────────────────┘
└▶ estimate ──▶ simplify ──▶ predict ──▶ cluster
model.json which levels edges/ clusters.csv
are worth
keeping?
simplify is the one diagnostic that has to come after estimate: it compares each level's
m and u against its neighbour's, so it needs a model to read them from. Everything it
merges changes how γ packs, so a schema it rewrites needs estimate run again against it.
Tuning is a loop back through the middle of that line, not a rerun of it. predict --spill
keeps each pair's agreement pattern, and rescore replays it under a new
model in a fraction of the time the first pass took — 0.01 s against 2.2 s on the 1M sample,
because every string metric has already been paid for.
The first six are diagnostics and cost seconds. They exist because the two decisions that
determine the quality of the output — the comparison levels and the blocking plan — are both
made before any expensive work runs, and both are hard to reason about without measurement.
profile comes first among them because it needs neither: no model, no blocking
plan and no known pairs, just the columns. levels is the one that answers the
first of those two decisions, and it needs nothing more than profile does.
Conventions shared by every command¶
--schema <file.json>is required by everything exceptgen-sample. It declares columns, comparisons and blocking sources; see the schema reference.- The parquet file is a positional argument, always last in the examples here.
- Errors go to stderr prefixed with
cpplink:orcpplink <command>:, and the exit code is1. A configuration error — a level applied to a column type it cannot read, a comparison with no trailingelse, a γ that overflows 32 bits — is reported at parse time, before a data file is opened. - Thresholds are given either in bits of match weight (
--threshold) or as a posterior probability (--probability). Prefer bits; see choosing a threshold. cpplink::Run(args, out, err)takes explicit streams rather than usingstd::cout, so every output shown in these pages is captured verbatim in the test suite.