Skip to content

Feasibility API

Calculate weighted feasibility scores for agricultural interventions across Assam's blocks. This is the core analytical engine of LEAF DSS - it evaluates how suitable each block is for a given intervention based on multiple variable criteria and returns a scored GeoJSON for map display.


How Feasibility Scoring Works

The scoring algorithm (feasibility.py) works as follows:

  1. Define criteria - Provide variable filters with acceptable min/max ranges and importance weights.
  2. Evaluate each block - For each block, check whether each variable value falls within its [min_val, max_val] range. A variable with no data for that block is excluded from both the numerator and the denominator (it neither helps nor hurts the score).
  3. Calculate score - Weighted fraction of criteria met, scaled to 0-100:
score = Σ (in_range × weight) / Σ (weight over variables with data) × 100
  1. Classify - The numeric score is bucketed into a class:
Score Class key Label Color
= 100 very_high 100% #1b5e20 (dark green)
75 – <100 high 75-100% #81c784 (green)
50 – <75 moderate_high 50-75% #c5e1a5 (light green)
25 – <50 moderate 25-50% #ffd700 (gold)
1 – <25 low 1-25% #ff8c00 (orange)
< 1 very_low 0% #ff0000 (red)
no data no_data No Data #E0E0E0 (grey)
  1. Return - GeoJSON with the score embedded as properties on every block, plus distribution and summary statistics.

Class thresholds are inclusive on the lower bound

The classification is a simple >= cascade in code (classify_feasibility). A block scoring exactly 75.0 is high; a block scoring exactly 50.0 is moderate_high. Only an exact 100 is very_high.


Calculate Block Feasibility

Calculates feasibility scores for all 220 blocks in Assam based on the provided filters, and returns them as a scored GeoJSON. Statistics can optionally be scoped to a single district.

POST /api/calculate-feasibility

Request Body

{
  "intervention": "Organic Farming",
  "filters": [
    {
      "column": "AD",
      "min_val": 20,
      "max_val": 60,
      "weight": 1.0
    },
    {
      "column": "WA",
      "min_val": 30,
      "max_val": 70,
      "weight": 0.8
    }
  ],
  "district": "Tinsukia"
}
Field Type Required Description
intervention string No Intervention name. Used only when filters is empty - the server loads this intervention's default variable config and uses it as the filters.
filters array No Custom filter criteria. If present, these are used verbatim and intervention is ignored.
filters[].column string Yes Variable column name (e.g. AD, WA). Also accepts the alias field.
filters[].min_val number No Minimum acceptable value. Also accepts range_min. Missing/null → treated as -∞.
filters[].max_val number No Maximum acceptable value. Also accepts range_max. Missing/null → treated as +∞.
filters[].weight number No Importance weight. Also accepts any positive number; default 1.
district string No District name (e.g. Tinsukia) or numeric district ID. Scopes the returned statistics/distribution to that district only - the geojson still contains all blocks.

Intervention vs Filters

  • Filters provided: the server uses your filters exactly as given (intervention is ignored).
  • Intervention only (no filters): the server looks up the intervention's pre-configured variables and uses them as filters.
  • Neither: no criteria → every block scores as No Data.

Response

200 OK

{
  "geojson": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "properties": {
          "Block_name": "Digboi",
          "Dist_Name": "Tinsukia",
          "AD": 45.2,
          "WA": 35.1,
          "feasibility": 75.0,
          "feasibility_class": "high",
          "feasibility_label": "75-100%",
          "feasibility_color": "#81c784"
        },
        "geometry": { "type": "Polygon", "coordinates": [[]] }
      }
    ]
  },
  "statistics": {
    "total_blocks": 220,
    "blocks_with_data": 205,
    "blocks_no_data": 15,
    "mean": 52.3,
    "median": 50.0,
    "min": 0.0,
    "max": 100.0,
    "high_feasibility": 40,
    "low_feasibility": 30,
    "distribution": {
      "100%": 12,
      "75-100%": 33,
      "50-75%": 28,
      "25-50%": 35,
      "1-25%": 25,
      "0%": 19,
      "No Data": 15
    },
    "variable_stats": {
      "AD": { "min": 5.0, "max": 90.0, "mean": 45.2 },
      "WA": { "min": 10.0, "max": 80.0, "mean": 40.1 }
    }
  }
}
Field Type Description
geojson object GeoJSON FeatureCollection with all blocks and their feasibility properties.
geojson.features[].properties.feasibility number | null Numeric score 0-100, or null when the block has no data for the criteria.
geojson.features[].properties.feasibility_class string Class key (very_high, high, moderate_high, moderate, low, very_low, no_data).
geojson.features[].properties.feasibility_label string Human-readable class label (75-100%, etc.).
geojson.features[].properties.feasibility_color string Hex color for map display.
statistics.total_blocks number Blocks in scope (all, or the selected district).
statistics.blocks_with_data / blocks_no_data number Blocks with / without a computable score.
statistics.mean / median / min / max number Summary of the computable scores.
statistics.high_feasibility number Count of blocks scoring >= 75.
statistics.low_feasibility number Count of blocks scoring < 25.
statistics.distribution object Count per class label (always includes all seven labels).
statistics.variable_stats object Per-variable min/max/mean across in-scope blocks.

No filters_applied echo

The response does not include a filters_applied field, nor a mean_score / *_count shape. Read statistics.mean and statistics.distribution instead.

Errors

Code Description
500 Server error. Response body: { "error": "<message>" }. Raised for a malformed request body or any calculation failure.

Example

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

# Using custom filters
curl -X POST https://leaf-asrlm.in/api/calculate-feasibility \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      {"column": "AD", "min_val": 20, "max_val": 60, "weight": 1.0},
      {"column": "WA", "min_val": 30, "max_val": 70, "weight": 0.8}
    ]
  }'
import requests

# Calculate feasibility with custom filters
response = requests.post(
    "https://leaf-asrlm.in/api/calculate-feasibility",
    json={
        "filters": [
            {"column": "AD", "min_val": 20, "max_val": 60, "weight": 1.0},
            {"column": "WA", "min_val": 30, "max_val": 70, "weight": 0.8}
        ]
    }
)
result = response.json()

stats = result["statistics"]
print(f"Mean feasibility: {stats['mean']:.1f}")
print(f"High-feasibility blocks (>=75): {stats['high_feasibility']}")

# Find top-scoring blocks
features = result["geojson"]["features"]
top = sorted(
    features,
    key=lambda f: f["properties"].get("feasibility") or 0,
    reverse=True,
)
for f in top[:5]:
    p = f["properties"]
    print(f"  {p['Block_name']}: {p['feasibility']} ({p['feasibility_label']})")
const response = await fetch('/api/calculate-feasibility', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    intervention: 'Organic Farming'
  })
});
const result = await response.json();

console.log(`Mean score: ${result.statistics.mean}`);
// Add scored GeoJSON to the map
L.geoJSON(result.geojson, {
  style: feature => ({
    fillColor: feature.properties.feasibility_color,
    fillOpacity: 0.7
  })
}).addTo(map);