Data-hygiene audit
pdcli audit runs 11 data-hygiene checks across your deals, persons, organizations, and
activities, and prints a findings count per check. Run a subset with --checks, see the
flagged items with --verbose, and fail CI on serious findings with --strict.
pdcli audit┌─────┬──────────────────────────────────────────────────────┬──────────┐│ Sev │ Check │ Findings │├─────┼──────────────────────────────────────────────────────┼──────────┤│ ● │ Open deals untouched for >14 days │ 18 ││ ● │ Open deals with no future activity scheduled │ 31 ││ ● │ Open deals past their expected close date │ 4 ││ ● │ Open deals missing owner, person/org, value, or currency │ 7 ││ ● │ Open deals far older than the typical won cycle │ 2 ││ ○ │ Closed deals missing their close timestamp │ 0 ││ ● │ Persons sharing the same email │ 3 ││ ● │ Persons with neither email nor phone │ 12 ││ ○ │ Organizations with the same normalized name │ 5 ││ ○ │ Overdue open activities piling up per owner │ 44 ││ ○ │ Deals with a value but no currency │ 0 │└─────┴──────────────────────────────────────────────────────┴──────────┘● is a must-severity check, ○ is a should. Only must checks gate --strict.
The checks
Section titled “The checks”| Name | Sev | Detection rule |
|---|---|---|
stale-deals | must | Open deal where now − update_time > 14 days. |
no-next-activity | must | Open deal with no not-done activity due today or later linked to it. |
past-close-date | must | Open deal whose expected_close_date is before today. |
missing-fields | must | Open deal missing any of: owner; both person and org; a value > 0; or currency when it has a value. |
ancient-deals | must | Open deal whose age (now − add_time) exceeds 2× the avg won cycle, or 168 days when no won deals exist. |
missing-close-time | should | A won deal with no won_time, or a lost deal with no lost_time. |
duplicate-persons | must | Two or more persons sharing the same email (trimmed, lowercased). |
uncontactable-persons | must | Person with neither a non-empty email nor phone. |
duplicate-orgs | should | Two or more orgs with the same normalized name (lowercased, common suffixes like Inc/Ltd/LLC/GmbH stripped, non-alphanumerics removed), plus fuzzy near-matches that normalize differently but score Jaro-Winkler ≥ 0.92. |
overdue-activities | should | Open (not-done) activities past due, counted per owner. |
currency-missing | should | Open deal with a value > 0 but no currency. |
The thresholds are constants in the audit: stale = 14 days, ancient = 2× the avg won cycle
(won_time − add_time across won deals) with a 168-day fallback when there are no won deals
to learn from.
The Findings count is the number of flagged items per check — except overdue-activities,
where it's the total overdue activities summed across owners.
duplicate-orgs reports two shapes of finding. Exact groups are tagged kind: exact with
the shared normalized name and the ids. Fuzzy near-matches — pairs whose names normalize
differently but still score Jaro-Winkler
≥ 0.92 — are tagged kind: fuzzy, carry both names and ids, and include the score:
{"kind":"exact","name":"acme","ids":[12,48]}{"kind":"fuzzy","names":["acmecorp","acmecrop"],"ids":[12,93],"score":0.9667}Fix what the audit finds
Section titled “Fix what the audit finds”The duplicate checks pair naturally with the merge commands — close a duplicate finding by folding the loser into the survivor:
pdcli person merge 123 --into 456pdcli org merge 12 --into 48The positional <id> is the losing record and Pipedrive deletes it; --into is the
survivor whose data wins on conflict. Both commands confirm before deleting unless you pass
--yes. So a duplicate-persons or duplicate-orgs finding becomes a one-liner: merge the
duplicate id into the one you want to keep.
When the right fix is to change a record's type — a misfiled deal that's really an early
lead, or a duplicate that should fold back into the lead inbox — the convert commands move it
across without re-keying. lead convert <id> turns a lead into a deal (and deal convert <id>
goes the other way); both run as async jobs, so add --wait to block until the conversion
finishes before you act on the result. On success the source record is deleted, so
deal convert confirms first unless you pass --yes:
pdcli lead convert adf21080-0e10-11eb-879b-05d71fb426ec --wait # promote a lead to a dealpdcli deal convert 204 --wait --yes # demote a misfiled deal to a leadWhen a deal finding needs investigating before you touch it — why is this deal stale, or
who changed its stage — deal history <id> prints its field-change audit trail (who changed
what, when), newest-first. Narrow to one field with --field:
pdcli deal history 204 # full change log for a flagged dealpdcli deal history 204 --field stage_id # just the stage movesFindings carry owner user IDs, not names. Resolve them with user list (the whole
directory) or user find <term> (by name, or --by-email for an address):
pdcli user list --output jsonpdcli user find "jane" --output jsonRun a subset
Section titled “Run a subset”pdcli audit --checks stale-deals,duplicate-persons --verbosePass a comma-separated list of check names (the Name column above). An unknown name fails
fast with exit 64 and lists the valid names.
See the items: --verbose
Section titled “See the items: --verbose”In table output, --verbose prints the flagged items under each check that has findings,
as one JSON object per line, truncated at 25 items with a "… N more" footer:
Open deals untouched for >14 days {"id":204,"title":"Acme renewal","days":31} {"id":311,"title":"Globex expansion","days":19} {"id":340,"title":"Initech seats","days":17}
Persons sharing the same email {"email":"jane@acme.com","ids":[88,142]}A check with more than 25 findings prints the first 25 followed by a
… N more footer (N = total − 25).
For full machine-readable output (no truncation), use --output json — every check
includes its full items array.
Gate CI: --strict
Section titled “Gate CI: --strict”--strict exits 1 if any must-severity check has findings, naming them; otherwise
exit 0. should findings never fail the gate.
pdcli audit --strictError: 7 must-severity checks found issues: stale-deals, no-next-activity, past-close-date, missing-fields, ancient-deals, duplicate-persons, uncontactable-personsRecipe: weekly hygiene report
Section titled “Recipe: weekly hygiene report”Run a scoped, strict audit on a schedule and let the exit code decide whether to alert. With env-var auth it's fully non-interactive:
PDCLI_COMPANY_DOMAIN=acme PDCLI_API_TOKEN=$PD_TOKEN \ pdcli audit --checks stale-deals,no-next-activity,duplicate-persons --strict --output json \ > hygiene-$(date +%F).json \ || echo "Hygiene issues found — see report"See CI recipes and Exit codes for wiring this into a pipeline.
Process compliance: audit stage-skips
Section titled “Process compliance: audit stage-skips”Where the 11 checks above grade your data, audit stage-skips grades your process: did
deals move through the pipeline the way they're supposed to? It mines each deal's changelog
(open, won, and lost — history matters in every state) and walks the stage_id transitions
chronologically, flagging two kinds of out-of-band move with who made them:
- Forward skips — a deal that jumped a gate (destination order > source order + 1), listing the stages it leapt over. A clean single-stage advance is normal and never flagged.
- Backward moves — a deal pulled back to an earlier stage (often sandbagging: dragging a deal down to look conservative, or to reset its age).
pdcli audit stage-skips --pipeline 1┌──────┬──────────┬──────────────────────────┬──────────────────────┬──────────────────────┬───────┐│ Deal │ Kind │ From │ To │ Skipped gates │ Actor │├──────┼──────────┼──────────────────────────┼──────────────────────┼──────────────────────┼───────┤│ 204 │ skip │ Qualified (1) │ Negotiations Started (3) │ Proposal Made │ 42 ││ 311 │ backward │ Negotiations Started (3) │ Qualified (1) │ │ 17 │└──────┴──────────┴──────────────────────────┴──────────────────────┴──────────────────────┴───────┘From/To carry the stage's order number in parentheses; Skipped gates names the stages a
forward skip leapt over (empty for a backward move). Actor is the user ID who made the
change — resolve it with user find / user list, exactly as for the deal findings above.
This is informational and always exits 0 — it surfaces moves for review rather than
gating CI. Cross-pipeline hops and moves touching a since-deleted stage are ignored (stage
order is only comparable within one pipeline). Like the time-intelligence metrics, it costs
one changelog request per deal (20 tokens each) and warns on stderr above 100 deals. The
conversion-side view of the same data — every transition counted, with backward edges and
Won/Lost terminals — is metrics conversion-matrix.