Skip to main content

Command Palette

Search for a command to run...

How We Turn a 35% BLS PMHNP Growth Projection Into Search, Alerts, and Better Job Matches

A builder’s take on macro labor stats: ingesting 500+ sources daily, normalizing messy postings, and surfacing “career confidence” as data you can query.

Updated
5 min read

The BLS projection (35% PMHNP growth from 2024–2034) is a macro signal. The hard part is translating it into a daily system that answers: where are the roles, what do they pay, how fast do they move, and which postings are actually real.

How We Turn a 35% BLS PMHNP Growth Projection Into Search, Alerts, and Better Job Matches

The BLS projection of 35% PMHNP job growth (2024–2034) is a clean number that shows up in headlines. As builders, we treat it as a macro input—useful, but not directly actionable.

What’s actionable is what that trend turns into at the job-posting layer:

  • more postings (and more duplicates)
  • faster hiring cycles (time-to-fill compresses)
  • wider variance by state, setting, and employer type
  • more compensation noise (ranges, bonuses, productivity, telehealth modifiers)

At PMHNP Hiring, we aggregate from 500+ job sources daily and maintain 10,000+ verified PMHNP jobs across 50 states. The goal isn’t to repeat the BLS number—it’s to make it queryable: where is growth showing up today, and what does “good role” mean in that market?

Macro projection → micro signals we can measure

The 35% projection is best read as sustained demand: more people seeking care, expanding access efforts, and pressure on psychiatry capacity. PMHNPs sit right in the middle.

But “more jobs” doesn’t mean “every job is a fit.” So we track operational signals that correlate with a heated market:

  • posting velocity (new jobs per day/week by state + setting)
  • time-to-fill proxies (how long a posting stays live, how often it gets refreshed)
  • credentialing friction (signals in text like “credentialing support,” “start in 2–4 weeks,” etc.)
  • comp package completeness (whether salary, schedule, supervision, and ramp are specified)

One example we surface internally is a rolling days-live metric. It’s not perfect, but it’s observable at scale.

-- Rough “days live” metric from our normalized postings table
select
  state,
  percentile_cont(0.5) within group (order by (now() - first_seen_at)) as median_days_live,
  count(*) as active_jobs
from jobs
where
  role = 'PMHNP'
  and status = 'active'
group by state
order by median_days_live asc;

If median days-live drops, it often matches what clinicians feel as “employers are moving faster.” In the original blog, we cited time-to-fill tightening (e.g., ~32 days vs ~45). We treat those as hypotheses and validate them against our own observed posting lifecycle.

The ingestion pipeline: 500+ sources, one schema

Scraping PMHNP jobs isn’t “fetch HTML, parse title.” Every source has its own quirks:

  • different location formats (city/state, remote, multi-state, “within 50 miles”)
  • different salary formats (hourly, annual, per-visit, wide ranges)
  • duplicated listings across ATS platforms, job boards, and staffing agencies
  • stale posts that get re-published with new IDs

Our pipeline is built to turn that mess into a stable contract:

  1. Fetch (scheduled jobs) from boards/ATS feeds
  2. Parse into a common intermediate model
  3. Normalize fields (title → role, location → geo, salary → annualized range)
  4. Deduplicate and verify
  5. Index for real-time filtering + alerts

Tech stack pieces:

  • Next.js + TypeScript for UI and API routes
  • Supabase (Postgres) for storage + full-text search + RLS
  • Stripe for billing (alerts/subscriptions)

Deduplication: the “more jobs” trap

Job growth increases volume, but it also increases duplicates. A single PMHNP opening can appear on:

  • the employer site
  • an ATS mirror
  • 2–5 job boards
  • a staffing listing with rewritten text

If we don’t dedupe, users think there are more unique opportunities than there are—and alerts become spam.

We generate a fingerprint using a mix of deterministic and fuzzy signals:

  • normalized employer name
  • canonicalized location (lat/lng + radius buckets)
  • role taxonomy (PMHNP vs “Psych NP” variants)
  • compensation overlap (when present)
  • text similarity on responsibilities and requirements

Pseudo-code sketch:

type Job = {
  title: string
  employer: string
  city?: string
  state?: string
  lat?: number
  lng?: number
  description: string
  salaryMin?: number
  salaryMax?: number
}

function fingerprint(job: Job) {
  return hash([
    normalizeEmployer(job.employer),
    normalizeRole(job.title),
    geoBucket(job.lat, job.lng, 0.25),
    salaryBucket(job.salaryMin, job.salaryMax),
    simhash(normalizeText(job.description))
  ].join('|'))
}

This is what turns macro growth into a trustworthy count of verified jobs.

Salary data: why “$139K–$155K” is a normalization problem

Clinically, people want to know pay. Technically, pay is one of the messiest fields we ingest.

Common failure modes:

  • hourly rates without hours/week
  • “$120k–$250k” ranges that include productivity/bonus but aren’t labeled
  • sign-on bonuses mixed into base
  • “per diem” roles listed as annual
  • DNP vs MSN differentials inconsistently stated

So we normalize salaries into an annualized range with metadata:

  • pay_type (hourly/annual/unknown)
  • annual_min, annual_max
  • confidence_score
  • includes_bonus (best-effort)

Example normalization logic:

function annualize({ payType, min, max, hoursPerWeek = 40 }: any) {
  if (payType === 'hourly') {
    return {
      annualMin: min * hoursPerWeek * 52,
      annualMax: max * hoursPerWeek * 52,
    }
  }
  return { annualMin: min, annualMax: max }
}

That’s how we can talk about national ranges (like ~$139K–$155K common bands, entry-level around ~$126K) while still being honest about variance and data quality.

What the growth signal means for “career confidence” (as data)

The BLS projection improves the odds that you’ll find opportunities—but the quality of those opportunities depends on factors you can filter for:

  • setting (outpatient, community mental health, integrated care, telepsych)
  • onboarding support (mentorship/supervision signals)
  • realistic ramp + admin time
  • credentialing speed

From a product standpoint, this is why we invest in structured fields extracted from unstructured text. A fast offer can correlate with staffing pressure; we try to surface the context so users can choose strategically.

In other words: the 35% growth projection is the headline. The system work is turning it into a search experience where you can reliably answer, “Where are the real roles, and which ones are built to support me once I’m hired?”