Skip to content

Getting Started

This guide walks you through installing LEAF DSS locally, configuring the required environment, understanding the project layout, and making your first API calls against the live service.

Live deployment

The production instance runs at https://leaf-asrlm.in (hosted on Render, auto-deployed from the main branch). The API examples in this guide use that base URL so you can try them without a local setup. When running locally, swap https://leaf-asrlm.in for http://localhost:5000.


Prerequisites

  • Python 3.11 (the app is built and deployed on 3.11.0)
  • pip (included with Python)
  • Git for cloning the repository
  • Geospatial system libraries — GDAL / GEOS / PROJ. geopandas, fiona, and shapely need these native libraries present. On Debian/Ubuntu: apt-get install gdal-bin libgdal-dev libgeos-dev libproj-dev. On Windows, use the OSGeo4W stack or install via conda. (The provided Dockerfile bundles them.)
  • A Supabase Postgres database — the clustering module reads and writes all cluster state through Postgres. See Environment Variables.
  • An OpenAI API key — required for the AI recommendation feature (RAG).

A database is required

LEAF DSS is not a CSV-only app. The block-feasibility map is served from committed shapefiles/CSVs, but the cluster planner (the /api/clusters/* endpoints, the /clustering UI, and the /update ops console) stores every cluster in Supabase Postgres. Without a valid DATABASE_URL the clustering features and the background scheduler are inert.


Installation

# Clone the repository
git clone <repo-url>
cd IWMI_LEAF

# Install Flask app dependencies
cd leaf_flask
pip install -r requirements.txt

# Configure environment (see the next section)
cp .env.example .env
# then edit .env and fill in DATABASE_URL and OPENAI_API_KEY

# Run the development server
python app.py

The app starts at http://localhost:5000 with debug mode enabled. Hot-reload is active — code changes auto-restart the server.

Production run command

Render (and the Dockerfile) launch the app under gunicorn, not the Flask dev server:

gunicorn app:app --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --preload
Run this from inside leaf_flask/. The --preload flag runs the in-process scheduler once per boot; the two workers coordinate background jobs via a Postgres advisory lock.

MkDocs Documentation

To also serve this documentation site at /documentation/:

# From the project root (not leaf_flask/)
pip install mkdocs-material
mkdocs build          # generates leaf_flask/site/
python leaf_flask/app.py   # docs now served at /documentation/


Key URLs

URL Description
http://localhost:5000/ Main dashboard — interactive map with feasibility analysis
http://localhost:5000/clustering Cluster planner UI (village-cluster workbench)
http://localhost:5000/update Ops console — coverage refresh, config validation, AI-doc upload
http://localhost:5000/docs Swagger UI — interactive API explorer
http://localhost:5000/documentation/ MkDocs documentation site (this site)
http://localhost:5000/api API endpoint listing (JSON)
http://localhost:5000/health Health check endpoint

Project Structure

IWMI_LEAF/
├── leaf_flask/                 # Flask application (gunicorn target: app:app)
│   ├── app.py                  # App factory: registers blueprints, Swagger, scheduler
│   ├── blueprints/             # Route handlers, one module per API group
│   │   ├── pages.py            # Server-rendered page routes (/, /clustering, /about)
│   │   ├── levels.py           # /health, /documentation, level config, AI docs
│   │   ├── blocks.py           # /api/blocks*
│   │   ├── locations.py        # /api/locations, /api/districts*
│   │   ├── interventions.py    # /api/interventions, /api/variables*
│   │   ├── feasibility.py      # /api/calculate-feasibility, /api/ai-recommendation*
│   │   ├── villages.py         # /api/villages* (village master reads)
│   │   ├── clusters.py         # /api/clusters* (needs Postgres)
│   │   ├── infrastructure.py   # /api/infrastructure* (POIs)
│   │   ├── production_tool.py  # /api/production-tool* (dashboard feed)
│   │   ├── export.py           # /api/export/csv
│   │   ├── config.py           # /api/config, sheet validate/refresh
│   │   └── api_info.py         # /api index listing
│   ├── db.py                   # Postgres connection pool (DSN from DATABASE_URL)
│   ├── schema.sql              # Cluster tables (clusters, cluster_villages, …)
│   ├── migrations/             # Numbered SQL migrations (applied manually)
│   ├── clustering.py           # Greedy seed-and-grow algorithm (ALGO_VERSION)
│   ├── villages.py             # Village-master loading + cadre-edit protection
│   ├── google_sheets.py        # Published-CSV overlay (dss_input, block_values)
│   ├── scheduler.py            # In-process daemon: coverage sweep + nightly backup
│   ├── db_backup.py            # Nightly dump to Supabase Storage
│   ├── feasibility.py          # Block feasibility score calculations
│   ├── rag_utils.py            # AI/RAG recommendation engine (LangChain + OpenAI)
│   ├── config.py               # Colors, variable groups, map config
│   ├── data_utils.py           # Shapefile/CSV loading, GeoJSON conversion
│   ├── requirements.txt        # Python dependencies
│   ├── templates/              # Jinja2 page templates
│   ├── static/                 # css/, js/ (vanilla JS frontend), images/
│   └── data/                   # Committed base geodata + village master
│       ├── villages.csv        # Cluster planner source of truth (~21,495 rows)
│       ├── 4DSS_VAR_2.0.shp    # Block-level shapefile (feasibility variables)
│       ├── Block_assam.shp     # District-block mapping
│       ├── districts.geojson   # District boundaries
│       ├── protected_areas/    # Protected Areas of India shapefile
│       ├── DSS_input2.csv      # Intervention & variable config (Google Sheet fallback)
│       ├── block_values.csv    # Per-block variable overlay (Google Sheet fallback)
│       └── vectorstore/        # Chroma vector store (generated from ai-docs/)
├── ai-docs/                    # PDF policy documents for RAG
├── docs/                       # MkDocs source files
├── leaf_flask/site/            # MkDocs built output (generated)
├── mkdocs.yml                  # MkDocs configuration
└── render.yaml / Dockerfile    # Deployment definitions

What is and isn't in git

data/villages.csv and the base shapefiles are committed. The raw SHG survey workbook that villages.csv is built from is gitignored (contains PII). Live cluster state lives in Postgres, not in the repo — only the empty schema.sql and migrations are committed.


Environment Variables

Copy leaf_flask/.env.example to leaf_flask/.env and fill in the values below. On Render, these are set in the service dashboard (not in render.yaml).

Variable Required Purpose
DATABASE_URL Yes Supabase Postgres connection string (the direct/IPv6 DSN). Every cluster read/write and the background scheduler depend on it. Without it, clustering endpoints error and the scheduler is a no-op.
OPENAI_API_KEY Yes (for AI) OpenAI key used by the RAG recommendation engine. Required for /api/ai-recommendation*; rag_utils raises on import if unset.
SUPABASE_URL For backups Supabase project URL. Used only by the nightly DB backup (PostgREST/Storage over IPv4), independent of the Postgres path.
SUPABASE_SECRET_KEY For backups Supabase service key for the Storage backup bucket.
SUPABASE_PUBLISHABLE_KEY No Supabase anon/publishable key (reserved).
SECRET_KEY No Flask session secret. Defaults to a built-in dev value.
PORT No Server port (Render injects this). Defaults to 5000.
JIRA_EMAIL, JIRA_API_TOKEN No Optional Jira integration credentials.

No authentication

LEAF DSS has no user auth. Admin/destructive actions (refresh-all, imports, AI-doc upload) are gated only by a ?admin=1 query param or an X-Admin: 1 header. Do not expose the destructive endpoints publicly without adding a real auth layer. Rotate all secrets on handover — only .env.example should ever be committed.


First API Calls

These run against the live service. For a local instance, replace the base URL with http://localhost:5000.

Health check

curl https://leaf-asrlm.in/health
# → {"status": "healthy"}

List available interventions

curl https://leaf-asrlm.in/api/interventions
# → {"interventions": [{"key": "...", "name": "Organic Farming", ...}, ...]}

Get all blocks as GeoJSON

curl https://leaf-asrlm.in/api/blocks | python -m json.tool | head -20

Calculate feasibility for an intervention

curl -X POST https://leaf-asrlm.in/api/calculate-feasibility \
  -H "Content-Type: application/json" \
  -d '{"intervention": "Organic Farming"}'

List stored clusters for a block (requires the database)

# Reads cluster state from Postgres; auto-refreshes stale, unlocked scopes.
curl "https://leaf-asrlm.in/api/clusters?block=BEHALI&commodity=Goatery"

Get the default clustering parameters

curl https://leaf-asrlm.in/api/clusters/params
# → {"min_members_per_village": 6, "min_cluster_members": 30,
#    "max_cluster_members": 150, "max_radius_km": 5.0,
#    "emit_provisional": true, "commodities": ["Dairy", "Goatery", ...]}

Get protected areas overlay

curl https://leaf-asrlm.in/api/protected-areas/geojson | python -m json.tool | head -30
import requests

BASE = "https://leaf-asrlm.in"

# Feasibility for an intervention
r = requests.post(f"{BASE}/api/calculate-feasibility",
                  json={"intervention": "Organic Farming"})
r.raise_for_status()
print(r.json()["statistics"])

# Clusters for a block (Postgres-backed)
clusters = requests.get(f"{BASE}/api/clusters",
                        params={"block": "BEHALI", "commodity": "Goatery"}).json()
print(len(clusters), "clusters")
const BASE = "https://leaf-asrlm.in";

// Feasibility for an intervention
const res = await fetch(`${BASE}/api/calculate-feasibility`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ intervention: "Organic Farming" }),
});
const data = await res.json();
console.log(data.statistics);

// Clusters for a block (Postgres-backed)
const clusters = await fetch(
  `${BASE}/api/clusters?block=BEHALI&commodity=Goatery`
).then((r) => r.json());
console.log(clusters.length, "clusters");

Using the Dashboard

Assam is covered as 35 districts and 220 blocks.

1. Select an Intervention

Choose an intervention from the dropdown (e.g., "Organic Farming"). The map immediately colors all blocks by feasibility score.

2. Filter by District

Optionally select a district to zoom in and filter the distribution chart to that district's blocks only.

3. Configure Variables

Click Configure to open the modal. Adjust variable ranges (drag sliders), weights, and add/remove variables. Click Apply to recalculate.

4. Toggle Map Overlays

Check the Protected Areas checkbox above the map to overlay national parks, wildlife sanctuaries, and conservation reserves.

5. Explore Block Details

Click any block on the map to open its detail view. See all variable values grouped by category (Land & Agriculture, Water, Infrastructure, Livestock, People & Collectives), with out-of-range indicators.

6. Plan Clusters

Open a block's clustering view (/clustering/<block>) to see the village-level clusters generated by the seed-and-grow algorithm across the six livestock commodities (Dairy, Goatery, Piggery, Backyard Poultry, Duckery, Fishery Activity). This view reads and writes Postgres.

7. Get AI Recommendations

In the block detail view, click the robot icon to generate an AI-powered recommendation backed by policy document citations (requires OPENAI_API_KEY).

8. Export Data

Use the export controls to download the current block dataset (/api/export/csv) or the cluster coverage as row-per-village CSV (/api/clusters/export.csv).


Using the Swagger UI

  1. Navigate to https://leaf-asrlm.in/docs (or http://localhost:5000/docs).
  2. Endpoints are grouped by tag: Blocks, Locations, Interventions, Variables, Feasibility, AI, Export, Config, Clusters, Villages, Infrastructure.
  3. Click any endpoint to expand it and see parameters + response schema.
  4. Click Try it out to enable editing.
  5. Fill in parameters and click Execute to make a live request.
  6. View the response body, headers, and cURL command.

POST Endpoints

For POST endpoints (Feasibility, AI, Export, cluster import/regenerate), edit the JSON body in the text area before clicking Execute. The Swagger UI pre-fills a template based on the schema. Admin-gated POSTs need ?admin=1 appended.


Troubleshooting

Problem Solution
ModuleNotFoundError Run pip install -r leaf_flask/requirements.txt
geopandas/fiona import or shapefile-read errors Install the GDAL/GEOS/PROJ system libraries (see Prerequisites), or use the Dockerfile
Cluster endpoints return 500 / "database" errors DATABASE_URL is unset or unreachable — clustering requires Supabase Postgres
/api/clusters returns [] for a valid block Scope may not have been generated yet; the smart-refresh only fires for a given block, and locked/finalized scopes are served as-is
AI recommendations return 503 Set OPENAI_API_KEY, then POST /api/ai-recommendation/init to build the vector store
Background clustering never runs The in-process scheduler is a no-op without DATABASE_URL; there is no separate Render worker
Documentation 404 Run mkdocs build from the project root to generate leaf_flask/site/
Map shows no blocks Check that 4DSS_VAR_2.0.shp and its .dbf/.shx sidecars exist in leaf_flask/data/