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:
- Define criteria - Provide variable filters with acceptable min/max ranges and importance weights.
- 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). - Calculate score - Weighted fraction of criteria met, scaled to 0-100:
- 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) |
- 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.
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
filtersexactly as given (interventionis 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);