API reference

Engine — savitr.mlx_ocr

Generic MLX runtime for Surya OCR — load the model once, OCR page images to text.

This roll-agnostic engine turns a page image into the model’s raw text. The electoral-roll parsing of that text lives in savitr.rolls.

It is reusable for any Surya OCR task, not just rolls.

savitr.mlx_ocr.BASE_REPO = 'datalab-to/surya-ocr-2'

Upstream Surya weights. Not published in MLX form — converted locally, once.

savitr.mlx_ocr.BASE_PATHS = ('models/surya-mlx-4bit', '~/.cache/savitr/surya-mlx-4bit')

Locations searched for a converted base model, in order.

savitr.mlx_ocr.convert_hint(looked_in)[source]

Build the one message every entry point gives when there is no base model.

Parameters:

looked_in (list[str])

Return type:

str

savitr.mlx_ocr.base_model_path(path=None)[source]

Return a local MLX Surya directory, or say exactly how to make one.

Savitr publishes the terse roll model, while base Surya must be converted locally. This function prevents a missing local path from being mistaken for a Hub repository ID.

The library raises FileNotFoundError; CLI entry points turn it into a SystemExit with the same guidance.

Parameters:

path (str | None)

Return type:

str

class savitr.mlx_ocr.MLXSuryaOCR(mlx_path=None, max_tokens=8192, prompt='OCR this image to HTML.')[source]

Load an MLX-converted Surya model once; OCR page images to text.

With no mlx_path, this looks for a converted base Surya model. Pass savitr.rolls.resolve_terse_model() for the electoral-roll model.

Parameters:
  • mlx_path (str | None)

  • max_tokens (int)

  • prompt (str)

ocr_image(png_path)[source]

OCR one page image; return (text, generation_token_count).

Parameters:

png_path (str)

Return type:

tuple[str, int]

Electoral-roll parsing — savitr.rolls.parse

Convert Surya HTML or terse output to voter records.

The generic MLX engine turns page images into text. These functions parse that text into the canonical electoral-roll fields.

savitr.rolls.parse.TERSE_REPO = 'gojiberries/savitr'

Hugging Face repository for the distilled roll model.

savitr.rolls.parse.resolve_terse_model(local='models/surya-terse-8bit')[source]

Resolve a local model directory or download the pinned Hub snapshot.

Parameters:

local (str)

Return type:

str

savitr.rolls.parse.parse_voters(html)[source]

Split page HTML into voter records using the Name : anchor.

Parameters:

html (str)

Return type:

list[dict]

savitr.rolls.parse.dedupe_voters(voters)[source]

Collapse duplicated rows, keeping the fullest record per voter.

Keys by EPIC id when present, then serial number, then identity (name + relation + age). A serial prevents two legitimate voters with the same name and age from being collapsed. The fullest repeated record wins.

Parameters:

voters (list[dict])

Return type:

list[dict]

savitr.rolls.parse.TERSE_COLS = ['number', 'id', 'elector_name', 'relationship', 'father_or_husband_name', 'house_no', 'age', 'sex']

Canonical column order for one terse voter line.

savitr.rolls.parse.TERSE_PROMPT = 'Extract every voter from this electoral-roll page as pipe-delimited rows, one per line, columns: serial|epic|name|relation(F/H/M)|relation_name|house|age|sex'

Instruction given to the distilled model.

savitr.rolls.parse.to_terse(voters)[source]

Render voter dicts to terse pipe-delimited text (the training target).

Parameters:

voters (list[dict])

Return type:

str

savitr.rolls.parse.parse_terse(text)[source]

Parse the terse model’s output into voter dicts (value-anchored, not positional).

The model can drop the relation-code column. Anchoring the EPIC at the front and age and sex at the end keeps other fields aligned.

Parameters:

text (str)

Return type:

list[dict]

Pipeline — savitr.rolls.pipeline

Parse Manipur 2025 rolls into the canonical voter CSV.

The pipeline OCRs each page with MLX Surya, parses voter records, and applies the in-rolls column schema.

Use savitr parse-rolls --help for command-line options.

savitr.rolls.pipeline.html_to_text(html)[source]

Convert HTML to newline-separated text for cover-field parsing.

Parameters:

html (str)

Return type:

str

savitr.rolls.pipeline.parse_pdf_mlx(eng, pdf_path, dpi, terse=True, cover_eng=None)[source]

OCR + parse one PDF with MLX; return (rows, recon) in the canonical schema.

In terse mode, eng reads voters from every page. If cover_eng is provided, it reads the first two pages for metadata.

In HTML mode, eng reads both voters and metadata.

Parameters:
Return type:

tuple[list[dict], dict]

savitr.rolls.pipeline.main()[source]

Run the savitr parse-rolls command: roll PDFs -> canonical voter CSV.

Return type:

int

Schema — savitr.rolls.schema

Canonical in-rolls voter CSV schema (vendored from parse_unsearchable_rolls).

The output stays compatible with the parse_unsearchable_rolls pipeline.

savitr.rolls.schema.ac_part_from_filename(name)[source]

AC01_part001_final_ENG.pdf -> (‘1’, ‘1’).

Parameters:

name (str)

Return type:

tuple[str, str]