Evidence Tools

Derived data subjects, Nigerian PII scanning, Section 37 decision receipts, statutory collisions, schema-as-RoPA, and grievance notices. Pure functions from /server, plus CLI commands.

Why these exist

The compliance score asks whether you support a right. Regulators and auditors ask what you actually did, on what grounds, and who signed it off. These tools turn self-attested booleans into derived, cited and reviewable records. Every one is a pure function with no React, exported from /server, /core and the package root.

Drafts, not decisions

Nothing here files with the NDPC, sends to a data subject, or deletes data. Outputs that affect a person are drafts that a named reviewer must approve. Not legal advice.

Derived data subjects

estimateDerivedDataSubjects counts the guarantors, next of kin, referees, dependants and uploaded phone contacts you hold, and shows your declared DCPMI tier beside the tier you would reach if they were counted. It never blends the two. Multipliers are required inputs; there are no built-in defaults. Try it at /how-many-data-subjects.

const r = estimateDerivedDataSubjects({
  directSubjects: 900,
  relationships: [{ kind: 'guarantor', perDirectSubject: 2, coverage: 0.6 }],
});
r.tiers.declared.tier;    // 'OHL'
r.tiers.withDerived.tier; // 'EHL'

// npx ndpr dcpmi --direct 900 --relations guarantor:2:0.6 --revenue 500000000

estimateStatutoryCeiling returns the NDPA Section 48(4) maximum from a pinnable ruleset, always labelled as a ceiling and never a forecast.

Nigerian PII detection and ndpr scan

detectNigerianPII finds Nigerian mobile numbers, NIN/BVN-shaped numbers, NUBAN account numbers, Luhn-valid cards, CAC numbers, plates and emails. NIN and BVN cannot be told apart or proven by pattern, so they report low confidence unless nearby text names them. validateNuban applies the CBN check digit when you know the bank code. Redaction is masking, not anonymisation.

npx ndpr scan src --fail-on high          # pre-commit or CI gate, any stack
npx ndpr scan . --types national_id,nuban --json

Section 37 decision receipts

issueDecisionReceipt gives a person subject to a solely automated decision an 8-character code to quote when asking for human review. You must state the legal basis; the toolkit never chooses it. The SMS never includes the outcome. decisionContestRequest turns a receipt into an automated_decision_making request in your normal DSR queue.

Statutory collisions

resolveDsrOutcomepartitions a request's data categories into act now, hold (with an end date and cited grounds) and needs human review, and drafts the letter. Grounds come from a register of StatutoryExceptionRows. Only rows marked reviewed, with a named reviewer, a verbatim quote and a source, can produce a hold. The two starter rows ship unverified and only route requests to a person.

const draft = resolveDsrOutcome(
  { id: 'dsr_1', type: 'erasure', sector: 'banking', dataCategories: ['marketing_preferences', 'kyc_identity'] },
  { register: myReviewedRegister, asOf: '2026-10-13' },
);
draft.proposed;        // 'partially_fulfil'
draft.requiresSignOff; // always true

planRetention returns per-record retain, review-for-deletion, statutory-hold or needs-review decisions. It never deletes.

Schema-as-RoPA

Annotate personal-data columns once in your Prisma schema. The lint fails unannotated personal-looking fields and annotations missing a category, purpose, basis or retention period; it never infers them. The generator turns valid annotations into draft RoPA entries marked asserted for review.

/// @ndpr subject=customer purpose=kyc basis=legal_obligation retain=P5Y
model Customer {
  /// @ndpr category=identifier.bvn processor=smile-identity
  bvn String
  /// @ndpr none
  riskBand Int
}

// npx ndpr schema lint prisma/schema.prisma
// npx ndpr schema ropa prisma/schema.prisma --json

Grievance notices

composeGrievancePacketwrites a data subject's notice to a controller, modelled on the Standard Notice to Address Grievance in NDPC GAID 2025 Article 40 and Schedule 9, with the matching requests attached and renderings for email, WhatsApp and SMS. It needs only a name and a phone or email. draftSection46Complaintdrafts a complaint to the Commission only after the decision date and only with the subject's confirmation. Try it at /report-am.