SRS maternal mortality¶
The Registrar General of India’s 4-page Special Bulletin on Maternal Mortality in India 2021-23 comes from the Sample Registration System (SRS). Table 1 on page 3 gives, for India, 19 States, the other States together and three subtotals, the Maternal Mortality Ratio (MMR, maternal deaths per 100,000 live births) with its 95% confidence interval, the Maternal Mortality Rate (per 100,000 women aged 15-49) and the lifetime risk. Table 2 on page 4 gives the age distribution of maternal and non-maternal deaths, also with intervals.
The files are in pdfexorcist/recipes/srs_maternal_mortality/: recipe.toml (Table 1), age.toml (Table 2), cod_top10.toml (see below), and the parsers srs_mmr.py and srs_cod_top10.py. The fixtures are the whole bulletin and one page of the Cause of Death report, in tests/fixtures/srs_mmr/.
Intervals and percents¶
A row of Table 1 prints as
INDIA 88 (79,96) 5 0.18%
VALUE in pdfexorcist/rows.py reads (79,96) as one parenthesised number with a thousands comma, so the default parser would vote on the interval as one string. srs_mmr.py splits it into mmr_lower and mmr_upper, and drops the % from the lifetime risk, whose column is lifetime_risk_pct.
PDFium splits a few tokens at glyph gaps: 0.18 %, (18,26 ), 30 - 34. The parser joins a lone % or ) to the token before it and closes up a hyphen between digits.
The recipe¶
name = "SRS maternal mortality by State"
[pages]
match = 'Table 1: Maternal Mortality Ratio'
[engines]
ocr = false
[parser]
type = "custom"
function = "srs_mmr.py:parse"
key = ["page", "row", "column"]
[[checks]]
function = "srs_mmr.py:within_ci"
# Lifetime risk = 1 - (1 - rate/100000)^35, from the bulletin's paragraph 5
[[checks]]
function = "srs_mmr.py:lifetime_risk"
[output]
layout = "table"
rows = ["area"]
columns = "column"
format = "csv"
age.toml is the same with match = 'Table 2: Age Distribution' and only the interval check.
Run it¶
pdfexorcist extract tests/fixtures/srs_mmr/srs_mmr_bulletin_2021-23.pdf --recipe pdfexorcist/recipes/srs_maternal_mortality/recipe.toml -o mmr.csv
✓ pdftotext 120 readings 0.0s
✓ pdfplumber 120 readings 0.1s
✓ pymupdf 120 readings 0.0s
✓ camelot 120 readings 0.5s
✓ pdfium 120 readings 0.0s
✓ Agreed 120 100.0% of cells
Unresolved 0
✓ Checks 2 rules all passed
area,mmr,mmr_lower,mmr_upper,mortality_rate,lifetime_risk_pct
INDIA,88,79,96,5,0.18
Assam,110,53,167,6,0.23
...
EAG AND ASSAM SUBTOTAL,118,103,133,9,0.31
...
OTHER SUBTOTAL,75,62,88,4,0.14
age.toml reads Table 2, every cell agreed:
area,maternal_pct,maternal_lower,maternal_upper,non_maternal_pct,non_maternal_lower,non_maternal_upper
15-19,4,2,6,8,8,9
...
15-49,100,,,100,,
The 15-49 total prints no interval, so those cells are empty.
The parser¶
parse() in srs_mmr.py, in outline (pseudocode):
def parse(pages):
for page_no, lines in pages:
for line in lines:
tokens = mend PDFium splits, then split on spaces
label, values = leading words, trailing numbers and intervals
values shaped n (lo,hi) n p% -> Table 1: mmr, mmr_lower, mmr_upper, mortality_rate, lifetime_risk_pct
values shaped p% (lo,hi) p% (lo,hi) -> Table 2: maternal_pct, ..., non_maternal_upper
values shaped p% p% -> Table 2 total row
"SUBTOTAL" after a label-only line -> join them: "EAG AND ASSAM SUBTOTAL"
-> yield {"page", "row", "area", "column", "value"}
The parser picks the table from the shape of the values. Four engines read the first subtotal as two lines, EAG AND ASSAM and SUBTOTAL, and camelot as one cell; joining them gives every engine the same row key.
The checks¶
The estimate lies inside its 95% CI, for the MMR in Table 1 and for both proportions in Table 2.
Lifetime risk matches the Maternal Mortality Rate. The bulletin defines lifetime risk as 1 - (1 - rate/100000)^35. The rate is printed as a whole number, so the check allows the risk the formula gives for rate ± 0.5, plus half a unit in the last printed place. Kerala (rate 1, risk 0.05%) sits near the edge.
Both checks pass on every row.
The Cause of Death report¶
cod_top10.toml reads Table 2.3A of the companion SRS Cause of Death Statistics 2021-23 (page 31 of the PDF): the top 10 causes of death in India, as shares of deaths for males, females and persons.
pdfexorcist extract tests/fixtures/srs_mmr/srs_cod_2021-23_p31.pdf --recipe pdfexorcist/recipes/srs_maternal_mortality/cod_top10.toml -o cod.csv
rank,cause,male,female,person
1,Cardiovascular diseases,32.4,29.1,31.0
2,Respiratory infections,8.9,9.9,9.3
...
other,All Other Remaining Causes,17.6,15.2,16.6
The whole report gives the same output, every cell agreed. Rows are keyed by rank because causes 7 and 10 wrap onto a second line that only camelot joins; cause takes the majority reading, the first line. Each sex’s eleven shares must add up to 100 within rounding.
tests/test_srs_maternal_mortality.py compares all three tables with the expected*.csv files in tests/fixtures/srs_mmr/, checked against pdftotext -layout.