← All projects PathShala
PiSquareLabs · Public-sector decision support

PathShala

An investigation tool for district education officers in Himachal Pradesh: pick a closing school, compare receiving schools side by side, investigate each with agents, and only then choose one. The LLM never produces numbers or approvals.

BROWSER UIAGENT LAYERTOOLS · READ-ONLYDATAHash router — #/ · #/case/:id/compare · evidence · investigate · policy · report · #/mergesHash router#/ · #/case/:id/compare ·evidence · investigate ·policy · report · #/mergesui/pages + Leaflet map — GeoJSON polygons, no tiles; every step expandable to input/outputui/pages + Leaflet mapGeoJSON polygons, no tiles;every step expandable toinput/outputagent/runner.js — validates every output with Ajv, retries once, falls back to simulated, logs to case_logagent/runner.jsvalidates every output withAjv, retries once, fallsback to simulated, logs tocase_logagent/provider.js — SimulatedProvider (default) · GeminiProvider (stub) via VITE_AGENT_PROVIDERagent/provider.jsSimulatedProvider (default)· GeminiProvider (stub) viaVITE_AGENT_PROVIDER8 agents — { id, systemPrompt, tools, outputSchema, simulate(ctx) }8 agents{ id, systemPrompt, tools,outputSchema, simulate(ctx)}Gemini proxy — /api/agent holds the key; streams NDJSON tool_call / final; the browser runs each tool on sql.jsGemini proxy/api/agent holds the key;streams NDJSON tool_call /final; the browser runs eachtool on sql.jsagent/tools.js — 14 async JSON-in/JSON-out tools with Gemini function declarationsagent/tools.js14 async JSON-in/JSON-outtools with Gemini functiondeclarationsretrieval/bm25.js — BM25 over policy chunks (RTE Rules, Samagra Shiksha, HP decisions)retrieval/bm25.jsBM25 over policy chunks (RTERules, Samagra Shiksha, HPdecisions)engine/* — deterministic rules: checks, plan, questions, verdicts, merges, screeningengine/*deterministic rules: checks,plan, questions, verdicts,merges, screeningsql.js SQLite (WASM) — seed.sql at boot · saved to localStorage after every writesql.js SQLite (WASM)seed.sql at boot · saved tolocalStorage after everywritecase/findings.js — the ONLY writer: findings, evidence, status, approvalscase/findings.jsthe ONLY writer: findings,evidence, status, approvalseventscallbuildtool callsSELECT onlyruleswrites
UIAgent layerRead-only logicDatabaseOptional / not wired
Everything runs in the browser from one file. Agent outputs pass Ajv validation in the runner, then only the findings repository (case/findings.js) writes to the DB.
The decision

Evidence, not just distance and headcount

Consolidating a low-enrolment school affects children who then have to walk further, over terrain, to a school with enough seats. PathShala gives the officer a structured, referenced investigation — and a hard rule: where there is no data it says “Data unavailable” and nothing is invented.

0
specialist + coordinator agents
0
read-only tools
0
single self-contained index.html
0
servers required
System architecture

Browser-only, with a clean seam for Gemini

The diagram above is the whole system. Three design choices shape it:

◧

Database in the browser

SQLite compiled to WebAssembly (sql.js) loads seed.sql at boot and persists to localStorage (pathshala.db.v7) after every write. The key includes a seed fingerprint, so stale saves are discarded.

⛨

One writer

case/findings.js is the only module that writes cases, evidence and approvals. Tools are read-only (SELECT/WITH only; writes, multiple statements and unknown tables are rejected).

⇄

Provider seam

SimulatedProvider reproduces the prototype exactly. GeminiProvider builds the request already; a /api/agent proxy streams NDJSON tool calls the browser runs on sql.js.

Officer workflow

Choose last

Click a step. The final choice only appears after every option has been investigated.

Choose ONE closing school

Map click or the “needs review” list (≤30 students, or poor/unsafe building). Tick two or more receiving schools. Each candidate gets a 0–100 screening aid — never a recommendation.

score = 100 − 2×road_km − seat_shortfall_penalty − 0.1×(walk_min − 30) − 5×hazards

Side-by-side, nothing chosen yet

Distance and walking time, hazards on the route, free seats (classrooms × planning norm), teachers, building, facilities and citizen feedback — one column per candidate.

walk_min = Tobler(slope) × child_pace (rule R13) free seats = classrooms × max_per_room (R7, derived)

Every school gets its own track

Per-candidate evidence, agent findings, field questions and answers, and interventions. Switch with school tabs or Investigate all. Every step is logged to agent_steps with input/output.

Access Analyst → EvidenceBundle Community Analyst → EvidenceBundle Gap Finder → FieldQuestions Policy Researcher → Interventions

