npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

open-civics

v0.1.3

Published

US legislator contact data — names, emails, phones, districts

Readme

open-civics

Structured contact data for US elected officials — state legislators, county councils, and city councils — scraped weekly and published as npm packages.

South Carolina is fully covered: 170 state legislators and 96 local jurisdictions (every county and incorporated municipality).

Packages

Install via npm:

npm install open-civics            # contact data
npm install open-civics-boundaries # district boundary GeoJSON

| Package | What's in it | |---------|-------------| | open-civics | Names, titles, emails, phones, districts for state and local reps | | open-civics-boundaries | GeoJSON district boundaries for client-side point-in-polygon matching |

What the data looks like

Each local jurisdiction file has a meta block and a members array:

{
  "meta": {
    "state": "SC",
    "level": "local",
    "jurisdiction": "county:greenville",
    "label": "Greenville County Council",
    "lastUpdated": "2026-03-14"
  },
  "members": [
    {
      "name": "Benton Blount",
      "title": "Chairman, District 19",
      "email": "[email protected]",
      "phone": "(864) 483-2474"
    }
  ]
}

State legislator files are keyed by district number with senate/house chambers:

{
  "meta": { "state": "SC", "level": "state" },
  "senate": {
    "1": { "name": "...", "district": "1", "party": "R", "email": "...", "phone": "..." }
  },
  "house": {
    "1": { "name": "...", "district": "1", "party": "D", "email": "...", "phone": "..." }
  }
}

Usage

// State legislators
import scState from 'open-civics/sc/state.json';
const senator = scState.senate["1"];

// Local councils
import greenvilleCounty from 'open-civics/sc/local/county-greenville.json';
const members = greenvilleCounty.members;

// District boundaries (GeoJSON FeatureCollection)
import senateBoundaries from 'open-civics-boundaries/sc/boundaries/sldu.json';
import houseBoundaries from 'open-civics-boundaries/sc/boundaries/sldl.json';
import countyBoundaries from 'open-civics-boundaries/sc/boundaries/county-greenville.json';

Boundary files are standard GeoJSON FeatureCollections. Each feature has a properties.district field matching the district keys in the contact data. Use any point-in-polygon library (Turf.js, Mapbox, etc.) to find which district a user's address falls in.

Data structure

data/
  sc/
    state.json                # State legislators (senate + house + governor)
    local/
      county-greenville.json  # Greenville County Council
      place-greenville.json   # Greenville City Council
      ...                     # 96 jurisdiction files total
    boundaries/
      sldu.json               # State senate district boundaries
      sldl.json               # State house district boundaries
      county-greenville.json  # County council district boundaries
      place-greenville.json   # City council district boundaries (where available)
      ...

How scraping works

Data is scraped from government websites using Python adapters — one per site pattern. Five shared adapters handle the most common CMS platforms:

| Adapter | Sites | How it works | |---------|-------|-------------| | Revize | ~30 cities | Parses bold name / mailto / phone patterns | | CivicPlus | ~14 counties | Parses staff directory tables with JS-obfuscated emails | | TableAdapter | ~10 jurisdictions | Auto-detects HTML tables with name/email/phone columns | | GenericMailto | ~15 cities | Finds mailto links in WordPress/Drupal content areas | | DrupalViews | ~3 counties | Parses Drupal views-row and person-item patterns |

The remaining jurisdictions use bespoke adapters, MASC (Municipal Association of SC), or SCAC (SC Association of Counties) as data sources.

Boundary data comes from the US Census TIGER/Line shapefiles (state districts) and ArcGIS REST services (local districts).

Running the scrapers

Requires Python 3.12+.

pip install -r requirements.txt

# Scrape everything for a state
python -m scrapers --state SC

# Scrape only state legislators
python -m scrapers --state SC --state-only

# Scrape only local councils
python -m scrapers --state SC --local-only

# Scrape state + local, skip boundaries (faster)
python -m scrapers --state SC --skip-boundaries

# Preview what would run without scraping
python -m scrapers --dry-run

# Validate all data files
python validate.py

Automation

Three GitHub Actions workflows keep data fresh:

  • Weekly scrape (Mondays 10am ET): Runs state + local scrapers, opens a PR with changes
  • Monthly scrape (1st of month 10am ET): Full scrape including boundary rebuilds
  • Validation: Runs on every PR touching data/ — auto-merges data-update/* branches if validation passes
  • Publish: Weekly npm publish if data changed since last release

Running tests

pip install -r requirements-dev.txt

# Unit tests (fast, no network)
pytest tests/unit/ -v

# All tests including integration
pytest -v

# Refresh integration test snapshots from live sites
python scripts/refresh_snapshots.py

Adding a new state

  1. Add a state block to registry.json under states.XX
  2. Add email format rules to scrapers/state_email_rules.py
  3. Run python -m scrapers --state XX --state-only to pull legislators
  4. Add local jurisdiction adapters as needed (see CLAUDE.md for the adapter selection checklist)
  5. Add boundary sources to registry.json and run python -m scrapers --state XX --boundaries-only

License

See LICENSE for details.