Scrapes Gram Panchayat (GP) scores from the Government of India's Panchayat Advancement Index (PAI) portal — overall and theme-wise PAI scores for every GP, by State → District → Block, for both published years.
Covers PAI 1.0 (2022-2023) and PAI 2.0 (2023-2024): overall PAI score plus nine thematic scores/grades (T1 Poverty-Free … T9 Women-Friendly) per Gram Panchayat.
| year | states with data | districts | GPs | score rows (long) | blocks (data / no-data) |
|---|---|---|---|---|---|
| 2022-2023 | 29 / 34 | 753 | 169,673 | 1,696,730 | 5,917 / 1,282 |
| 2023-2024 | 33 / 34 | 741 | 183,011 | 1,830,110 | 6,103 / 1,032 |
Full per-state breakdown: docs/pai_summary_by_state.csv
(year totals: docs/pai_summary.csv). Regenerate with
uv run scripts/data_summary.py.
Download — parsed data (GitHub Release data-v2)
pai_2022-2023_data.tar.gz(~96 MB)pai_2023-2024_data.tar.gz(~87 MB)
Each archive contains, under consolidated/:
| file | grain | description |
|---|---|---|
gp_metadata_<year>.csv |
one row / GP | identity & location: state, district, block, gp_name, gp_code, scorecard_url, … |
gp_scores_long_<year>.csv |
one row / GP × theme | tidy scores: theme_slug, theme_header, score, grade, band (slug overall_pai_score = overall) |
gp_scores_wide_<year>.csv |
one row / GP | wide form: one <theme>_score / _grade / _band column per theme |
block_manifest_<year>.csv, dropdown_inventory_<year>.csv |
— | scrape provenance / the option universe |
Archives also include the raw per-block CSV/JSON tree under blocks/ and a SUMMARY.md.
The consolidated CSVs are rebuilt from the per-block files (de-duplicated current state),
not the append-only logs.
The rendered HTML for every block page (~13,400 pages) is archived on Dataverse:
doi:10.7910/DVN/FRUKWS
(pai_2022-2023_html.tar.gz, pai_2023-2024_html.tar.gz).
- West Bengal has no GP scores in either year (the portal returns "Details are not available"); a few small UTs/states have data only in 2023-2024.
- Reproducible end-to-end from
scripts/pai_scraper_resumable.py→scripts/data_summary.py→scripts/build_release.py.
Uses uv (Python ≥ 3.13):
uv sync
uv run playwright install chromiumuv run scripts/pai_scraper_resumable.py \
--years 2022-2023 --state-contains Bihar --limit-districts 1 --limit-blocks 3If Chromium is flaky, try installed Chrome with --browser-channel chrome.
uv run scripts/pai_scraper_resumable.py --years 2022-2023 2023-2024 --headless --delay 1.5Run the same command again — blocks with DONE.json are skipped. Retry flags:
--retry-empty— re-do blocks that finished with zero GP rows.--retry-no-data— re-verify blocks previously marked "no data available". The server returns an identical "not available" alert both for genuinely empty blocks and (spuriously) under load, so a no-data result is confirmed by re-searching--no-data-confirmtimes (default 2) before it is accepted; if data appears it is recovered, and an indeterminate recheck is retried (never a terminal false negative).--overwrite— rescrape matching blocks unconditionally.
data/
├── pai_scrape.log
├── block_manifest.csv # append-only scrape log
├── dropdown_inventory.csv # append-only option universe
├── 2022-2023/
│ └── State__code/District__code/Block__code/
│ ├── context.json
│ ├── DONE.json
│ ├── FAILED.json # only if failed
│ ├── html/page_001.html
│ ├── data_wide.csv # authoritative per-block data
│ ├── metadata.csv
│ └── scores_long.csv
└── 2023-2024/
The per-block files are the source of truth. The consolidated gp_metadata.csv /
gp_scores_long.csv are generated on demand from them (the scraper no longer writes them
inline), so they can never drift or duplicate:
uv run scripts/pai_rebuild_index.py --out data # de-duplicated global indexes
uv run scripts/data_summary.py # coverage tables -> docs/pai_summary*.csv
uv run scripts/scrape_progress.py --year 2023-2024 # progress from block_manifest.csv
uv run scripts/pai_inspect_output.py --out data # quick counts
uv run scripts/build_release.py # release archives -> dist/make sync # uv sync + chromium
make format # ruff format + ruff check --fix
make lint # ruff format --check + ruff check
make test # pytest