krushimitravit's picture
Update app.py
d271986 verified
Raw
History Blame Contribute Delete
7.92 kB
from flask import Flask, render_template, request, jsonify, Response
import requests
from bs4 import BeautifulSoup
from flask import stream_with_context
from concurrent.futures import ThreadPoolExecutor, as_completed
app = Flask(__name__)
# Internal mapping of crops to pests (for the form)
CROP_TO_PESTS = {
"Sorgum": ["FallArmyWorm"],
"Maize": ["FallArmyWorm"],
"Rice": ["Blast", "GallMidge", "YSB", "PlantHopper", "BlueBeetle", "BacterialLeafBlight"],
"Cotton": ["Thrips", "Whitefly", "PinkBollworm", "Jassid", "BollRot", "AmericanBollworm"],
"Soybean": ["Girdlebeetle", "H.armigera", "Semilooper", "Spodoptera", "StemFLy"],
"Tur": ["Wilt", "Webbed_Leaves", "Pod_damage"],
"Sugarcane": ["FallArmyGrub", "WhiteGrub"],
"Gram": ["H.armigera", "Wilt"]
}
# Fixed year options for the form
YEARS = ["2024-25", "2023-24", "2022-23", "2021-22"]
# Map our internal crop names to the external page's crop values.
CROP_MAPPING = {
"Cotton": "1",
"Gram": "4",
"Maize": "7",
"Rice": "3",
"Sorghum": "6",
"Soybean": "2",
"Sugarcane": "8",
"Tur": "5",
"Sorgum": "6"
}
# Map our internal pest names to external page values per crop.
PEST_MAPPING = {
"Cotton": {
"FallArmyWorm": "71"
},
"Gram": {
"H.armigera": "72",
"Wilt": "73"
},
"Maize": {
"FallArmyWorm": "74"
},
"Rice": {
"Blast": "75",
"GallMidge": "76",
"YSB": "77",
"PlantHopper": "78",
"BlueBeetle": "79",
"BacterialLeafBlight": "80"
},
"Soybean": {
"Girdlebeetle": "81",
"H.armigera": "82",
"Semilooper": "83",
"Spodoptera": "84",
"StemFLy": "85"
},
"Tur": {
"Wilt": "86",
"Webbed_Leaves": "87",
"Pod_damage": "88"
},
"Sugarcane": {
"FallArmyGrub": "89",
"WhiteGrub": "90"
},
"Sorgum": {
"FallArmyWorm": "91"
}
}
# Parameter codes and labels for the final image URL
PARAMS = {
"Mint": "Min Temperature",
"Maxt": "Max Temperature",
"RH": "Relative Humidity",
"RF": "Rainfall",
"PR": "Pest Report"
}
def build_image_url(crop, year, pest, param, week):
"""Build the external ICAR image URL for a given combination."""
base = f"http://www.icar-crida.res.in:8080/naip/gisimages/{crop}/{year}/{pest}_"
return f"{base}{param}{week}.jpg"
def image_exists(url):
"""Return True if the remote image URL returns a valid image (HTTP 200 + image content-type)."""
try:
resp = requests.head(url, timeout=4, allow_redirects=True)
if resp.status_code == 200:
return resp.headers.get("Content-Type", "").startswith("image/")
# Some servers don't support HEAD — fall back to a streaming GET
if resp.status_code in (405, 403):
resp = requests.get(url, timeout=4, stream=True)
resp.close()
return resp.status_code == 200 and resp.headers.get("Content-Type", "").startswith("image/")
except Exception:
pass
return False
def week_has_any_data(week, crop, year, pest):
"""Return week string if any param image exists for it, else None."""
for param_code in PARAMS:
if image_exists(build_image_url(crop, year, pest, param_code, week)):
return week
return None
@app.route('/')
def index():
crop = request.args.get('crop', '')
pest = request.args.get('pest', '')
year = request.args.get('year', '')
week = request.args.get('week', '')
param = request.args.get('param', '')
image_url = ""
data_available = True
if crop and pest and year and week and param:
external_image_url = build_image_url(crop, year, pest, param, week)
if image_exists(external_image_url):
image_url = f"/proxy-image?url={external_image_url}"
else:
data_available = False
return render_template('index.html',
crops=list(CROP_TO_PESTS.keys()),
crop_to_pests=CROP_TO_PESTS,
years=YEARS,
params=PARAMS,
selected_crop=crop,
selected_pest=pest,
selected_year=year,
selected_week=week,
selected_param=param,
image_url=image_url,
data_available=data_available)
@app.route('/fetch_weeks')
def fetch_weeks():
"""
Return weeks that have at least one available param image.
Checks are run in parallel to stay well within the gunicorn timeout.
"""
crop = request.args.get('crop', '')
pest = request.args.get('pest', '')
year = request.args.get('year', '')
if not (crop and pest and year):
return jsonify({"weeks": []})
ext_crop = CROP_MAPPING.get(crop, '')
ext_pest = PEST_MAPPING.get(crop, {}).get(pest, '')
# Step 1: scrape candidate weeks from upstream (fast — single request)
candidate_weeks = []
try:
payload = {"country": ext_crop, "city": ext_pest, "sowing": year}
resp = requests.get(
"http://www.icar-crida.res.in:8080/naip/gismaps.jsp",
params=payload, timeout=8
)
soup = BeautifulSoup(resp.text, 'html.parser')
candidate_weeks = [
opt.get('value') for opt in soup.select('select[name="week"] option')
if opt.get('value') and "Select" not in opt.get('value', '')
]
except Exception:
pass
if not candidate_weeks:
candidate_weeks = [str(i) for i in range(1, 53)]
# Step 2: check all candidate weeks in parallel (max 20 threads, 4s per request)
available_weeks = []
with ThreadPoolExecutor(max_workers=20) as executor:
futures = {
executor.submit(week_has_any_data, week, crop, year, pest): week
for week in candidate_weeks
}
found = set()
for future in as_completed(futures):
result = future.result()
if result is not None:
found.add(result)
# Preserve original order
available_weeks = [w for w in candidate_weeks if w in found]
return jsonify({"weeks": available_weeks})
@app.route('/check_availability')
def check_availability():
"""
Check which params are available for a given crop/pest/year/week combination.
All 5 param checks run in parallel.
Returns a dict of {param_code: bool}.
"""
crop = request.args.get('crop', '')
pest = request.args.get('pest', '')
year = request.args.get('year', '')
week = request.args.get('week', '')
if not (crop and pest and year and week):
return jsonify({"availability": {}})
def check_param(param_code):
url = build_image_url(crop, year, pest, param_code, week)
return param_code, image_exists(url)
availability = {}
with ThreadPoolExecutor(max_workers=5) as executor:
for param_code, exists in executor.map(lambda p: check_param(p), PARAMS.keys()):
availability[param_code] = exists
return jsonify({"availability": availability})
@app.route('/proxy-image')
def proxy_image():
external_url = request.args.get('url')
if not external_url:
return "Missing URL", 400
try:
resp = requests.get(external_url, timeout=10, stream=True)
if resp.status_code != 200 or not resp.headers.get('Content-Type', '').startswith('image/'):
return "Image not available", 404
return Response(
stream_with_context(resp.iter_content(chunk_size=1024)),
mimetype=resp.headers.get('Content-Type', 'image/jpeg')
)
except Exception as e:
return str(e), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860, debug=False)