API¶
Rajasthan land records (jamabandi) via Bhu-Naksha.
Khatedar name, father, jati and residence per plot.
Thin client for the Rajasthan Bhu-Naksha portal.
The portal is the map front-end to the state’s land records. Its REST layer, unlike the Apna Khata nakal page, asks for no login, no OTP and no captcha: the “captcha” on the map’s Nakal button is generated and checked in browser JavaScript and is never sent to the server.
Two calls matter.
ListsAfterLevel walks a six-level tree: district, tehsil, RI circle,
halka, village, sheet. Codes are passed back as a comma-separated string with
a trailing comma, exactly as the page builds it; without the trailing comma
the call returns empty lists.
getPlotInfo takes a giscode, which is the six level codes concatenated
(01 + 002 + 0745 + 02920 + 11035 + 001), and a plot
number, and returns one JSON object whose info field holds the khata
number and one line per co-owner: name, father or husband, share, जाति-
and residence. A plot that does not exist answers with an empty body and
HTTP 200 (occasionally 204), not an error, so “miss” is a first-class
outcome here.
- exception rajasthan_ror.portal.PortalError[source]¶
The portal answered, but not with what the call asked for.
- class rajasthan_ror.portal.Level(code, name, has_data)[source]¶
One entry in the portal’s location tree.
- rajasthan_ror.portal.with_connection_backoff(call, *, what, sleep=<built-in function sleep>, total=7200.0)[source]¶
Run
calluntil it stops failing at the connection level.Name resolution, connect and read failures are retried with a delay that doubles from
CONNECT_BACKOFF_STARTtoCONNECT_BACKOFF_CAPfor up tototalseconds. Anything the portal actually answers is not a connection failure and is returned to the caller as is.- Parameters:
- Returns:
Whatever
callreturns once it succeeds.- Raises:
PortalError – When
totalseconds of retrying did not get through.- Return type:
T
- class rajasthan_ror.portal.Session(pause=0.2, timeout=60.0, retries=4)[source]¶
One HTTP session against the portal, with bounded retries.
The portal has shown no 429s, but latency swings from under a second to several, and a crawl that hammers it is a crawl that gets blocked. A connection-level failure is waited out (see
with_connection_backoff()); an answer the portal gives that is not what was asked for is retriedretriestimes and then raised.- children(codes)[source]¶
List the entries one level below
codes.- Parameters:
codes (list[str]) – Level codes from the district down; empty for the districts themselves.
- Returns:
The child entries in the portal’s order.
- Raises:
ValueError – When
codesalready reaches the sheet level.PortalError – When the portal’s answer is not a non-empty list.
- Return type:
- plot_info(giscode, plotno)[source]¶
Fetch the record for one plot.
- Parameters:
- Returns:
The portal’s JSON object, or
Nonewhen the portal says there is no such plot.- Raises:
PortalError – When the body is neither empty nor JSON.
- Return type:
- rajasthan_ror.portal.giscode(codes)[source]¶
Build the sheet key
getPlotInfowants from the six level codes.- Parameters:
codes (list[str]) – District, tehsil, RI circle, halka, village and sheet codes.
- Returns:
The codes concatenated in that order.
- Raises:
ValueError – When there are not exactly six codes.
- Return type:
Enumerate the portal’s location tree down to the sheet, one row per sheet.
Six levels: district, tehsil, RI circle, halka, village, sheet. The sheet is
the unit getPlotInfo is keyed on, so it is the unit the crawl is keyed on.
Villages typically have one sheet; a village listed twice, as (गत) past
and (चालु) current, carries one code and is folded to the current row.
Checkpointed per district as gzipped JSONL under raw/locations/ so an
interrupted run resumes at the district it was in, and folded into
raw/villages.parquet at the end. raw/ is under the current working
directory; see rajasthan_ror.paths.
- Usage:
uv run rajasthan-ror-list [–districts 01,21] [–workers 4]
- rajasthan_ror.locations.walk(session, codes, names, sheets)[source]¶
Descend from
codesto the sheets beneath it, appending one row each.- Parameters:
- Return type:
None
- rajasthan_ror.locations.district_file(code)[source]¶
Checkpoint file for one district.
- Parameters:
code (str) – District code as the portal gives it.
- Returns:
Path of the gzipped JSONL under
raw/locations/.- Return type:
Path
- rajasthan_ror.locations.list_district(code, name)[source]¶
List every sheet in one district and checkpoint the result.
- rajasthan_ror.locations.fold()[source]¶
Combine the district checkpoints into
raw/villages.parquet.- Returns:
One row per sheet, past/current village pairs folded to the current row.
- Return type:
DataFrame
- rajasthan_ror.locations.main()[source]¶
Command-line entry point: list districts in parallel, then fold.
- Return type:
None
Fetch every plot record for every sheet in raw/villages.parquet.
The portal has no call that lists the plots on a sheet; the map finds them by
click. So the crawl walks plot numbers upward from 1. Khasra numbers are
dense integers, but subdivided khasras are written 1256/287 on the nakal
and the portal has not answered to that form under any spelling tried, so
what this collects is the integer-numbered plots. The parse records which
numbers were tried.
Where a sheet ends is found by probing, not by a long run of misses. Over 68
finished Nagaur sheets the largest gap inside a sheet’s numbering had median
3 and maximum 57, so a plain miss run had to be 60 to be safe and cost 60
requests on every sheet, 16% of all requests. Instead, after --probe-after
consecutive misses the crawl probes ahead at doubling offsets (20, 40, 80,
160, 320, 640 past the current number). A hit resumes the walk; six misses
end the sheet. That is 26 requests where 60 were, and it reaches 660 numbers
past the last hit where the old rule reached 60.
Every hit carries ownerplots, the other plot numbers on the same khata.
Those share the owner block by construction, so they are recorded as
via the plot that named them rather than fetched, which cuts the request
count from the number of plots to roughly the number of khatas plus the
misses. The parse expands them back to one row per plot.
Checkpointing is per sheet, appended to raw/plots/<giscode>.jsonl.gz, one
line per plot number tried: ok true with the portal’s JSON, ok false
with a reason. A sheet whose file ends with a done line is skipped on the
next run; one that does not replays saved answers and fetches missing numbers.
raw/ is under the current working directory; see
rajasthan_ror.paths.
- Usage:
uv run rajasthan-ror-fetch –districts 21 –workers 8 uv run rajasthan-ror-fetch –giscodes 0100207450292011035001
- rajasthan_ror.plots.trim_truncated(path)[source]¶
Cut a checkpoint with a corrupt tail back to its readable prefix.
gzip readers stop at the first damaged member, so appending to a file a crash cut short would hide every later record from the parser. The original bytes are backed up before the file is rewritten with its readable lines; the crawl then refetches from there.
- rajasthan_ror.plots.read_progress(path)[source]¶
Recover where a sheet’s crawl got to from its checkpoint file.
Tolerates a truncated tail from a killed worker: the readable prefix is used and the rest is refetched.
trim_truncatedshould run first so the tail is not appended after.- Parameters:
path (Path) – The sheet’s checkpoint file; may not exist yet.
- Returns:
Highest plot number tried, whether the sheet is finished, the number of hits so far, and the plot numbers already known via
ownerplots, each mapped to the plot that named it.- Return type:
- rajasthan_ror.plots.integer_plots(ownerplots)[source]¶
Plot numbers the walk could reach, i.e. plain integers.
Slashed (subdivided) numbers are dropped: the portal does not answer to them. The portal sends
ownerplotssometimes as a list and sometimes as its string repr, so both shapes are read.
- rajasthan_ror.plots.crawl_sheet(session, giscode, probe_after, max_plot)[source]¶
Walk one sheet’s plot numbers upward, probing ahead to find its end.
A portal failure is checkpointed before it propagates to the caller.
- Parameters:
- Returns:
Requests made and plots found (including ones already known from a previous run).
- Return type:
- rajasthan_ror.plots.run()[source]¶
Crawl the queued sheets with a worker pool.
- Raises:
SystemExit – With status 1 if any sheet failed, allowing a supervisor to retry the unfinished pass.
- Return type:
None
- rajasthan_ror.plots.main()[source]¶
Run one fetcher at a time so checkpoint writers cannot overlap.
- Raises:
SystemExit – With status 1 for unfinished work or 2 for an active writer.
- Return type:
None
Split fetched plot records into one row per khatedar.
Reads only raw/plots/ and writes raw/owners.parquet (both under the
current working directory; see rajasthan_ror.paths), so a parser
change never costs a refetch.
The info field is a short free-text block:
क्षेत्रफल : 0.0600 Hectare खाता संख्या : 847 1.) गुलाब सिंह चीता पुत्र अमर सिंह हिस्सा- 2/3 जाति- मेर(मेहरात, चीता) सा. अजयसर खातेदार 2.) सुगरा पत्नि खंगार सिंह हिस्सा- 1/3 जाति- मेर(मेहरात, चीता) सा देह खातेदार
Each numbered entry is one co-owner: a name, then a relation word (पुत्र
son of, पत्नि wife of, …) and the relative, then हिस्सा- the share,
जाति- the caste, a residence introduced by सा. (sakin, resident
of; देह means this very village) or निवासी or an urban address block
starting मकान संख्या-, and a closing tenure word such as खातेदार.
The parse is a marker scan, not one whole-line regex, for the reason the Odisha parser gives: a single regex silently drops every shape it did not anticipate, and the shapes here already include institutional owners with no relation and no caste, a caste with a parenthesised, comma-bearing gloss, and a residence that is an address rather than a village. Every row keeps the raw line so a parse can be audited against its source. An entry can wrap across embedded newlines; its continuation belongs to the same owner.
- Usage:
uv run rajasthan-ror-parse
- rajasthan_ror.parse.split_owner(line)[source]¶
Split one numbered owner block into fields.
A missing marker yields
Nonefor that field, never a dropped row.- Parameters:
line (str) – One
N.) ...entry, including any embedded line breaks.- Returns:
owner_seq,raw_line,name,relation,relative,share,jati,residenceandtenure.- Raises:
ValueError – When the line does not start with an owner number.
- Return type:
- rajasthan_ror.parse.split_info(info)[source]¶
Split an
infoblock into area, khata number and owner rows.- Parameters:
info (str) – The free-text block from a plot record.
- Returns:
area_ha(float orNone),khata(str orNone) andowners, onesplit_owner()dict per numbered line or explicit unnumbered government owner (whoseowner_seqisNone).- Return type:
- rajasthan_ror.parse.rows_from_record(record, by_plot=None)[source]¶
Owner rows for one fetched record.
A
viarecord, one known only from another plot’sownerplots, borrows that plot’s data fromby_plot.- Parameters:
- Returns:
One row per co-owner, each carrying the plot’s giscode, plot number, khata, area, owner count and
viasource; empty for a miss.- Return type:
- rajasthan_ror.parse.main()[source]¶
Command-line entry point: parse every sheet into
raw/owners.parquet.- Return type:
None
Map a jati string as the patwari wrote it to a Rajasthan schedule category.
The schedules (schedules/rajasthan_schedules.json, shipped with the
package) are in Roman script, as the Social Justice department publishes
them; the records are in Devanagari, spelled by hand. Matching goes through a
phonetic key: Devanagari is transliterated (Harvard-Kyoto), the inherent
vowel and aspiration are dropped, long and short vowels merged, w and
v merged, doubled letters collapsed. मेघवाल and Meghwal both key
to megvl; जाट and Jat to jt; Jatia stays jti.
A record’s caste field can name several communities at once
(मेर(मेहरात काठात,मेहरात-घोड़ात, चीता)) or carry a religion
(लोहार मुसलमान). The string is split on brackets, commas and hyphens and
every piece is tried; the first schedule hit wins, in the order SC, ST, MBC,
OBC, because a community that appears in two lists is in the earlier one by
a narrower definition (Dholi Bhil is ST, Dholi is SC). A string that hits no
schedule is labelled unlisted, which is where Rajput, Brahmin, Bania and
Jain sit, and is not the same as General: an unlisted string may be a
misspelling the key did not bridge.
Usage as a library:
categorise("मेघवाल") -> ("SC", "Megh, Meghval, Meghwal, Menghvar", "exact").
- rajasthan_ror.categorise.key(text)[source]¶
Phonetic key shared by a Devanagari spelling and its Roman schedule entry.
The inherent vowel
agoes (it is what Roman spellings drop:jATaandJat); the other vowels stay, with long and short merged, because without themJatandJatiaorBhilandBalaicollide.
- rajasthan_ror.categorise.schedule_entries()[source]¶
Load and validate the packaged schedules.
- Returns:
Every synonym row, in file order.
- Raises:
ValueError – When the file’s schema version is not
SCHEMA_VERSION, an entry fails_validated_entry(), or a(category, entry_no, synonym)triple repeats.- Return type:
tuple[ScheduleEntry, …]
- rajasthan_ror.categorise.pieces(jati)[source]¶
Split a caste string into the community names it carries.
- rajasthan_ror.categorise.categorise(jati, fuzzy_cutoff=0.9)[source]¶
Look up one caste string in the schedules.
- Parameters:
jati (str | None) – The caste field as written;
Noneor blank is unlisted.fuzzy_cutoff (float) –
difflib.get_close_matches()ratio a key must reach to count as a near miss. Keys shorter than five letters are never fuzzy-matched.
- Returns:
Category (
SC,ST,MBC,OBCorunlisted), the schedule entry matched orNone, and how:exactfor a key match,fuzzyfor a close key,nonewhen nothing in the schedules is near it.- Return type:
Where the crawl keeps its files.
Everything lives under raw/ in the current working directory, not next
to the package: the package is installed into a virtualenv, while the crawl
is run from the repository root (crawl.sh does cd there first) and
its checkpoints must survive reinstalls. Run the console scripts from the
directory that should hold raw/.