Skip to content
MUNEEB SHAFIQ
AGENTIC DATA PIPELINESF02

Provider Research Factory

Turns a population query into a verified national dataset behind two human approval gates.

  • Python 3.12
  • Stdlib streaming CSV
  • openpyxl, optional
  • Claude Code subagents
Status
Version 1 delivered. Three national datasets built and signed off at GATE D, urology (the reference build, reproduced end to end), dentistry and cardiology, with a fourth run directory present as a second urology cut. Build-order steps 4 to 6 (single outbound channel with measurement, email enrichment with recorded consent basis, measurement feedback loop) are deliberately unbuilt; the repo contains no send path. Last pushed 1 September 2026.
Private repository

By the numbers · 11

  • 9,835,402

    Registry rows streamed per national build

  • 409,848 / 73,394 / 22,195

    Records delivered across the three national datasets

  • 11

    assertion functions in the deterministic pipeline

  • 286

    Assertions in the synthetic end-to-end smoke suite

  • 7 lenses x 3 verifiers, 2-of-3 to survive

    Adversarial QA protocol

  • 75

    Committed QA artifacts across runs (64 for the cardiology run alone)

  • 24 removed, 0 by the standalone report

    Deactivated records caught by the corpus scrub that the official snapshot missed

  • 16 of 16 absent, 7 of 7 control matched

    Independent verification of scrubbed records against the live NPI Registry

  • 30 codes admitted, 5 cardiology

    Measured cost of a prefix filter over the controlled vocabulary

  • 40,643 records (74.4%)

    Headline records silently dropped by the most natural text filter

  • 13,562 excluded, 273 surviving, 0 in headline

    Enforced cost of the adjacency decision on the cardiology run

Summary

A query-to-dataset factory for professional-population research, built on the Puffo agent platform. A population query ("All urologists in the United States") is parsed into a frozen target spec, researched by parallel judgment agents, approved by a human at a source gate, then built by deterministic Python that streams the ~9.8M-row NPPES national registry, applies an exact-match taxonomy code set, joins CMS enrichment sources under hit-rate bands, normalizes, and packages CSV splits plus a styled workbook. An adversarial QA panel of seven lenses attacks the built dataset; each finding goes to three independent verifiers and survives only on two confirmations. A second human gate signs the deliverable before anything is called delivered. An optional marketing layer runs on an allowlist-derived view from which contact channels the compliance gate permanently blocks have been structurally stripped, and the system contains no send path at all, deliberately, and by build order.

The problem

Buying a professional contact list means buying someone else's undocumented selection rule. The failure modes are quiet: a specialty filter that substring-matches the wrong specialty, an enrichment join that silently returns empty, a status flag that misses providers who have since been deactivated. None of these announce themselves, the file still opens, the counts still look plausible, and the error only surfaces after the list has been used. For regulated outreach to clinicians the exposure compounds: contacting a wrong or dead record is a data-quality problem, contacting them on a legally blocked channel is a statutory one. The requirement was a dataset whose selection rule, provenance and limits are all reconstructible, and an outreach layer that cannot reach a blocked channel even by mistake.