Totals of all options side by side

Questions answered, concerns confirmed, interventions, cost per year, one-time cost, first-year total. The officer picks last.

option qs concerns cost/yr one-time A 6/6 2 ₹… ₹… B 4/6 3 ₹… ₹…

Referenced, critiqued, approved by a person

Only the chosen school’s report is drafted — every sentence carries a reference — then critiqued, approved and submitted by the officer.

draft_report → sentences[{ text, refs[] }] Report Critic → refs real? numbers in evidence? officer approves → submit
Agents

Eight specialists, schema-checked outputs

Each agent exports { id, systemPrompt, tools, outputSchema, simulate(ctx) }. Prompts live in src/agent/prompts/*.md, schemas (JSON Schema 2020-12) in src/agent/schemas/*.json. The merge-side agents (planner, success monitor, survey builder) stay deterministic.

1

Coordinator

merges specialist evidence into findings

Findings

(calls specialists)

2

Access Analyst

students, routes, terrain, transport

EvidenceBundle

sql_query · school_profile · route_calc · gis_overlay · transport_lookup

3

Community Analyst

feedback themes, recurring concerns, field observations

EvidenceBundle

feedback_search · classify_feedback · recurring_concerns · field_observations

4

Gap Finder

evidence gaps → targeted field questions

FieldQuestions

evidence_gaps

5

Evidence Updater

field answers → evidence & finding statuses

EvidenceUpdate

apply_field_answers

6

Policy Researcher

policy chunks → interventions with costs

Interventions

policy_retrieve · cost_calc

7

Report Drafter

rationale, a reference on every sentence

Draft

get_case_evidence · draft_report

8

Report Critic

checks references, numbers, wording

Critique

get_case_evidence

Guardrails

The LLM never produces numbers or approvals

Enforced in code and tested with Playwright (tests/agents.spec.js).

CASE EVIDENCEREPORT DRAFTERREPORT CRITICOFFICER ONLYget_case_evidence — findings, evidence, field answers, cited chunksget_case_evidencefindings, evidence, fieldanswers, cited chunksdraft_report — sentences[{ text, refs }] — may only use evidence already in the casedraft_reportsentences[{ text, refs }] —may only use evidence alreadyin the caseCritique — every sentence has a real reference; every number appears in the evidenceCritiqueevery sentence has a realreference; every numberappears in the evidenceApprove & submit — no agent code writes cases.status or reports.approvedApprove & submitno agent code writescases.status orreports.approvedpassesfails → revise
DataAgentHuman
Costs come only from cost_calc (₹6,000 per child from the Samagra norm; ₹9 lakh per classroom from the HP PAB).

Validated output

Every agent output passes Ajv; on failure the runner retries once, then falls back to the simulated result and logs it.

Evidence-bound drafts

The Drafter may only use evidence already in the case; the Critic checks references and numbers.

Humans decide

Only the officer approves, selects an intervention, marks evidence verified, or submits.

Data audit

Every value has a source — or is labelled

Values in the demo may be synthetic, but every field must have a real source and gaps are never filled. Tests fail if removed fields come back.

ShownSourceStatus
School name, level, UDISE code, enrolment, teachers, classroomsUDISE+ Know Your Schoolreal for named merge schools, else mock
Seat capacityclassrooms × planning maximum (R7)derived
CoordinatesDatameet / India Data Portal UDISE exportapproximate
Road / walking distance & time, climbGoogle Routes + Elevation APIs; Tobler × child pacemock geometry (Tirthan valley); other pairs labelled straight-line
Bridges, steep paths, landslide zonesOpenStreetMap, JRC water, DEM, GSImock geometry
Citizen feedback (Hindi + English)synthesised from real complaint patternsmock
Field observations (bridge washed away, unsafe building)The Tribune reportsreal
Merge pairs, outcomes, precedentsHP merger list, Tribune, LiveLaw, Scroll, The Federal, The Wirereal
RTE Rules, Samagra Shiksha norm, HP PAB cost, HP merger criteriaofficial textsreal, quoted as stored
Bus timetable, “road closures”, per-habitation child countsno sourceremoved → “Data unavailable”
Stack & delivery

Vite 6Vanilla JS (ES modules)sql.js (SQLite WASM)LeafletAjvBM25PlaywrightPython seed pipelineFastAPI proxyDocker / nginxFirebase / GitHub Pages

Deploy anywhere: GitHub Pages serves the built single-file index.html (≈1 MB gzipped, no network requests except optional Google Fonts); or docker build (nginx, Cloud Run) or firebase deploy.

← PreviousMUDRA+ Next →GroundTruth