Approach

  1. Fix the doctrine before the code: METHODOLOGY.md is a literal record of one hand-built dataset, every command, every dead end, all three defects, and the system is derived from it. The README states that when the two disagree, the methodology wins and the README has the bug.

  2. Split judgment from mechanics. Models are used in exactly four places (source discovery, ontology resolution, adversarial QA, marketing content); fetch, filter, join, normalize and package are deterministic Python, because a deterministic stage can be asserted against.

  3. Two-layer agent definition: agents/*.md holds the doctrine brief, .claude/agents/*.md is thin Claude CLI wiring, puffo/profiles/*.md is the same brief plus a workspace contract for the hosted agent roster. Behavior changes go in the brief, never the wiring.

  4. Place the human gates where they are cheapest. GATE R reviews the source manifest before any large download (a wrong source caught before download costs a review; after, it costs the run). GATE D reviews the finished deliverable before it is called delivered.

  5. Encode the three defects the reference build found the hard way as standing guards: declared-column existence and minimum join hit rate, exact-match code sets only, and an independent cross-check against the authoritative deactivation list.

  6. Turn QA into an adversarial protocol rather than a checklist: seven single-lens critics in parallel, each finding sent to three verifiers prompted to refute it, surviving only on 2-of-3, defaulting to refuted under uncertainty, looping until two consecutive clean rounds.

  7. Make the QA panel own the blind ontology critic and run it by default, because a deriver who commissions their own critic chooses its framing, inputs and timing, every degree of freedom the check exists to remove.

  8. Make the compliance boundary structural: marketing/extract.py builds an allowlist view, so the fields the gate permanently blocks do not exist in the namespace a send decision would run in.

  9. Write no send path. Build order puts single-channel outbound at step 4, behind the compliance gate at step 3; steps 4-6 are unbuilt on purpose and the repo makes premature sending impossible by not containing it.

  10. Commit run identity, not run payload: spec, sources, manifest, QA findings and documentation are versioned; the CSV deliverables, raw downloads, source cache and suppression store are not.

Architecture

provider-research-factory · flow
A population query enters an orchestrator session, which parses it into a target spec and either reuses a proven spec or fans out three to four source-discovery agents with different framings, one ontology agent and one schema probe, merging their output into the run's spec.json and sources.json.The run halts at GATE R for human approval of sources, code set and disk budget before any large download.Deterministic pipeline stages then run in sequence, resumable fetch, bulk stream and exact-code filter, enrichment joins under declared hit-rate bands, normalization, with barriers only where a stage genuinely needs the complete prior output.Nine assertions can fail the build at any point, and none can be downgraded to a warning.Deterministic QA runs first, then an adversarial panel of seven single-lens critics in parallel; each finding goes to three verifiers prompted to refute it and survives only on two confirmations, looping until two consecutive clean rounds.Packaging emits CSV splits and a styled workbook, a documentation agent writes the deliverable README, and a deterministic output validator runs last.GATE D puts the finished deliverable in front of a human before it is marked delivered.Only afterwards, and only on request, does the marketing layer build an allowlist view, the step that structurally strips the channels the compliance gate blocks, and run segmentation and explainable prioritization on it.Every list build consults an append-only suppression store, and every channel decision calls the compliance gate, which can only permit or block and never sends.
ComponentRole
CLAUDE.md + agents/00-orchestrator.mdOrchestrator doctrine: runs the state machine, spawns judgment subagents by type, executes pipeline scripts rather than doing their work in-model, and stops at both human gates. Explicitly forbidden from weakening an assertion or phrasing a gate so that silence reads as consent.
agents/01-06 briefsOne model-tier brief per judgment role, source discovery, ontology, schema probe, QA panel, documentation, marketing. These are the doctrine layer; .claude/agents/*.md is thin wiring that points at them.
puffo/profiles/*.mdSeven hosted-agent profiles for the Puffo roster, each the repo brief plus a workspace contract (repo root, per-agent write scope, no push) and a use-case isolation rule.
pipeline/contracts.py + run.pyTyped stage hand-offs and the stage driver. run.py is the single entry point (python pipeline/run.py <slug> <stage>) and holds the provenance checks that need both the declared-source and fetch-result collections in scope.
pipeline/assertions.pyNine build-failing invariants: declared columns exist, join hit rates inside a declared band, no duplicate keys, no records on any scrub list, expected geographies present, coverage thresholds, no unrendered placeholders, splits partition the master by membership rather than summing to it, inputs identified by sha256, extraction ratio sane.
pipeline/fetchkit.py + sourcecache.pyResumable downloads with archive verification and a true-size probe (Windows reports stale sizes under an open write handle), plus a persistent cross-run cache of upstream archives so a rebuild does not re-download.
pipeline/filter_bulk.py + registries/nppes.pyStreams the national registry, selects on exact taxonomy codes across primary and secondary slots, applies newest-wins merging of weekly increments ordered by coverage end rather than filename.
pipeline/enrich.py + normalize.py + package.py + manifest.pyHit-rate-guarded joins, phone/postal/name normalization and derived fields, CSV and workbook packaging with a re-parse cross-check of every written file, and a BuildManifest that is diffed against the previous run of the same spec.
pipeline/qa_checks.pyDeterministic QA, integrity, coverage, geography, scrub and placeholder checks, run before the adversarial panel, on the principle that deterministic findings are free.
compliance/channel_rules.pyThe gate. One contact, one channel, one verdict, with the statute named on every block: fax and SMS and robocall blocked outright, email blocked without a recorded consent basis, phone and physical mail allowed with attached obligations, unknown channels blocked rather than assumed fine. No bypass parameter exists.
marketing/extract.pyThe structural compliance boundary. Builds an allowlist marketing view, audits the written header against the forbidden-field set, and records provenance including the master and spec hashes it read.
marketing/segmentation.py + prioritization.pyCohort cuts from signals already in the data, and explainable scoring that emits weighted components rather than opaque totals, with a read audit that distinguishes a suppressed axis from a column that silently failed to resolve.
marketing/suppression.pyAppend-only opt-out / bounce / do-not-contact store that is its own audit log, consulted at list build, and reporting whether the store existed separately from how many keys it held.
tests/test_smoke.pyEnd-to-end run on a synthetic NPPES-shaped world including a substring decoy, exercising filter through marketing and proving the guards fire, a missing declared column, a sub-minimum hit rate, a decoy that must not be selected, a forbidden field that must not survive into the view.
runs/{slug}/qa/The committed evidence trail: per-finding files, per-round ledgers, pre-registrations written before a re-cut runs, a contamination register frozen before any blind critic is commissioned, and retractions of the panel's own confirmed findings.

Trade-offs

  • Chose

    Deterministic Python for fetch, filter, join, normalize and package

    Over

    Agent-driven data handling with an LLM inside the file-streaming loop

    README and METHODOLOGY 2.1 state the governing principle directly: a script does mechanical work faster and more reliably than an agent, and a deterministic stage can be asserted against. The README calls an architecture that puts an LLM in the file-streaming loop a design defect.

  • Chose

    Compliance rules as hard-coded functions with no bypass parameter

    Over

    Compliance stated as instructions in the agent prompts

    channel_rules.py opens with the reasoning: a model can be argued out of a prompt, it cannot be argued out of a blocked function call. The module notes that adding a force flag or admin mode is itself the defect.

  • Chose

    Structural stripping, an allowlist marketing view that omits blocked-channel fields

    Over

    Policy prohibition on using those fields downstream

    extract.py states the requirement as architectural, not policy: the forbidden columns must be unable to reach the marketing layer, so no later code path, including one written by someone who never read the methodology, can route them. The gate's own DIRECT branch notes that reaching it means a boundary was already crossed.

  • Chose

    A hit-rate band (declared floor and declared ceiling) on every enrichment join

    Over

    A minimum hit rate alone

    assertions.py records the failure it was written for: a one-sided floor asks whether there is enough coverage, and a fabricated or wrongly-joined column always answers yes. medicare_enrolled read 100% against a source that never ran and defeated every missingness check by not being missing.

  • Chose

    Exact-match code sets over a controlled vocabulary

    Over

    Name-substring filtering on the specialty term

    Documented as Defect 2 and measured repeatedly: 'urolog' is a substring of 'neurology'; on cardiology a 207R prefix filter admits 30 codes of which only 5 are cardiology, and filtering the segment label on 'cardiolog' drops 40,643 records (74.4% of the headline) while the survivors still look like a plausible list.

  • Chose

    Findings survive only on 2-of-3 independent confirmations, defaulting to refuted

    Over

    Accepting any single agent's finding

    agents/04-qa-panel.md states that phantom defects cost rebuild time and trust, and that a claim without a reproducer is an opinion and is discarded. Refuted findings are still committed, as evidence the check ran.

  • Chose

    Committing run identity (spec, sources, manifest, QA) and gitignoring the CSV payload

    Over

    Committing the delivered datasets alongside their provenance

    .gitignore records both reasons: every payload byte is reproducible from committed files, the delivered dentist master alone is 120 MB and over GitHub's per-file limit, and a repository that leaves the machine should not carry the providers' personal data.

  • Chose

    Reusing a proven spec from specs/ instead of re-running Tier 1 research

    Over

    Re-deriving the code set on every run of a solved query

    agents/00-orchestrator.md: a proven spec's code set was already resolved, criticised and human-approved once, and rerunning research on a solved query only creates the chance to resolve it differently.

  • Chose

    Constraining the blind ontology critic by allowlist (specialty string, vocabulary URL)

    Over

    Enumerating forbidden information channels as a denylist

    The orchestrator brief argues a denylist has to anticipate every channel, and the channel that leaks will be the one nobody listed. The same argument drives the allowlist marketing view.

  • Chose

    Python standard library plus openpyxl

    Over

    pandas

    METHODOLOGY Phase 0.7 records the decision as deliberate, no reason to pull numpy for a streaming job. requirements.txt is openpyxl only, and the README notes even that is optional.

At scale

  • ~9.8 million registry rows streamed per national build

  • 409,848 records in the largest delivered dataset; 60-column output schema on the reference build

  • 1.09 GB compressed source expanding to 11.0 GB, against a documented 10-12x disk budget rule

  • 155 tracked files, 22 Python modules, 477,699 bytes of Python (corrected: 181 counted directories as paths)

  • 75 committed QA artifacts across four run directories; 64 for the cardiology run alone

  • Seven-agent hosted roster plus seven Claude CLI project subagent definitions

  • Six upstream sources per build, one bulk registry, weekly increments, a deactivation report, a pinned taxonomy vocabulary, and three CMS enrichment datasets

  • 1,986-line synthetic end-to-end test suite carrying 286 checks, running with no downloads (corrected: the 287th match is the check() definition itself)

My role

Architect and operator. The seven human-authored commits on 10 August 2026 are the founding set and they are the whole design: the initial Provider Research Factory (pipeline, compliance gate, marketing layer, agent briefs), the relocation and isolation rules, the wiring of those briefs as runnable Claude CLI project subagents, the Puffo provisioning pack of profiles and roster parameters, a persistent resumable source cache, corrected column declarations against the local corpus, and the first live end-to-end run producing 22,195 records with a population identical to the hand-built reference. The 314 subsequent commits were made by mso-orchestrator, an agent Muneeb defined in this repo (puffo/profiles/mso-orchestrator.md), provisioned into a space he created on his own machine, and pointed at a workspace contract he wrote. He owns the doctrine those agents follow, the deterministic pipeline they are forbidden to bypass, the gates they must stop at, and the human sign-off at GATE D on every delivered dataset. He did not hand-write the later runs.