From 943780b8b1952a1207f25b01629440c237ad9a12 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Mon, 25 May 2026 16:27:37 +0530 Subject: [PATCH 01/12] Fix : Updated the Colors in Change Detection --- templates/mws-report.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/mws-report.html b/templates/mws-report.html index b5cb7647..52568eba 100644 --- a/templates/mws-report.html +++ b/templates/mws-report.html @@ -1447,7 +1447,7 @@

Degradation of land

-
+
Crops - Crops
@@ -1459,7 +1459,7 @@

Degradation of land

Crops - Barren
-
+
Crops - Shrubs and Scrubs
From ce620cd50bff6a8d854bd903ba2c1e83a2226de1 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Wed, 3 Jun 2026 17:42:47 +0530 Subject: [PATCH 02/12] Chore : Working on Village Report --- dpr/api.py | 173 +- dpr/gen_village_report.py | 2963 ++++++++++++++++ templates/global_village_fetcher.js | 114 + templates/mws-report.html | 26 +- templates/resource-report.html | 10 +- templates/village-report.html | 4977 ++++++++++++++++++++++++--- 6 files changed, 7812 insertions(+), 451 deletions(-) create mode 100644 dpr/gen_village_report.py create mode 100644 templates/global_village_fetcher.js diff --git a/dpr/api.py b/dpr/api.py index 87911e34..286ea3f2 100644 --- a/dpr/api.py +++ b/dpr/api.py @@ -81,6 +81,26 @@ get_fishery_water_potential_data, get_agroforestry_transition_data, ) +from .gen_village_report import ( + get_village_polygon_and_info, + get_development_data, + get_block_development_data, + calculate_demographics, + get_basic_infrastructure, + get_health_and_wash, + get_education_institutions, + get_financial_inclusion, + get_welfare_inclusion, + get_community_institutes, + get_livelihood_diversification, + get_livestock_management, + get_irrigation_Infra, + get_agri_support_service, + get_ecological_climate_resiliance, + get_all_villages_basic_infrastructure, + get_all_villages_education_institutions, + get_mwses_boundaries, +) from .gen_report_download import render_pdf_with_firefox from .utils import validate_email, transform_name from .tasks import generate_dpr_task @@ -563,29 +583,146 @@ def generate_tehsil_report(request): @api_view(["GET"]) @auth_free @schema(None) -@api_security_check(auth_type="Auth_free") def generate_village_report(request): - try: - # ? district, block, villageId - params = request.GET - result = {} + """ + Generate comprehensive village report with all sections. + """ + state = request.GET.get('state') + district = request.GET.get('district') + block = request.GET.get('block') + village_id = request.GET.get('villageId') + + # Get village polygon and info from GeoServer + village_data = get_village_polygon_and_info(state, district, block, village_id) + + # Get Development Scores + development_score = get_development_data(state, district, block, village_id) + block_development_score = get_block_development_data(state, district, block) + + # Calculate demographic data with percentages + demographic_data = calculate_demographics(village_data['properties']) + + # Calculate Basic Infra + basic_infra_data = get_basic_infrastructure(state, district, block, village_id) + # Convert scores to performance categories + basic_infra_performance = [ + 'Low' if score <= 0.33 else 'High' if score > 0.66 else 'Medium' + for score in basic_infra_data + ] + + # Calculate Health and Wash + health_wash_data = get_health_and_wash(state, district, block, village_id) + health_wash_performance = [ + # Maternal & Child Health (High / Low only) + "High" if health_wash_data[0] > 0.66 else "Low", + + # Water & Sanitation (High / Medium / Low) + ( + "Low" + if health_wash_data[1] <= 0.33 + else "High" + if health_wash_data[1] > 0.66 + else "Medium" + ) + ] - for key, value in params.items(): - result[key] = value + #Calculate Education Institutions + education_data = get_education_institutions(state, district, block, village_id) + + # Calculate Financial Inclusion + finance_data = get_financial_inclusion(state, district, block, village_id) + + #Calculate Welfare + welfare_data = get_welfare_inclusion(state, district, block, village_id) + + #Calculate Community Institutions + community_data = get_community_institutes(state, district, block, village_id) + + #Livelihood Diversification + livelihood_data = get_livelihood_diversification(state, district, block, village_id) + + #Livestock Management + livestock_data = get_livestock_management(state, district, block, village_id) + + #Irrigation data + irrigation_data = get_irrigation_Infra(state, district, block, village_id) + + #Agriculture Support + agri_support_data = get_agri_support_service(state, district, block, village_id) + + #Climate Resiliance + climate_resiliance_data = get_ecological_climate_resiliance(state, district, block, village_id) + + + #Map Data + basic_infra_map = get_all_villages_basic_infrastructure(state, district, block) + education_map = get_all_villages_education_institutions(state, district, block) + mwses_data = get_mwses_boundaries(state, district, block, village_id) + + + # Build context for template + context = { + # Location info + "state": state, + "district": district, + "block": block, + "village_id": village_id, + "village_name": village_data['village_name'], + "gram_panchayat_name": village_data['gram_panchayat_name'], + "area_hectares": village_data['area_hectares'], + "village_polygon": json.dumps(village_data['village_polygon']), - context = { - "state": result["state"], - "district": result["district"], - "block": result["block"], - "village_id" : result["villageId"], - "development_scores": json.dumps([0.85, 0.72, 0.65, 0.78, 0.82, 0.75, 0.68, 0.80, 0.75, 0.70]) # Serialize to JSON string - } + # RADAR CHART DATA + "village_scores": json.dumps(development_score), + "block_average_scores": json.dumps(block_development_score), + + # DEMOGRAPHIC DATA + "demographic_data": demographic_data, - return render(request, "village-report.html", context) + # BASIC INFRASTRUCTURE DATA + "basic_infra_data" : json.dumps(basic_infra_data), + "basic_infra_performance": basic_infra_performance, + + # HEALTH AND WASH DATA + "health_wash_data" : json.dumps(health_wash_data), + "health_wash_performance" : health_wash_performance, + + #Education Data + "education_data" : json.dumps(education_data), + + #Finance Data + "finance_data" : json.dumps(finance_data), + + #Welfare Inclusion + "welfare_data" : json.dumps(welfare_data), + + #Community Institutions + "community_data" : json.dumps(community_data), + + #Livelihood Diversification + "livelihood_data" : json.dumps(livelihood_data), + + #Livestock Management + "livestock_data" : json.dumps(livestock_data), + + #Irrigation Data + "irrigation_data" : json.dumps(irrigation_data), + + #Agri Support Data + "agri_support_data" : json.dumps(agri_support_data), + + #Climate Data + "climate_resiliance_data" : json.dumps(climate_resiliance_data), + + "village_polygon": json.dumps(village_data['village_polygon']), + "mwses_polygon": json.dumps(mwses_data['polygon']), + + "basic_infra_map" : json.dumps(basic_infra_map), + "education_map" : json.dumps(education_map) + } + + return render(request, 'village-report.html', context) - except Exception as e: - logger.exception("Exception in generate_village_report api :: ", e) - return render(request, "error-page.html", {}) # --------------------------------------------------------------------------- # DPR Data API diff --git a/dpr/gen_village_report.py b/dpr/gen_village_report.py new file mode 100644 index 00000000..cf6e625d --- /dev/null +++ b/dpr/gen_village_report.py @@ -0,0 +1,2963 @@ +import re +import requests +import geopandas as gpd +import pandas as pd +import numpy as np +import pymannkendall as mk +import json +import ast +from shapely.geometry import shape, mapping +from shapely.wkt import dumps as shapely_to_wkt +from shapely.ops import unary_union + +from nrm_app.settings import EXCEL_DIR, GEOSERVER_URL, OVERPASS_URL +from utilities.logger import setup_logger + +logger = setup_logger(__name__) + +DATA_DIR_TEMP = EXCEL_DIR + + +# ? MARK: HELPER FUNCTIONS +def get_geojson(workspace, layer_name): + """Construct the GeoServer WFS request URL for fetching GeoJSON data.""" + geojson_url = f"{GEOSERVER_URL}/{workspace}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName={workspace}:{layer_name}&outputFormat=application/json" + return geojson_url + +def calculate_demographics(properties): + """ + Calculate demographic metrics and percentages from village properties. + """ + + # Extract base values + tot_p = properties.get('TOT_P', 0) # Total Population + p_lit = properties.get('P_LIT', 0) # Total Literate + p_sc = properties.get('P_SC', 0) # Total SC Population + p_st = properties.get('P_ST', 0) # Total ST Population + + # Calculate percentages (avoid division by zero) + literacy_percentage = round((p_lit / tot_p * 100), 2) if tot_p > 0 else 0 + sc_percentage = round((p_sc / tot_p * 100), 2) if tot_p > 0 else 0 + st_percentage = round((p_st / tot_p * 100), 2) if tot_p > 0 else 0 + + # Build demographic data dictionary + demographic_data = { + # Population + 'TOT_P': properties.get('TOT_P', 0), + 'TOT_M': properties.get('TOT_M', 0), + 'TOT_F': properties.get('TOT_F', 0), + 'No_HH': properties.get('No_HH', 0), + + # SC Population with percentage + 'P_SC': properties.get('P_SC', 0), + 'M_SC': properties.get('M_SC', 0), + 'F_SC': properties.get('F_SC', 0), + 'sc_percentage': sc_percentage, + + # ST Population with percentage + 'P_ST': properties.get('P_ST', 0), + 'M_ST': properties.get('M_ST', 0), + 'F_ST': properties.get('F_ST', 0), + 'st_percentage': st_percentage, + + # Literacy + 'P_LIT': properties.get('P_LIT', 0), + 'M_LIT': properties.get('M_LIT', 0), + 'F_LIT': properties.get('F_LIT', 0), + 'literacy_percentage': literacy_percentage, + + # Illiteracy + 'P_ILL': properties.get('P_ILL', 0), + 'M_ILL': properties.get('M_ILL', 0), + 'F_ILL': properties.get('F_ILL', 0), + + # Development Index + 'ADI_2011': properties.get('ADI_2011', 0), + 'ADI_2019': properties.get('ADI_2019', 0), + } + + return demographic_data + + +def _convert_wkt_to_gml_coordinates(wkt): + """ + Convert WKT coordinates to GML format. + Example: "POLYGON ((x1 y1, x2 y2, ...))" → "x1 y1x2 y2..." + """ + try: + # Extract coordinates from WKT + # WKT format: "POLYGON ((lon lat, lon lat, ...))" + import re + + # Extract the coordinate string + coords_str = re.search(r'\(\((.*?)\)\)', wkt).group(1) + + # Split into individual coordinates + coords = coords_str.split(',') + + # Convert to GML format + gml_coords = [] + for coord in coords: + coord = coord.strip() + if coord: + gml_coords.append(f"{coord}") + + return '\n '.join(gml_coords) + + except Exception as e: + print(f"[WARN] Error converting WKT to GML: {str(e)}") + return "" + + +def _try_bbox_query(layer_name, bounds): + """ + Fallback: Query using bounding box instead of spatial intersection. + """ + + try: + minx, miny, maxx, maxy = bounds + + # Add small buffer + buffer = 0.01 + minx -= buffer + miny -= buffer + maxx += buffer + maxy += buffer + + print(f"[DEBUG] BBOX query bounds: {minx}, {miny}, {maxx}, {maxy}") + + wfs_url = f"{GEOSERVER_URL}/wfs" + + params = { + 'service': 'WFS', + 'version': '2.0.0', + 'request': 'GetFeature', + 'typeNames': f"mws_layers:{layer_name}", + 'outputFormat': 'application/json', + 'bbox': f"{minx},{miny},{maxx},{maxy},urn:ogc:def:crs:EPSG:4326", + 'srsName': 'EPSG:4326' + } + + response = requests.get(wfs_url, params=params, timeout=30) + + if response.status_code == 200: + mws_data = response.json() + + if mws_data.get('features'): + print(f"[SUCCESS] BBOX query found {len(mws_data['features'])} features") + return { + 'success': True, + 'features': mws_data['features'], + 'count': len(mws_data['features']), + 'error': None + } + else: + print(f"[INFO] BBOX query returned 0 features") + return { + 'success': True, + 'features': [], + 'count': 0, + 'error': 'No features in bbox' + } + else: + error_msg = f"Status {response.status_code}" + print(f"[ERROR] BBOX query failed: {error_msg}") + return { + 'success': False, + 'features': [], + 'count': 0, + 'error': error_msg + } + + except Exception as e: + print(f"[ERROR] BBOX query exception: {str(e)}") + return { + 'success': False, + 'features': [], + 'count': 0, + 'error': str(e) + } + + +def test_mws_layer(district, block): + """ + Test if the MWSes layer exists in GeoServer. + """ + + mws_layer_name = f"deltaG_well_depth_{district}_{block}".lower() + + try: + wfs_url = f"{GEOSERVER_URL}/wfs" + + params = { + 'service': 'WFS', + 'version': '2.0.0', + 'request': 'GetFeature', + 'typeNames': f"mws_layers:{mws_layer_name}", + 'outputFormat': 'application/json', + 'maxFeatures': 1 + } + + response = requests.get(wfs_url, params=params, timeout=10) + + if response.status_code == 200: + print(f"✓ Layer '{mws_layer_name}' exists") + data = response.json() + features_count = len(data.get('features', [])) + print(f" Features available: {features_count}") + if data.get('features'): + print(f" Geometry type: {data['features'][0].get('geometry', {}).get('type')}") + print(f" Sample properties: {list(data['features'][0].get('properties', {}).keys())[:5]}") + return True + else: + print(f"✗ Layer '{mws_layer_name}' returned status {response.status_code}") + return False + + except Exception as e: + print(f"✗ Error testing layer: {str(e)}") + return False + + +def _try_post_intersects_query(layer_name, village_wkt): + """ + Try INTERSECTS spatial filter using POST request. + POST avoids URL length limits for complex geometries. + """ + + try: + print(f"[DEBUG] Attempting POST INTERSECTS query...") + + wfs_url = f"{GEOSERVER_URL}/wfs" + + # Build WFS query with CQL filter in request body + wfs_query = f""" + + + + + the_geom + + + + {_convert_wkt_to_gml_coordinates(village_wkt)} + + + + + +""" + + headers = { + 'Content-Type': 'application/xml' + } + + response = requests.post(wfs_url, data=wfs_query, headers=headers, timeout=30) + print(f"[DEBUG] POST response status: {response.status_code}") + + if response.status_code == 200: + mws_data = response.json() + if mws_data.get('features'): + print(f"[SUCCESS] Found {len(mws_data['features'])} features") + return { + 'success': True, + 'features': mws_data['features'], + 'count': len(mws_data['features']), + 'error': None + } + else: + print(f"[INFO] No features found") + return { + 'success': True, + 'features': [], + 'count': 0, + 'error': 'No intersecting features' + } + else: + error_msg = f"Status {response.status_code}: {response.text[:200]}" + print(f"[WARN] POST query failed: {error_msg}") + return { + 'success': False, + 'features': [], + 'count': 0, + 'error': error_msg + } + + except Exception as e: + print(f"[WARN] POST query exception: {str(e)}") + return { + 'success': False, + 'features': [], + 'count': 0, + 'error': str(e) + } + + +def get_mwses_boundaries(state, district, block, village_id): + """ + Get all MWSes (Micro-Watersheds) boundaries that intersect with the given village. + Uses POST requests to avoid URL length limits with complex geometries. + """ + + try: + # Step 1: Get village geometry + village_data = get_village_polygon_and_info(state, district, block, village_id) + + if not village_data or not village_data.get('village_polygon'): + return {'polygon': {}, 'count': 0, 'error': 'Village polygon not found'} + + village_geom = village_data['village_polygon'] + print(f"[DEBUG] village_geom type: {type(village_geom)}") + + # Step 2: Convert to Shapely + village_shapely = None + + if isinstance(village_geom, dict): + if village_geom.get('type') == 'FeatureCollection': + geometries = [] + for feature in village_geom.get('features', []): + if 'geometry' in feature: + geometries.append(shape(feature['geometry'])) + village_shapely = unary_union(geometries) + print(f"[DEBUG] Converted FeatureCollection to Shapely (unary_union)") + elif village_geom.get('type') == 'Feature': + village_shapely = shape(village_geom['geometry']) + print(f"[DEBUG] Converted Feature to Shapely") + elif village_geom.get('type') in ['Polygon', 'MultiPolygon', 'Point', 'LineString']: + village_shapely = shape(village_geom) + print(f"[DEBUG] Converted {village_geom.get('type')} to Shapely") + + if village_shapely is None: + return {'polygon': {}, 'count': 0, 'error': 'Could not convert village geometry'} + + # Step 3: Query MWSes layer + mws_layer_name = f"deltaG_well_depth_{district}_{block}".lower() + print(f"[DEBUG] Querying layer: {mws_layer_name}") + + # Convert to WKT + village_wkt = shapely_to_wkt(village_shapely) + print(f"[DEBUG] Village WKT length: {len(village_wkt)} characters") + + # Step 4: Try POST request with INTERSECTS (avoids URL length limits) + result = _try_post_intersects_query(mws_layer_name, village_wkt) + + if result['success']: + print(f"[SUCCESS] POST INTERSECTS query returned {result['count']} features") + return { + 'polygon': {'type': 'FeatureCollection', 'features': result['features']}, + 'count': result['count'], + 'error': None + } + + print(f"[WARNING] POST INTERSECTS query failed: {result['error']}") + + # Step 5: Fallback to BBOX query + print(f"[INFO] Trying fallback with BBOX query...") + bounds = village_shapely.bounds + result = _try_bbox_query(mws_layer_name, bounds) + + if result['success']: + print(f"[SUCCESS] BBOX query returned {result['count']} features") + return { + 'polygon': {'type': 'FeatureCollection', 'features': result['features']}, + 'count': result['count'], + 'error': None + } + + print(f"[ERROR] BBOX query also failed: {result['error']}") + return { + 'polygon': {}, + 'count': 0, + 'error': f"Both queries failed. BBOX error: {result['error']}" + } + + except Exception as e: + print(f"[EXCEPTION] {str(e)}") + import traceback + traceback.print_exc() + return { + 'polygon': {}, + 'count': 0, + 'error': f'Error: {str(e)}' + } + + +def get_mwses_ids(state, district, block, village_id): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_mws = pd.read_excel( + excel_file, + sheet_name="mws_intersect_villages" + ) + + village_id = int(village_id) + + mws_ids = [] + + for _, row in df_mws.iterrows(): + + village_ids_raw = row.get("Village IDs") + + if pd.isnull(village_ids_raw): + continue + + try: + village_ids = ast.literal_eval( + str(village_ids_raw) + ) + + except Exception: + continue + + if village_id in village_ids: + + mws_ids.append( + str(row.get("MWS UID")) + ) + + return mws_ids + + except Exception as e: + + logger.info( + "Not able to access MWS data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + +# ? MARK: MAIN SECTION +def get_village_polygon_and_info(state, district, block, village_id): + + try: + # Construct the layer name based on state/district/block + workspace = 'panchayat_boundaries' + layer_name = f"{district}_{block}".lower() # e.g., "ajmer_bhinay" + + # Create WFS URL with CQL_FILTER to query by vill_ID + base_url = f"{GEOSERVER_URL}/{workspace}/ows" + + # CQL_FILTER: search for the specific village by ID + cql_filter = f"vill_ID={village_id}" + + params = { + 'service': 'WFS', + 'version': '1.0.0', + 'request': 'GetFeature', + 'typeName': f'{workspace}:{layer_name}', + 'outputFormat': 'application/json', + 'CQL_FILTER': cql_filter + } + + logger.info(f"Querying GeoServer for village: state={state}, district={district}, block={block}, village_id={village_id}") + + # Make request to GeoServer + response = requests.get(base_url, params=params, timeout=10) + response.raise_for_status() + + geojson_data = response.json() + + # Check if we got features + if not geojson_data.get('features') or len(geojson_data['features']) == 0: + logger.warning(f"No village found with ID {village_id} in {district}, {block}") + return { + 'village_polygon': None, + 'village_name': None, + 'gram_panchayat_name': None, + 'area_hectares': None, + 'properties': {} + } + + # Extract the first feature (should be only one with specific vill_ID) + feature = geojson_data['features'][0] + properties = feature.get('properties', {}) + + # Extract village information from properties + village_name = properties.get('vill_name', None) + + # Try to construct gram panchayat name (may not be in properties, so optional) + # You can customize this based on your actual data structure + gram_panchayat_name = properties.get('gram_panchayat', None) or properties.get('gp_name', None) + + # Extract area if available (in hectares) + # This depends on your data; adjust the property name if different + area_hectares = properties.get('area_hectares', None) or properties.get('area_ha', None) + + logger.info(f"Found village: {village_name} (ID: {village_id})") + + # Return the village polygon as a FeatureCollection (required for template) + village_polygon_geojson = { + 'type': 'FeatureCollection', + 'features': [feature] + } + + return { + 'village_polygon': village_polygon_geojson, + 'village_name': village_name, + 'gram_panchayat_name': gram_panchayat_name, + 'area_hectares': area_hectares, + 'properties': properties + } + + except requests.exceptions.RequestException as e: + logger.error(f"GeoServer request failed: {str(e)}") + return { + 'village_polygon': None, + 'village_name': None, + 'gram_panchayat_name': None, + 'area_hectares': None, + 'properties': {} + } + except (json.JSONDecodeError, KeyError) as e: + logger.error(f"Error parsing GeoServer response: {str(e)}") + return { + 'village_polygon': None, + 'village_name': None, + 'gram_panchayat_name': None, + 'area_hectares': None, + 'properties': {} + } + + +def get_development_data(state, district, block, village_id): + + def normalize_column(df, column): + df[column] = df[column].astype(str).str.strip() + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + def calculate_band_score(value): + if value <= 0.33: + return 0.33 + elif value <= 0.66: + return 0.66 + return 1 + + def distance_score(distance, high_limit, medium_limit=None): + + if pd.isnull(distance): + return 0.33 + + if medium_limit is None: + return 1 if distance < high_limit else 0.33 + + if distance < high_limit: + return 1 + elif high_limit <= distance <= medium_limit: + return 0.66 + return 0.33 + + def get_distance_logic(row, columns, logic="max"): + + values = [ + get_numeric(row, col) + for col in columns + ] + + values = [v for v in values if pd.notnull(v)] + + if not values: + return None + + return max(values) if logic == "max" else min(values) + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel(excel_file, sheet_name="antyodaya") + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + normalize_column(df, "village_id") + normalize_column(df_facilities, "censuscode2011") + + village_id = str(village_id).strip() + + matched_rows = df[df["village_id"] == village_id] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + facility_row = ( + facility_match.iloc[0] + if not facility_match.empty + else None + ) + + scores = [] + + # ========================================================= + # Infrastructure Score + # ========================================================= + + infrastructure_avg = ( + safe_float(row.get("road_connectivity_cat_value", 0)) + + safe_float(row.get("energy_access_cat_value", 0)) + + safe_float(row.get("housing_quality_cat_value", 0)) + ) / 3 + + scores.append(calculate_band_score(infrastructure_avg)) + + # ========================================================= + # Health Score + # ========================================================= + + maternal_child_score = safe_float( + row.get("maternal_child_health_cat_value", 0) + ) + + water_sanitation_score = safe_float( + row.get("water_sanitation_cat_value", 0) + ) + + essential_health_services_score = 0.33 + advanced_health_services_score = 0.33 + + if facility_row is not None: + + essential_distance = get_distance_logic( + facility_row, + [ + "health_sub_cen_distance", + "health_phc_distance" + ], + logic="max" + ) + + essential_health_services_score = distance_score( + essential_distance, + high_limit=2, + medium_limit=5 + ) + + advanced_distance = get_distance_logic( + facility_row, + [ + "health_chc_distance", + "health_dis_h_distance", + "health_s_t_h_distance" + ], + logic="min" + ) + + advanced_health_services_score = distance_score( + advanced_distance, + high_limit=10, + medium_limit=25 + ) + + health_avg = ( + maternal_child_score + + water_sanitation_score + + essential_health_services_score + + advanced_health_services_score + ) / 4 + + scores.append(calculate_band_score(health_avg)) + + #* Education Score + education_score = 0.33 + + if facility_row is not None: + + essential_education_distance = get_distance_logic( + facility_row, + [ + "school_primary_distance", + "school_upper_primary_distance", + "school_secondary_distance" + ], + logic="max" + ) + + higher_education_distance = get_distance_logic( + facility_row, + [ + "school_higher_secondary_distance", + "college_distance", + "universities_distance" + ], + logic="min" + ) + + if ( + essential_education_distance is not None + and higher_education_distance is not None + ): + + if ( + essential_education_distance > 2 + and higher_education_distance > 8 + ): + education_score = 0.33 + + elif ( + essential_education_distance < 2 + and higher_education_distance < 8 + ): + education_score = 1 + + else: + education_score = 0.67 + + scores.append(education_score) + + #* Financial Inclusion Score + financial_inclusion_score = 0.33 + + if facility_row is not None: + + financial_distance = get_distance_logic( + facility_row, + [ + "csc_distance", + "bank_mitra_distance", + "bank_branch_distance", + "bank_atm_distance" + ], + logic="max" + ) + + financial_inclusion_score = distance_score( + financial_distance, + high_limit=2, + medium_limit=5 + ) + + scores.append(financial_inclusion_score) + + + #* Welfare Inclusion Score + social_protection_score = safe_float(row.get("social_protection_cat_value", 0)) + + pds_score = 0.5 + + if facility_row is not None: + + pds_distance = get_numeric( + facility_row, + "pds_distance" + ) + + pds_score = 1 if ( + pd.notnull(pds_distance) + and pds_distance < 2 + ) else 0.5 + + welfare_avg = ( + social_protection_score + pds_score + ) / 2 + + scores.append(calculate_band_score(welfare_avg)) + + #* Community Institutions + community_score = safe_float(row.get("institutionalization_cat_value", 0)) + civic_score = safe_float(row.get("civic_infrastructure_cat_value", 0)) + + community_avg_score = (community_score + civic_score) / 2 + + scores.append(calculate_band_score(community_avg_score)) + + #* Livelihood Diversification Score + livelihood_farm_score = safe_float(row.get("farm_employment_feat_value", 0)) + livelihood_forest_score = safe_float(row.get("livelihoods_forest_resources_cat_value", 0)) + livelihood_fish_score = safe_float(row.get("livelihoods_fisheries_cat_value", 0)) + livelihood_alternate_score = safe_float(row.get("livelihoods_alternative_farming_cat_value", 0)) + livelihood_cottage_score = safe_float(row.get("livelihoods_cottage_traditional_industry_cat_value", 0)) + + livelihood_avg_score = (livelihood_farm_score + livelihood_forest_score + livelihood_fish_score + livelihood_alternate_score + livelihood_cottage_score)/5 + + scores.append(calculate_band_score(livelihood_avg_score)) + + + #* Livestock + livestock_support_score = safe_float(row.get("livestock_veterinary_cat_value", 0)) + livestock_pasture_score = safe_float(row.get("livelihoods_common_resources_cat_value", 0)) + + livestock_support_avg = (livestock_support_score + livestock_pasture_score) / 2 + + if facility_row is not None: + husbandry_distance = get_numeric( + facility_row, + "agri_industry_dairy_animal_husbandry_distance" + ) + + # High: < 10 km + if pd.notnull(husbandry_distance) and husbandry_distance < 10: + husbandry_score = 1 + + # Moderate: 10 - 30 km + elif ( + pd.notnull(husbandry_distance) + and 10 <= husbandry_distance <= 30 + ): + husbandry_score = 0.67 + + # Low: > 30 km + else: + husbandry_score = 0.33 + + livestock_avg_score = (livestock_support_avg + husbandry_score) / 2 + + scores.append(calculate_band_score(livestock_avg_score)) + + #* Agricultural Productivity and Resource Use + agri_avg_score = ( + safe_float(row.get("agricultural_markets_cat_value", 0)) + + safe_float(row.get("agriculture_land_cultivation_cat_value", 0)) + + safe_float(row.get("agriculture_irrigation_watershed_cat_value", 0)) + + safe_float(row.get("agriculture_support_services_cat_value", 0)) + ) / 4 + + + facility_scores = [] + + if facility_row is not None: + + agri_facility_configs = [ + { + "column": "agri_industry_agri_support_infrastructure_distance", + "high_limit": 10, + "medium_limit": 50, + }, + { + "column": "agri_industry_agri_processing_distance", + "high_limit": 5, + "medium_limit": 20, + }, + { + "column": "agri_industry_co_operatives_societies_distance", + "high_limit": 10, + "medium_limit": 30, + }, + { + "column": "agri_industry_markets_trading_distance", + "high_limit": 3, + "medium_limit": 10, + }, + ] + + facility_scores = [ + distance_score( + get_numeric(facility_row, config["column"]), + high_limit=config["high_limit"], + medium_limit=config["medium_limit"], + ) + for config in agri_facility_configs + ] + + + agri_produce_resource_score = (agri_avg_score + sum(facility_scores)) / (1 + len(facility_scores)) + + scores.append(calculate_band_score(agri_produce_resource_score)) + + #* Ecology and Climate Resilience + organic_farm_score = safe_float(row.get("agriculture_organic_farming_cat_value", 0)) + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + normalize_column(df_nrega, "vill_id") + + nrega_match = df_nrega[ + df_nrega["vill_id"] == village_id + ] + + nrega_score = 0.33 + + if not nrega_match.empty: + + nrega_row = nrega_match.iloc[0] + + # Exclude non-numeric columns + exclude_columns = ["vill_id", "vill_name"] + + year_columns = [ + col for col in df_nrega.columns + if col not in exclude_columns + ] + + # Sum all yearly NREGA asset columns + total_nrega_assets = sum([ + safe_float(nrega_row.get(col, 0)) + for col in year_columns + ]) + + # Assign score + if total_nrega_assets < 100: + nrega_score = 0.33 + + elif 100 <= total_nrega_assets <= 300: + nrega_score = 0.67 + + else: + nrega_score = 1 + + # Final Ecology and Climate Resilience Score + + ecology_climate_avg = ( + organic_farm_score + nrega_score + ) / 2 + + scores.append( + calculate_band_score(ecology_climate_avg) + ) + + return scores + + except Exception as e: + + logger.info( + "Not able to access excel for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_block_development_data(state, district, block): + + def normalize_column(df, column): + df[column] = df[column].astype(str).str.strip() + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + def calculate_band_score(value): + if value <= 0.33: + return 0.33 + elif value <= 0.66: + return 0.66 + return 1 + + def distance_score(distance, high_limit, medium_limit=None): + + if pd.isnull(distance): + return 0.33 + + if medium_limit is None: + return 1 if distance < high_limit else 0.33 + + if distance < high_limit: + return 1 + + elif high_limit <= distance <= medium_limit: + return 0.67 + + return 0.33 + + def get_distance_logic(row, columns, logic="max"): + + values = [ + get_numeric(row, col) + for col in columns + ] + + values = [v for v in values if pd.notnull(v)] + + if not values: + return None + + return max(values) if logic == "max" else min(values) + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel(excel_file, sheet_name="antyodaya") + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + normalize_column(df, "village_id") + normalize_column(df_facilities, "censuscode2011") + normalize_column(df_nrega, "vill_id") + + block_scores = [] + + # ========================================================= + # Infrastructure Score + # ========================================================= + + infrastructure_avg = ( + df[ + [ + "road_connectivity_cat_value", + "energy_access_cat_value", + "housing_quality_cat_value" + ] + ] + .apply(pd.to_numeric, errors="coerce") + .mean() + .mean() + ) + + block_scores.append( + calculate_band_score(infrastructure_avg) + ) + + # ========================================================= + # Health Score + # ========================================================= + + maternal_child_avg = pd.to_numeric( + df["maternal_child_health_cat_value"], + errors="coerce" + ).mean() + + water_sanitation_avg = pd.to_numeric( + df["water_sanitation_cat_value"], + errors="coerce" + ).mean() + + essential_health_scores = [] + advanced_health_scores = [] + + for _, facility_row in df_facilities.iterrows(): + + essential_distance = get_distance_logic( + facility_row, + [ + "health_sub_cen_distance", + "health_phc_distance" + ], + logic="max" + ) + + essential_health_scores.append( + distance_score( + essential_distance, + high_limit=2, + medium_limit=5 + ) + ) + + advanced_distance = get_distance_logic( + facility_row, + [ + "health_chc_distance", + "health_dis_h_distance", + "health_s_t_h_distance" + ], + logic="min" + ) + + advanced_health_scores.append( + distance_score( + advanced_distance, + high_limit=10, + medium_limit=25 + ) + ) + + health_avg = ( + maternal_child_avg + + water_sanitation_avg + + (sum(essential_health_scores) / len(essential_health_scores)) + + (sum(advanced_health_scores) / len(advanced_health_scores)) + ) / 4 + + block_scores.append( + calculate_band_score(health_avg) + ) + + # ========================================================= + # Education Score + # ========================================================= + + education_scores = [] + + for _, facility_row in df_facilities.iterrows(): + + essential_education_distance = get_distance_logic( + facility_row, + [ + "school_primary_distance", + "school_upper_primary_distance", + "school_secondary_distance" + ], + logic="max" + ) + + higher_education_distance = get_distance_logic( + facility_row, + [ + "school_higher_secondary_distance", + "college_distance", + "universities_distance" + ], + logic="min" + ) + + if ( + essential_education_distance is not None + and higher_education_distance is not None + ): + + if ( + essential_education_distance > 2 + and higher_education_distance > 8 + ): + education_scores.append(0.33) + + elif ( + essential_education_distance < 2 + and higher_education_distance < 8 + ): + education_scores.append(1) + + else: + education_scores.append(0.67) + + education_avg = ( + sum(education_scores) / len(education_scores) + if education_scores else 0.33 + ) + + block_scores.append( + calculate_band_score(education_avg) + ) + + # ========================================================= + # Financial Inclusion Score + # ========================================================= + + financial_scores = [] + + for _, facility_row in df_facilities.iterrows(): + + financial_distance = get_distance_logic( + facility_row, + [ + "csc_distance", + "bank_mitra_distance", + "bank_branch_distance", + "bank_atm_distance" + ], + logic="max" + ) + + financial_scores.append( + distance_score( + financial_distance, + high_limit=2, + medium_limit=5 + ) + ) + + financial_avg = ( + sum(financial_scores) / len(financial_scores) + if financial_scores else 0.33 + ) + + block_scores.append( + calculate_band_score(financial_avg) + ) + + # ========================================================= + # Welfare Inclusion Score + # ========================================================= + + social_protection_avg = pd.to_numeric( + df["social_protection_cat_value"], + errors="coerce" + ).mean() + + pds_scores = [] + + for _, facility_row in df_facilities.iterrows(): + + pds_distance = get_numeric( + facility_row, + "pds_distance" + ) + + pds_scores.append( + 1 if ( + pd.notnull(pds_distance) + and pds_distance < 2 + ) else 0.5 + ) + + welfare_avg = ( + social_protection_avg + + (sum(pds_scores) / len(pds_scores)) + ) / 2 + + block_scores.append( + calculate_band_score(welfare_avg) + ) + + + # Community Score + community_score = pd.to_numeric( + df["institutionalization_cat_value"], + errors="coerce" + ).mean() + + civic_score = pd.to_numeric( + df["civic_infrastructure_cat_value"], + errors="coerce" + ).mean() + + community_avg = (community_score + civic_score) / 2 + + block_scores.append( + calculate_band_score(community_avg) + ) + + + # Livelihood + livelihood_avg = ( + df[ + [ + "farm_employment_feat_value", + "livelihoods_forest_resources_cat_value", + "livelihoods_fisheries_cat_value", + "livelihoods_alternative_farming_cat_value", + "livelihoods_cottage_traditional_industry_cat_value" + ] + ] + .apply(pd.to_numeric, errors="coerce") + .mean() + .mean() + ) + + block_scores.append( + calculate_band_score(livelihood_avg) + ) + + + # Livestock + livestock_support_avg = pd.to_numeric( + df["livestock_veterinary_cat_value"], + errors="coerce" + ).mean() + + husbandry_scores = [] + + for _, facility_row in df_facilities.iterrows(): + + husbandry_distance = get_numeric( + facility_row, + "agri_industry_dairy_animal_husbandry_distance" + ) + + if pd.notnull(husbandry_distance) and husbandry_distance < 10: + husbandry_scores.append(1) + + elif ( + pd.notnull(husbandry_distance) + and 10 <= husbandry_distance <= 30 + ): + husbandry_scores.append(0.67) + + else: + husbandry_scores.append(0.33) + + livestock_avg = ( + livestock_support_avg + + sum(husbandry_scores)/len(husbandry_scores) + ) / 2 + + block_scores.append( + calculate_band_score(livestock_avg) + ) + + # Agricultural Productivity + agri_scores = [] + + for _, facility_row in df_facilities.iterrows(): + + village_id = facility_row["censuscode2011"] + + village_match = df[ + df["village_id"] == village_id + ] + + if village_match.empty: + continue + + row = village_match.iloc[0] + + # reuse same logic from village function + # compute agri_produce_resource_score + agri_avg_score = ( + safe_float(row.get("agricultural_markets_cat_value", 0)) + + safe_float(row.get("agriculture_land_cultivation_cat_value", 0)) + + safe_float(row.get("agriculture_irrigation_watershed_cat_value", 0)) + + safe_float(row.get("agriculture_support_services_cat_value", 0)) + ) / 4 + + + facility_scores = [] + + if facility_row is not None: + + agri_facility_configs = [ + { + "column": "agri_industry_agri_support_infrastructure_distance", + "high_limit": 10, + "medium_limit": 50, + }, + { + "column": "agri_industry_agri_processing_distance", + "high_limit": 5, + "medium_limit": 20, + }, + { + "column": "agri_industry_co_operatives_societies_distance", + "high_limit": 10, + "medium_limit": 30, + }, + { + "column": "agri_industry_markets_trading_distance", + "high_limit": 3, + "medium_limit": 10, + }, + ] + + facility_scores = [ + distance_score( + get_numeric(facility_row, config["column"]), + high_limit=config["high_limit"], + medium_limit=config["medium_limit"], + ) + for config in agri_facility_configs + ] + + agri_produce_resource_score = (agri_avg_score + sum(facility_scores)) / (1 + len(facility_scores)) + + agri_scores.append( + agri_produce_resource_score + ) + + block_scores.append( + calculate_band_score( + sum(agri_scores)/len(agri_scores) + ) + ) + + #Ecology & Climate Resilience + organic_farm_avg = pd.to_numeric( + df["agriculture_organic_farming_cat_value"], + errors="coerce" + ).mean() + + nrega_scores = [] + + exclude_columns = ["vill_id", "vill_name"] + + year_columns = [ + col for col in df_nrega.columns + if col not in exclude_columns + ] + + for _, nrega_row in df_nrega.iterrows(): + + total_nrega_assets = sum( + safe_float(nrega_row.get(col, 0)) + for col in year_columns + ) + + if total_nrega_assets < 100: + nrega_scores.append(0.33) + + elif 100 <= total_nrega_assets <= 300: + nrega_scores.append(0.67) + + else: + nrega_scores.append(1) + + nrega_avg = ( + sum(nrega_scores) / len(nrega_scores) + if nrega_scores + else 0.33 + ) + + ecology_avg = ( + organic_farm_avg + nrega_avg + ) / 2 + + block_scores.append( + calculate_band_score(ecology_avg) + ) + + return block_scores + + except Exception as e: + + logger.info( + "Not able to calculate block scores for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_basic_infrastructure(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel(excel_file, sheet_name="antyodaya") + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + road_score = safe_float(row.get("road_connectivity_cat_value", 0)) + + energy_score = safe_float(row.get("energy_access_cat_value", 0)) + + housing_score = safe_float(row.get("housing_quality_cat_value", 0)) + + return [ + road_score, + energy_score, + housing_score + ] + + except Exception as e: + logger.info( + "Not able to access excel for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + return [] + + +def get_health_and_wash(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + def get_distance_logic(row, columns, logic="max"): + + values = [ + get_numeric(row, col) + for col in columns + ] + + values = [v for v in values if pd.notnull(v)] + + if not values: + return None + + return max(values) if logic == "max" else min(values) + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + facility_row = ( + facility_match.iloc[0] + if not facility_match.empty + else None + ) + + maternal_child_score = safe_float( + row.get("maternal_child_health_cat_value", 0) + ) + + water_sanitation_score = safe_float( + row.get("water_sanitation_cat_value", 0) + ) + + if facility_row is not None: + + essential_distance = get_distance_logic( + facility_row, + [ + "health_sub_cen_distance", + "health_phc_distance" + ], + logic="max" + ) + + advanced_distance = get_distance_logic( + facility_row, + [ + "health_chc_distance", + "health_dis_h_distance", + "health_s_t_h_distance" + ], + logic="min" + ) + + return [ + maternal_child_score, # index 0 + water_sanitation_score, # index 1 + round(essential_distance, 2) if essential_distance is not None else None, + round(advanced_distance, 2) if advanced_distance is not None else None + ] + + except Exception as e: + logger.info( + "Not able to access excel for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + return [] + + +def get_education_institutions(state, district, block, village_id): + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + def get_distance_logic(row, columns, logic="max"): + + values = [ + get_numeric(row, col) + for col in columns + ] + + values = [v for v in values if pd.notnull(v)] + + if not values: + return None + + return max(values) if logic == "max" else min(values) + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + if facility_match.empty: + logger.info( + "No education data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + facility_row = facility_match.iloc[0] + + essential_education_distance = get_distance_logic( + facility_row, + [ + "school_primary_distance", + "school_upper_primary_distance", + "school_secondary_distance" + ], + logic="max" + ) + + higher_education_distance = get_distance_logic( + facility_row, + [ + "school_higher_secondary_distance", + "college_distance", + "universities_distance" + ], + logic="min" + ) + + color = "yellow" + + if (essential_education_distance is not None and higher_education_distance is not None): + + if (essential_education_distance > 2 and higher_education_distance > 8): + color = "red" + + elif (essential_education_distance < 2 and higher_education_distance < 8): + color = "green" + + else: + color = "yellow" + + return [ + round(essential_education_distance, 2) + if essential_education_distance is not None + else None, + + round(higher_education_distance, 2) + if higher_education_distance is not None + else None, + + color + ] + + except Exception as e: + logger.info( + "Not able to access education data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + return [] + + +def get_financial_inclusion(state, district, block, village_id): + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + def distance_score(distance, high_limit, medium_limit=None): + + if pd.isnull(distance): + return 0.33 + + if medium_limit is None: + return 1 if distance < high_limit else 0.33 + + if distance < high_limit: + return 1 + + elif high_limit <= distance <= medium_limit: + return 0.66 + + return 0.33 + + def get_distance_logic(row, columns, logic="max"): + + values = [ + get_numeric(row, col) + for col in columns + ] + + values = [v for v in values if pd.notnull(v)] + + if not values: + return None + + return max(values) if logic == "max" else min(values) + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + if facility_match.empty: + logger.info( + "No financial inclusion data found for village_id %s", + village_id, + ) + return [] + + facility_row = facility_match.iloc[0] + + financial_distance = get_distance_logic( + facility_row, + [ + "csc_distance", + "bank_mitra_distance", + "bank_branch_distance", + "bank_atm_distance" + ], + logic="max" + ) + + financial_inclusion_score = distance_score( + financial_distance, + high_limit=2, + medium_limit=5 + ) + + color = ( + "green" + if financial_inclusion_score == 1 + else "red" + ) + + return [ + financial_inclusion_score, + round(financial_distance, 2) + if financial_distance is not None + else None, + color + ] + + except Exception as e: + logger.info( + "Not able to access financial inclusion data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + return [] + + +def get_welfare_inclusion(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s", + village_id + ) + return [] + + row = matched_rows.iloc[0] + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + facility_row = ( + facility_match.iloc[0] + if not facility_match.empty + else None + ) + + social_protection_score = safe_float( + row.get("social_protection_cat_value", 0) + ) + + pds_distance = None + + if facility_row is not None: + + pds_distance = get_numeric( + facility_row, + "pds_distance" + ) + + if social_protection_score <= 0.33: + color = "red" + + elif social_protection_score <= 0.66: + color = "yellow" + + else: + color = "green" + + return [ + social_protection_score, + round(pds_distance, 2) + if pd.notnull(pds_distance) + else None, + color + ] + + except Exception as e: + + logger.info( + "Not able to access welfare inclusion data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_community_institutes(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + community_score = safe_float( + row.get("institutionalization_cat_value", 0) + ) + + civic_score = safe_float( + row.get("civic_infrastructure_cat_value", 0) + ) + + # Institutionalization Strength Color + if community_score <= 0.33: + community_color = "red" + + elif community_score <= 0.66: + community_color = "yellow" + + else: + community_color = "green" + + # Civic Infrastructure Availability Color + civic_color = ( + "green" + if civic_score > 0.66 + else "red" + ) + + return [ + community_score, + civic_score, + community_color, + civic_color + ] + + except Exception as e: + + logger.info( + "Not able to access community institution data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_livelihood_diversification(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + return [ + safe_float(row.get("farm_employment_feat_value", 0)), + safe_float(row.get("livelihoods_forest_resources_cat_value", 0)), + safe_float(row.get("livelihoods_alternative_farming_cat_value", 0)), + safe_float(row.get("livelihoods_fisheries_cat_value", 0)), + safe_float(row.get("livelihoods_cottage_traditional_industry_cat_value", 0)) + ] + + except Exception as e: + + logger.info( + "Not able to access livelihood diversification data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_livestock_management(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[df["village_id"] == village_id] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + facility_row = ( + facility_match.iloc[0] + if not facility_match.empty + else None + ) + + livestock_support_score = safe_float( + row.get("livestock_veterinary_cat_value", 0) + ) + + livestock_pasture_score = safe_float(row.get("livelihoods_common_resources_cat_value", 0)) + + husbandry_distance = None + + if facility_row is not None: + + husbandry_distance = get_numeric( + facility_row, + "agri_industry_dairy_animal_husbandry_distance" + ) + + veterinary_color = ( + "green" + if livestock_support_score > 0.66 + else "red" + ) + + pasture_color = ( + "green" + if livestock_pasture_score > 0.66 + else "red" + ) + + return [ + livestock_support_score, + livestock_pasture_score, + round(husbandry_distance, 2) + if pd.notnull(husbandry_distance) + else None, + veterinary_color, + pasture_color + ] + + except Exception as e: + + logger.info( + "Not able to access livestock management data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_land_cultivation(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + village_id = str(village_id).strip() + + matched_rows = df[df["village_id"] == village_id] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + land_utilization_score = safe_float(row.get("agriculture_land_cultivation_cat_value", 0)) + + + + except Exception as e: + + logger.info( + "Not able to access Land cultivation data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_irrigation_Infra(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + irrigation_watershed_score = safe_float( + row.get( + "agriculture_irrigation_watershed_cat_value", + 0 + ) + ) + + modern_irrigation_score = safe_float( + row.get( + "modern_irrigation_feat_value", + 0 + ) + ) + + # Irrigation Watershed Color + irrigation_watershed_color = ( + "green" + if irrigation_watershed_score > 0.66 + else "red" + ) + + # Modern Irrigation Color + if modern_irrigation_score <= 0.33: + modern_irrigation_color = "red" + + elif modern_irrigation_score <= 0.66: + modern_irrigation_color = "yellow" + + else: + modern_irrigation_color = "green" + + return [ + irrigation_watershed_score, + modern_irrigation_score, + irrigation_watershed_color, + modern_irrigation_color + ] + + except Exception as e: + + logger.info( + "Not able to access irrigation infrastructure data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_agri_support_service(state, district, block, village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + def get_numeric(row, column): + return pd.to_numeric(row.get(column, None), errors="coerce") + + def get_distance_logic(row, columns, logic="max"): + + values = [ + get_numeric(row, col) + for col in columns + ] + + values = [v for v in values if pd.notnull(v)] + + if not values: + return None + + return max(values) if logic == "max" else min(values) + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return [] + + row = matched_rows.iloc[0] + + facility_match = df_facilities[ + df_facilities["censuscode2011"] == village_id + ] + + facility_row = ( + facility_match.iloc[0] + if not facility_match.empty + else None + ) + + # ===================================================== + # Scores from Antyodaya + # ===================================================== + + agri_support_score = safe_float( + row.get( + "agriculture_support_services_cat_value", + 0 + ) + ) + + agri_market_score = safe_float( + row.get( + "agricultural_markets_cat_value", + 0 + ) + ) + + # ===================================================== + # Distances from Facilities + # ===================================================== + + post_harvest_distance = None + apmc_access_distance = None + + if facility_row is not None: + + post_harvest_distance = get_distance_logic( + facility_row, + [ + "agri_industry_storage_warehousing_distance", + "agri_industry_distribution_utilities_distance", + "agri_industry_agri_processing_distance", + "agri_industry_industrial_manufacturing_distance" + ], + logic="min" + ) + + apmc_access_distance = get_distance_logic( + facility_row, + [ + "apmc_distance", + "agri_industry_markets_trading_distance" + ], + logic="min" + ) + + # ===================================================== + # Colors + # ===================================================== + + if agri_support_score <= 0.33: + agri_support_color = "red" + + elif agri_support_score <= 0.66: + agri_support_color = "yellow" + + else: + agri_support_color = "green" + + agri_market_color = ( + "green" + if agri_market_score > 0.66 + else "red" + ) + + return [ + agri_support_score, # index 0 + agri_market_score, # index 1 + round(post_harvest_distance, 2) + if post_harvest_distance is not None + else None, # index 2 + round(apmc_access_distance, 2) + if apmc_access_distance is not None + else None, # index 3 + agri_support_color, # index 4 + agri_market_color # index 5 + ] + + except Exception as e: + + logger.info( + "Not able to access agri support service data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + +def get_ecological_climate_resiliance(state,district,block,village_id): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_nrega["vill_id"] = ( + df_nrega["vill_id"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + # ===================================================== + # Organic Farming Score + # ===================================================== + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + return [] + + row = matched_rows.iloc[0] + + organic_farming_score = safe_float( + row.get( + "agriculture_organic_farming_cat_value", + 0 + ) + ) + + if organic_farming_score <= 0.33: + organic_farming_color = "red" + + elif organic_farming_score <= 0.66: + organic_farming_color = "yellow" + + else: + organic_farming_color = "green" + + # ===================================================== + # NREGA Assets + # ===================================================== + + nrega_match = df_nrega[ + df_nrega["vill_id"] == village_id + ] + + if nrega_match.empty: + + return [ + None, + {}, + organic_farming_score, + organic_farming_color + ] + + nrega_row = nrega_match.iloc[0] + + category_counts = {} + + years = set() + + for column in df_nrega.columns: + + if column in ["vill_id", "vill_name"]: + continue + + # Split from right to extract year + try: + work_type, year = column.rsplit("_", 1) + + year = int(year) + + years.add(year) + + except Exception: + continue + + category_name = ( + work_type + .replace("_count", "") + .replace("_", " ") + .strip() + ) + + category_counts.setdefault( + category_name, + 0 + ) + + category_counts[category_name] += safe_float( + nrega_row.get(column, 0) + ) + + year_range = { + "from_year": min(years) if years else None, + "to_year": max(years) if years else None, + } + + return [ + year_range, + category_counts, + organic_farming_score, + organic_farming_color + ] + + except Exception as e: + + logger.info( + "Not able to access ecology data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return [] + + + + +#? Get Tehsil Map Data +def get_all_villages_basic_infrastructure(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_nrega = pd.read_excel(excel_file,sheet_name="nrega_assets_village") + + village_ids = (df_nrega["vill_id"].dropna().astype(str).unique()) + + result = {} + + for village_id in village_ids: + + village_data = get_basic_infrastructure(state, district, block, village_id) + + if not village_data: + + result[village_id] = { + "road_color": "black", + "energy_color": "black", + "housing_color": "black", + } + + continue + + road_score = village_data[0] + energy_score = village_data[1] + housing_score = village_data[2] + + result[village_id] = { + + "road_color": ( + "green" + if road_score > 0.66 + else "red" + ), + + "energy_color": ( + "green" + if energy_score > 0.66 + else "red" + ), + + "housing_color": ( + "red" + if housing_score <= 0.33 + else "green" + if housing_score > 0.66 + else "yellow" + ), + } + + return result + + except Exception as e: + + logger.info( + "Error calculating infrastructure colors for all villages: %s", + str(e) + ) + + return {} + +def get_all_villages_education_institutions(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") + + village_ids = ( + df_nrega["vill_id"] + .dropna() + .astype(str) + .unique() + ) + + result = {} + + for village_id in village_ids: + + education_data = get_education_institutions(state,district, block, village_id ) + + if not education_data: + + result[village_id] = { + "education_color": "black" + } + + continue + + result[village_id] = { + "education_color": education_data[2] + } + + return result + + except Exception as e: + + logger.info( + "Error calculating education colors for all villages: %s", + str(e) + ) + + return {} + + diff --git a/templates/global_village_fetcher.js b/templates/global_village_fetcher.js new file mode 100644 index 00000000..8f36824d --- /dev/null +++ b/templates/global_village_fetcher.js @@ -0,0 +1,114 @@ +// ===== GLOBAL VILLAGE BOUNDARIES FETCHER (Fetch Once, Reuse Everywhere) ===== + +// Global storage for villages +window.globalVillageData = { + villages: {}, // Features by village ID + geoserverData: null, // Full GeoServer response + isLoading: false, + isLoaded: false, + error: null +}; + +/** + * Fetch all villages from GeoServer once at page load + * This data is reused by all map sections + */ +async function initializeGlobalVillages() { + if (window.globalVillageData.isLoading || window.globalVillageData.isLoaded) { + return; + } + + window.globalVillageData.isLoading = true; + + try { + const geoserverUrl = `https://geoserver.core-stack.org:8443/geoserver/panchayat_boundaries/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=panchayat_boundaries:{{ district|lower }}_{{ block|lower }}&outputFormat=application/json`; + + console.log('🌐 Fetching villages from GeoServer (Global)...'); + + const response = await fetch(geoserverUrl); + if (!response.ok) { + throw new Error(`GeoServer error: ${response.status}`); + } + + const geoserverData = await response.json(); + console.log('✓ GeoServer response received:', geoserverData.features?.length, 'villages'); + + // Store GeoServer response + window.globalVillageData.geoserverData = geoserverData; + + // Parse features and store by village ID + const features = new ol.format.GeoJSON().readFeatures(geoserverData, { + featureProjection: 'EPSG:4326' + }); + + features.forEach(feature => { + const villageId = feature.get('vill_ID'); + window.globalVillageData.villages[villageId] = feature; + }); + + console.log('✓ Stored', Object.keys(window.globalVillageData.villages).length, 'village features globally'); + + window.globalVillageData.isLoaded = true; + window.globalVillageData.isLoading = false; + + return geoserverData; + + } catch (error) { + console.error('✗ Error fetching villages:', error); + window.globalVillageData.error = error; + window.globalVillageData.isLoading = false; + return null; + } +} + +/** + * Get parsed features for a specific map section + * Parameters: + * - mapDataString: Context data (e.g., '{{ education_map|escapejs }}') + * - Returns: Array of OL features ready to add to map + */ +function getVillageFeatures(mapDataString) { + if (!window.globalVillageData.geoserverData) { + console.warn('Global villages not loaded yet'); + return []; + } + + try { + const mapData = JSON.parse(mapDataString); + const features = new ol.format.GeoJSON().readFeatures(window.globalVillageData.geoserverData, { + featureProjection: 'EPSG:4326' + }); + + return features; + } catch (error) { + console.warn('Error parsing map data:', error); + return []; + } +} + +/** + * Wait for global villages to load + */ +async function waitForGlobalVillages(maxWait = 10000) { + const startTime = Date.now(); + + while (!window.globalVillageData.isLoaded && !window.globalVillageData.error) { + if (Date.now() - startTime > maxWait) { + throw new Error('Timeout waiting for global villages'); + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + + if (window.globalVillageData.error) { + throw window.globalVillageData.error; + } + + return window.globalVillageData.geoserverData; +} + +// Initialize global villages when page loads +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeGlobalVillages); +} else { + initializeGlobalVillages(); +} \ No newline at end of file diff --git a/templates/mws-report.html b/templates/mws-report.html index 52568eba..70c01f9f 100644 --- a/templates/mws-report.html +++ b/templates/mws-report.html @@ -1760,27 +1760,27 @@

Average percentage of double cropped area

-
+
Barren Lands
-
+
Single Kharif
-
+
Single Non-Kharif
-
+
Double Cropping
-
- Triple Cropping +
+ Tripple/Annual/Perennial Cropping
-
+
Shrubs and Scrubs
@@ -2956,9 +2956,9 @@

MGNREGA works

labels: cropYears, // TODO : Get years from backend, don't hardcode as year would increase as data increase datasets: [ { - label: 'Single-Cropping', - data: single, - backgroundColor: '#57ad2b', + label: 'Triple-Cropping', + data: triple, + backgroundColor: '#b3561d' }, { @@ -2968,9 +2968,9 @@

MGNREGA works

}, { - label: 'Triple-Cropping', - data: triple, - backgroundColor: '#b3561d' + label: 'Single-Cropping', + data: single, + backgroundColor: '#57ad2b', }, { diff --git a/templates/resource-report.html b/templates/resource-report.html index 2e9f399b..d5deda26 100644 --- a/templates/resource-report.html +++ b/templates/resource-report.html @@ -294,23 +294,23 @@

Village Overview over LULC

Barren Lands
-
+
Single Kharif
-
+
Single Non-Kharif
-
+
Double Cropping
-
+
Tripple/Annual/Perennial Cropping
-
+
Shrubs and Scrubs
diff --git a/templates/village-report.html b/templates/village-report.html index ce064093..f78a00c8 100644 --- a/templates/village-report.html +++ b/templates/village-report.html @@ -5,7 +5,10 @@ Village Report - {{ village_name }} - + + + + @@ -22,8 +25,11 @@ + + + + + - .chart-description { - font-size: 14px; - color: var(--text-secondary); - line-height: 1.8; - padding: 16px; - background-color: rgba(232, 220, 200, 0.4); - border-left: 4px solid var(--primary-accent); - margin-bottom: 24px; - border-radius: 2px; - } + + +
+
+

Village Development Report

+

Comprehensive Analysis of Village Profile & Development Metrics

+
+
- .chart-description p { - margin: 0; - } +
+
- /* ===== PRINT STYLES FOR CHARTS ===== */ - @media print { - .chart-container { - page-break-inside: avoid; - break-inside: avoid; - background-color: white; - border: 1px solid #ccc; - } - } - - - -
- -
-

Village Profile

+
+

Village Profile

-
+
+ Location: {{ state }} + {{ district }} + {{ block }} - ID: {{ village_id }} + + ID: {{ village_id }}
-
+
{% if village_name %} - Village {{ village_name }} + Village {{ village_name }} {% if gram_panchayat_name %} lies in Gram Panchayat {{ gram_panchayat_name }} {% endif %} @@ -320,233 +211,4489 @@

Village Profile

in {{ block }}, {{ district }}. {% endif %} {% else %} - Village in {{ block }}, {{ district }}, {{ state }}. + Village in {{ block }}, {{ district }}, {{ state }}. {% endif %}
-
-
-

Village Development Overview

+
+

Village Development Overview

+
-
-

The radar chart above illustrates the village's development across ten key dimensions, each rated on a scale of 0.25 to 1.00. This multidimensional assessment provides insights into infrastructure, health, education, financial inclusion, governance, livelihoods, agriculture, and environmental resilience.

+ +
+
+
+ High (0.67 - 1.00) +
+
+
+ Moderate (0.34 - 0.66) +
+
+
+ Low (0.00 - 0.33) +
- + +
+

Demographic Details

-
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValue
Total Population{{ demographic_data.TOT_P }}
Male{{ demographic_data.TOT_M }}
Female{{ demographic_data.TOT_F }}
Number of Households{{ demographic_data.No_HH }}
Scheduled Caste Population{{ demographic_data.P_SC }} ({{ demographic_data.sc_percentage }}%)
Male {{ demographic_data.M_SC }}
Female {{ demographic_data.F_SC }}
Scheduled Tribe Population{{ demographic_data.P_ST }} ({{ demographic_data.st_percentage }}%)
Male {{ demographic_data.M_ST }}
Female {{ demographic_data.F_ST }}
Literate Population{{ demographic_data.P_LIT }} ({{ demographic_data.literacy_percentage }}%)
Male{{ demographic_data.M_LIT }}
Female{{ demographic_data.F_LIT }}
Aggregate Development Index (ADI 2011){{ demographic_data.ADI_2011 }}
+
- + + From c7691e14804dc2d18c636a623a9e7146a6daa6af Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Thu, 18 Jun 2026 11:12:30 +0530 Subject: [PATCH 03/12] Chore : Updated Village Report and MWS Report --- dpr/api.py | 54 +- dpr/gen_mws_report.py | 80 +- dpr/gen_village_report.py | 1538 ++++++--- nrm_app/settings.py | 1 + templates/js/climate_vulnerability_section.js | 292 ++ templates/{ => js}/global_village_fetcher.js | 0 templates/js/village_map_utils.js | 491 +++ templates/mws-report.html | 189 +- templates/village-report-unavailable.html | 106 + templates/village-report.html | 3064 ++++------------- 10 files changed, 2893 insertions(+), 2922 deletions(-) create mode 100644 templates/js/climate_vulnerability_section.js rename templates/{ => js}/global_village_fetcher.js (100%) create mode 100644 templates/js/village_map_utils.js create mode 100644 templates/village-report-unavailable.html diff --git a/dpr/api.py b/dpr/api.py index 286ea3f2..2b30a763 100644 --- a/dpr/api.py +++ b/dpr/api.py @@ -15,6 +15,7 @@ from utilities.auth_check_decorator import api_security_check from utilities.auth_utils import auth_free from utilities.logger import setup_logger +from nrm_app.settings import BASE_API_URL from .gen_dpr import ( get_plan_details, @@ -66,6 +67,7 @@ get_factory_data, get_mining_data, get_green_credit_data, + get_intersecting_village_ids ) from .gen_tehsil_report import ( get_tehsil_data, @@ -98,8 +100,16 @@ get_agri_support_service, get_ecological_climate_resiliance, get_all_villages_basic_infrastructure, + get_all_villages_health_and_wash, get_all_villages_education_institutions, - get_mwses_boundaries, + get_all_villages_financial_inclusion, + get_all_villages_welfare_inclusion, + get_all_villages_community_institutes, + get_all_villages_livestock_management, + get_all_villages_irrigation_infra, + get_all_villages_agri_support_service, + get_all_villages_ecological_climate_resiliance, + get_mwses_ids, ) from .gen_report_download import render_pdf_with_firefox from .utils import validate_email, transform_name @@ -322,10 +332,14 @@ def generate_mws_report(request): green_credits = get_green_credit_data(state, district, block, uid) + village_ids = get_intersecting_village_ids(state, district, block, uid) + context = { + "state" : state, "district": district, "block": block, "mws_id": uid, + "base_url" : BASE_API_URL, "block_osm": parameter_block, "mws_osm": parameter_mws, "terrain_mws": terrain_mws, @@ -387,6 +401,7 @@ def generate_mws_report(request): "factory_desc": factory_desc, "mining_desc": mining_desc, "green_credit_desc": green_credits, + "village_ids" : json.dumps(village_ids) } # print("Api Processing End 1", datetime.now()) @@ -591,6 +606,13 @@ def generate_village_report(request): district = request.GET.get('district') block = request.GET.get('block') village_id = request.GET.get('villageId') + + if village_id == '0': + return render(request, 'village-report-unavailable.html', { + 'state': state, + 'district': district, + 'block': block, + }) # Get village polygon and info from GeoServer village_data = get_village_polygon_and_info(state, district, block, village_id) @@ -656,9 +678,18 @@ def generate_village_report(request): #Map Data basic_infra_map = get_all_villages_basic_infrastructure(state, district, block) + health_wash_map = get_all_villages_health_and_wash(state, district, block) education_map = get_all_villages_education_institutions(state, district, block) - mwses_data = get_mwses_boundaries(state, district, block, village_id) - + financial_map = get_all_villages_financial_inclusion(state, district, block) + welfare_map = get_all_villages_welfare_inclusion(state, district, block) + community_map = get_all_villages_community_institutes(state, district, block) + livestock_map = get_all_villages_livestock_management(state, district, block) + irrigation_infra_map = get_all_villages_irrigation_infra(state, district, block) + agri_support_map = get_all_villages_agri_support_service(state, district, block) + climate_resiliance_map = get_all_villages_ecological_climate_resiliance(state, district, block) + + mws_ids = get_mwses_ids(state, district, block, village_id) + mws_pattern_intensity = get_pattern_intensity(state, district, block) # Build context for template context = { @@ -667,6 +698,7 @@ def generate_village_report(request): "district": district, "block": block, "village_id": village_id, + "base_url" : BASE_API_URL, "village_name": village_data['village_name'], "gram_panchayat_name": village_data['gram_panchayat_name'], "area_hectares": village_data['area_hectares'], @@ -715,10 +747,22 @@ def generate_village_report(request): "climate_resiliance_data" : json.dumps(climate_resiliance_data), "village_polygon": json.dumps(village_data['village_polygon']), - "mwses_polygon": json.dumps(mwses_data['polygon']), "basic_infra_map" : json.dumps(basic_infra_map), - "education_map" : json.dumps(education_map) + "health_wash_map" : json.dumps(health_wash_map), + "education_map" : json.dumps(education_map), + "financial_map" : json.dumps(financial_map), + "welfare_map" : json.dumps(welfare_map), + "community_map" : json.dumps(community_map), + "livestock_map" : json.dumps(livestock_map), + "irrigation_infra_map" : json.dumps(irrigation_infra_map), + "agri_support_map" : json.dumps(agri_support_map), + "climate_resiliance_map" : json.dumps(climate_resiliance_map), + + "mws_ids" : json.dumps(mws_ids), + "pattern_intensity": json.dumps(mws_pattern_intensity['intensity']), + "mws_active_patterns": json.dumps(mws_pattern_intensity['mws_active_patterns']), + "pattern_display_mapping": json.dumps(mws_pattern_intensity['pattern_display_mapping']), } return render(request, 'village-report.html', context) diff --git a/dpr/gen_mws_report.py b/dpr/gen_mws_report.py index 317f6a89..642d23ba 100644 --- a/dpr/gen_mws_report.py +++ b/dpr/gen_mws_report.py @@ -2615,4 +2615,82 @@ def get_village_data(state, district, block, uid): district, block, ) - return [], [], [], [], [], [], [], [], [], [], [] \ No newline at end of file + return [], [], [], [], [], [], [], [], [], [], [] + + + +import ast + +def get_intersecting_village_ids(state, district, block, mws_uid): + """ + Reads the mws_intersect_villages sheet and returns the list of + village IDs that intersect with the given MWS UID. + """ + try: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + available_sheets = excel_file.sheet_names + + if "mws_intersect_villages" not in available_sheets: + logger.info( + "mws_intersect_villages sheet not found for %s district, %s block", + district, + block + ) + return [] + + df = pd.read_excel(file_path, sheet_name="mws_intersect_villages") + + if "Village IDs" not in df.columns: + logger.info( + "Village IDs column not found in mws_intersect_villages sheet for %s district, %s block", + district, + block + ) + return [] + + matching = df.loc[df["MWS UID"] == mws_uid, "Village IDs"] + + if matching.empty: + return [] + + raw_value = matching.iloc[0] + + if pd.isna(raw_value): + return [] + + try: + village_ids = ast.literal_eval(raw_value) + except (ValueError, SyntaxError) as parse_error: + logger.info( + "Could not parse Village IDs for MWS UID %s in %s district, %s block: %s", + mws_uid, + district, + block, + parse_error + ) + return [] + + if not isinstance(village_ids, list): + return [] + + return village_ids + + except Exception as e: + logger.info( + "Error accessing excel for %s district, %s block: %s", + district, + block, + ) + return [] \ No newline at end of file diff --git a/dpr/gen_village_report.py b/dpr/gen_village_report.py index cf6e625d..fafcb67c 100644 --- a/dpr/gen_village_report.py +++ b/dpr/gen_village_report.py @@ -6,9 +6,6 @@ import pymannkendall as mk import json import ast -from shapely.geometry import shape, mapping -from shapely.wkt import dumps as shapely_to_wkt -from shapely.ops import unary_union from nrm_app.settings import EXCEL_DIR, GEOSERVER_URL, OVERPASS_URL from utilities.logger import setup_logger @@ -79,312 +76,6 @@ def calculate_demographics(properties): return demographic_data -def _convert_wkt_to_gml_coordinates(wkt): - """ - Convert WKT coordinates to GML format. - Example: "POLYGON ((x1 y1, x2 y2, ...))" → "x1 y1x2 y2..." - """ - try: - # Extract coordinates from WKT - # WKT format: "POLYGON ((lon lat, lon lat, ...))" - import re - - # Extract the coordinate string - coords_str = re.search(r'\(\((.*?)\)\)', wkt).group(1) - - # Split into individual coordinates - coords = coords_str.split(',') - - # Convert to GML format - gml_coords = [] - for coord in coords: - coord = coord.strip() - if coord: - gml_coords.append(f"{coord}") - - return '\n '.join(gml_coords) - - except Exception as e: - print(f"[WARN] Error converting WKT to GML: {str(e)}") - return "" - - -def _try_bbox_query(layer_name, bounds): - """ - Fallback: Query using bounding box instead of spatial intersection. - """ - - try: - minx, miny, maxx, maxy = bounds - - # Add small buffer - buffer = 0.01 - minx -= buffer - miny -= buffer - maxx += buffer - maxy += buffer - - print(f"[DEBUG] BBOX query bounds: {minx}, {miny}, {maxx}, {maxy}") - - wfs_url = f"{GEOSERVER_URL}/wfs" - - params = { - 'service': 'WFS', - 'version': '2.0.0', - 'request': 'GetFeature', - 'typeNames': f"mws_layers:{layer_name}", - 'outputFormat': 'application/json', - 'bbox': f"{minx},{miny},{maxx},{maxy},urn:ogc:def:crs:EPSG:4326", - 'srsName': 'EPSG:4326' - } - - response = requests.get(wfs_url, params=params, timeout=30) - - if response.status_code == 200: - mws_data = response.json() - - if mws_data.get('features'): - print(f"[SUCCESS] BBOX query found {len(mws_data['features'])} features") - return { - 'success': True, - 'features': mws_data['features'], - 'count': len(mws_data['features']), - 'error': None - } - else: - print(f"[INFO] BBOX query returned 0 features") - return { - 'success': True, - 'features': [], - 'count': 0, - 'error': 'No features in bbox' - } - else: - error_msg = f"Status {response.status_code}" - print(f"[ERROR] BBOX query failed: {error_msg}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': error_msg - } - - except Exception as e: - print(f"[ERROR] BBOX query exception: {str(e)}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': str(e) - } - - -def test_mws_layer(district, block): - """ - Test if the MWSes layer exists in GeoServer. - """ - - mws_layer_name = f"deltaG_well_depth_{district}_{block}".lower() - - try: - wfs_url = f"{GEOSERVER_URL}/wfs" - - params = { - 'service': 'WFS', - 'version': '2.0.0', - 'request': 'GetFeature', - 'typeNames': f"mws_layers:{mws_layer_name}", - 'outputFormat': 'application/json', - 'maxFeatures': 1 - } - - response = requests.get(wfs_url, params=params, timeout=10) - - if response.status_code == 200: - print(f"✓ Layer '{mws_layer_name}' exists") - data = response.json() - features_count = len(data.get('features', [])) - print(f" Features available: {features_count}") - if data.get('features'): - print(f" Geometry type: {data['features'][0].get('geometry', {}).get('type')}") - print(f" Sample properties: {list(data['features'][0].get('properties', {}).keys())[:5]}") - return True - else: - print(f"✗ Layer '{mws_layer_name}' returned status {response.status_code}") - return False - - except Exception as e: - print(f"✗ Error testing layer: {str(e)}") - return False - - -def _try_post_intersects_query(layer_name, village_wkt): - """ - Try INTERSECTS spatial filter using POST request. - POST avoids URL length limits for complex geometries. - """ - - try: - print(f"[DEBUG] Attempting POST INTERSECTS query...") - - wfs_url = f"{GEOSERVER_URL}/wfs" - - # Build WFS query with CQL filter in request body - wfs_query = f""" - - - - - the_geom - - - - {_convert_wkt_to_gml_coordinates(village_wkt)} - - - - - -""" - - headers = { - 'Content-Type': 'application/xml' - } - - response = requests.post(wfs_url, data=wfs_query, headers=headers, timeout=30) - print(f"[DEBUG] POST response status: {response.status_code}") - - if response.status_code == 200: - mws_data = response.json() - if mws_data.get('features'): - print(f"[SUCCESS] Found {len(mws_data['features'])} features") - return { - 'success': True, - 'features': mws_data['features'], - 'count': len(mws_data['features']), - 'error': None - } - else: - print(f"[INFO] No features found") - return { - 'success': True, - 'features': [], - 'count': 0, - 'error': 'No intersecting features' - } - else: - error_msg = f"Status {response.status_code}: {response.text[:200]}" - print(f"[WARN] POST query failed: {error_msg}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': error_msg - } - - except Exception as e: - print(f"[WARN] POST query exception: {str(e)}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': str(e) - } - - -def get_mwses_boundaries(state, district, block, village_id): - """ - Get all MWSes (Micro-Watersheds) boundaries that intersect with the given village. - Uses POST requests to avoid URL length limits with complex geometries. - """ - - try: - # Step 1: Get village geometry - village_data = get_village_polygon_and_info(state, district, block, village_id) - - if not village_data or not village_data.get('village_polygon'): - return {'polygon': {}, 'count': 0, 'error': 'Village polygon not found'} - - village_geom = village_data['village_polygon'] - print(f"[DEBUG] village_geom type: {type(village_geom)}") - - # Step 2: Convert to Shapely - village_shapely = None - - if isinstance(village_geom, dict): - if village_geom.get('type') == 'FeatureCollection': - geometries = [] - for feature in village_geom.get('features', []): - if 'geometry' in feature: - geometries.append(shape(feature['geometry'])) - village_shapely = unary_union(geometries) - print(f"[DEBUG] Converted FeatureCollection to Shapely (unary_union)") - elif village_geom.get('type') == 'Feature': - village_shapely = shape(village_geom['geometry']) - print(f"[DEBUG] Converted Feature to Shapely") - elif village_geom.get('type') in ['Polygon', 'MultiPolygon', 'Point', 'LineString']: - village_shapely = shape(village_geom) - print(f"[DEBUG] Converted {village_geom.get('type')} to Shapely") - - if village_shapely is None: - return {'polygon': {}, 'count': 0, 'error': 'Could not convert village geometry'} - - # Step 3: Query MWSes layer - mws_layer_name = f"deltaG_well_depth_{district}_{block}".lower() - print(f"[DEBUG] Querying layer: {mws_layer_name}") - - # Convert to WKT - village_wkt = shapely_to_wkt(village_shapely) - print(f"[DEBUG] Village WKT length: {len(village_wkt)} characters") - - # Step 4: Try POST request with INTERSECTS (avoids URL length limits) - result = _try_post_intersects_query(mws_layer_name, village_wkt) - - if result['success']: - print(f"[SUCCESS] POST INTERSECTS query returned {result['count']} features") - return { - 'polygon': {'type': 'FeatureCollection', 'features': result['features']}, - 'count': result['count'], - 'error': None - } - - print(f"[WARNING] POST INTERSECTS query failed: {result['error']}") - - # Step 5: Fallback to BBOX query - print(f"[INFO] Trying fallback with BBOX query...") - bounds = village_shapely.bounds - result = _try_bbox_query(mws_layer_name, bounds) - - if result['success']: - print(f"[SUCCESS] BBOX query returned {result['count']} features") - return { - 'polygon': {'type': 'FeatureCollection', 'features': result['features']}, - 'count': result['count'], - 'error': None - } - - print(f"[ERROR] BBOX query also failed: {result['error']}") - return { - 'polygon': {}, - 'count': 0, - 'error': f"Both queries failed. BBOX error: {result['error']}" - } - - except Exception as e: - print(f"[EXCEPTION] {str(e)}") - import traceback - traceback.print_exc() - return { - 'polygon': {}, - 'count': 0, - 'error': f'Error: {str(e)}' - } - - def get_mwses_ids(state, district, block, village_id): try: @@ -1478,36 +1169,43 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_basic_infrastructure(state, district, block, village_id): - +def get_basic_infrastructure(state, district, block, village_id, df=None): + def safe_float(value, default=0): try: return float(value) except: return default - + try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - excel_file = pd.ExcelFile(file_path) + # Only load excel if dataframe not supplied + if df is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - df = pd.read_excel(excel_file, sheet_name="antyodaya") + excel_file = pd.ExcelFile(file_path) - df["village_id"] = ( - df["village_id"] - .astype(str) - .str.strip() - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -1516,35 +1214,38 @@ def safe_float(value, default=0): ] if matched_rows.empty: - logger.info( - "No data found for village_id %s in %s district, %s block", - village_id, - district, - block, - ) return [] row = matched_rows.iloc[0] - road_score = safe_float(row.get("road_connectivity_cat_value", 0)) - - energy_score = safe_float(row.get("energy_access_cat_value", 0)) - - housing_score = safe_float(row.get("housing_quality_cat_value", 0)) - return [ - road_score, - energy_score, - housing_score + safe_float( + row.get( + "road_connectivity_cat_value", + 0 + ) + ), + safe_float( + row.get( + "energy_access_cat_value", + 0 + ) + ), + safe_float( + row.get( + "housing_quality_cat_value", + 0 + ) + ) ] except Exception as e: + logger.info( - "Not able to access excel for %s district, %s block. Error: %s", - district, - block, + "Not able to access infrastructure data. Error: %s", str(e), ) + return [] @@ -1684,7 +1385,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_education_institutions(state, district, block, village_id): +def get_education_institutions(state, district, block, village_id, df_facilities=None): def get_numeric(row, column): return pd.to_numeric(row.get(column, None), errors="coerce") @@ -1705,30 +1406,32 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) - df_facilities["censuscode2011"] = ( - df_facilities["censuscode2011"] - .astype(str) - .str.strip() - ) + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -1802,7 +1505,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_financial_inclusion(state, district, block, village_id): +def get_financial_inclusion(state, district, block, village_id, df_facilities=None): def get_numeric(row, column): return pd.to_numeric(row.get(column, None), errors="coerce") @@ -1839,30 +1542,31 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df_facilities is None: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) - df_facilities["censuscode2011"] = ( - df_facilities["censuscode2011"] - .astype(str) - .str.strip() - ) + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -1920,7 +1624,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_welfare_inclusion(state, district, block, village_id): +def get_welfare_inclusion(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -1932,30 +1636,47 @@ def get_numeric(row, column): return pd.to_numeric(row.get(column, None), errors="coerce") try: + if df is None or df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + excel_file = pd.ExcelFile(file_path) - excel_file = pd.ExcelFile(file_path) + if df is None: - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + if df_facilities is None: + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) df["village_id"] = ( df["village_id"] @@ -2036,7 +1757,7 @@ def get_numeric(row, column): return [] -def get_community_institutes(state, district, block, village_id): +def get_community_institutes(state, district, block, village_id, df=None): def safe_float(value, default=0): try: @@ -2046,24 +1767,26 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) df["village_id"] = ( df["village_id"] @@ -2204,7 +1927,7 @@ def safe_float(value, default=0): return [] -def get_livestock_management(state, district, block, village_id): +def get_livestock_management(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -2217,29 +1940,33 @@ def get_numeric(row, column): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None or df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df_facilities is None: + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) df["village_id"] = ( df["village_id"] @@ -2386,7 +2113,7 @@ def safe_float(value, default=0): return [] -def get_irrigation_Infra(state, district, block, village_id): +def get_irrigation_Infra(state, district, block, village_id, df=None): def safe_float(value, default=0): try: @@ -2396,24 +2123,26 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) df["village_id"] = ( df["village_id"] @@ -2488,7 +2217,7 @@ def safe_float(value, default=0): return [] -def get_agri_support_service(state, district, block, village_id): +def get_agri_support_service(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -2515,29 +2244,33 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None or df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df_facilities is None: + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) df["village_id"] = ( df["village_id"] @@ -2669,7 +2402,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_ecological_climate_resiliance(state,district,block,village_id): +def get_ecological_climate_resiliance(state, district, block, village_id, df=None, df_nrega=None): def safe_float(value, default=0): try: @@ -2679,29 +2412,33 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None or df_nrega is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) + if df_nrega is None: + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) df["village_id"] = ( df["village_id"] @@ -2757,8 +2494,10 @@ def safe_float(value, default=0): if nrega_match.empty: return [ - None, - {}, + None, # year_range + {}, # category_counts + 0, # total_work_count + "red", # nrega_work_color organic_farming_score, organic_farming_color ] @@ -2774,8 +2513,8 @@ def safe_float(value, default=0): if column in ["vill_id", "vill_name"]: continue - # Split from right to extract year try: + work_type, year = column.rsplit("_", 1) year = int(year) @@ -2806,11 +2545,23 @@ def safe_float(value, default=0): "to_year": max(years) if years else None, } + total_work_count = sum( + category_counts.values() + ) + + nrega_work_color = ( + "green" + if total_work_count > 100 + else "red" + ) + return [ - year_range, - category_counts, - organic_farming_score, - organic_farming_color + year_range, # index 0 + category_counts, # index 1 + total_work_count, # index 2 + nrega_work_color, # index 3 + organic_farming_score, # index 4 + organic_farming_color # index 5 ] except Exception as e: @@ -2826,12 +2577,9 @@ def safe_float(value, default=0): - #? Get Tehsil Map Data def get_all_villages_basic_infrastructure(state, district, block): - try: - file_path = ( DATA_DIR_TEMP + state.upper() @@ -2846,15 +2594,41 @@ def get_all_villages_basic_infrastructure(state, district, block): excel_file = pd.ExcelFile(file_path) - df_nrega = pd.read_excel(excel_file,sheet_name="nrega_assets_village") + # Read once + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + village_ids = ( + df_nrega["vill_id"] + .dropna() + .astype(str) + .str.strip() + ) - village_ids = (df_nrega["vill_id"].dropna().astype(str).unique()) + village_ids = [ + vid + for vid in village_ids.unique() + if vid and vid != "0" + ] result = {} for village_id in village_ids: - village_data = get_basic_infrastructure(state, district, block, village_id) + village_data = get_basic_infrastructure(state, district, block, village_id, df=df) if not village_data: @@ -2866,9 +2640,7 @@ def get_all_villages_basic_infrastructure(state, district, block): continue - road_score = village_data[0] - energy_score = village_data[1] - housing_score = village_data[2] + road_score, energy_score, housing_score = village_data result[village_id] = { @@ -2903,9 +2675,128 @@ def get_all_villages_basic_infrastructure(state, district, block): ) return {} - -def get_all_villages_education_institutions(state, district, block): + +def get_all_villages_health_and_wash(state, district, block): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + + village_data[village_id] = { + "maternal_child_health_color": "black", + "water_sanitation_color": "black" + } + + continue + + row = matched_rows.iloc[0] + + maternal_child_score = safe_float( + row.get( + "maternal_child_health_cat_value", + 0 + ) + ) + + water_sanitation_score = safe_float( + row.get( + "water_sanitation_cat_value", + 0 + ) + ) + + # Maternal Child Health Color + + if maternal_child_score <= 0.33: + maternal_child_health_color = "red" + + elif maternal_child_score <= 0.66: + maternal_child_health_color = "yellow" + + else: + maternal_child_health_color = "green" + + # Water & Sanitation Color + + if water_sanitation_score <= 0.33: + water_sanitation_color = "red" + + elif water_sanitation_score <= 0.66: + water_sanitation_color = "yellow" + + else: + water_sanitation_color = "green" + + village_data[village_id] = { + "maternal_child_health_color": ( + maternal_child_health_color + ), + "water_sanitation_color": ( + water_sanitation_color + ) + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access health and wash data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_education_institutions( state, district, block): try: file_path = ( @@ -2922,20 +2813,40 @@ def get_all_villages_education_institutions(state, district, block): excel_file = pd.ExcelFile(file_path) - df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) village_ids = ( df_nrega["vill_id"] .dropna() .astype(str) - .unique() + .str.strip() ) + village_ids = [ + vid + for vid in village_ids.unique() + if vid and vid != "0" + ] + result = {} for village_id in village_ids: - education_data = get_education_institutions(state,district, block, village_id ) + education_data = get_education_institutions(state, district, block, village_id, df_facilities=df_facilities) if not education_data: @@ -2961,3 +2872,568 @@ def get_all_villages_education_institutions(state, district, block): return {} +def get_all_villages_financial_inclusion(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_ids = ( + df_nrega["vill_id"] + .dropna() + .astype(str) + .str.strip() + ) + + village_ids = [ + vid + for vid in village_ids.unique() + if vid and vid != "0" + ] + + result = {} + + for village_id in village_ids: + + financial_data = get_financial_inclusion( + state, + district, + block, + village_id, + df_facilities=df_facilities + ) + + if not financial_data: + + result[village_id] = { + "financial_color": "black" + } + + continue + + result[village_id] = { + "financial_color": financial_data[2] + } + + return result + + except Exception as e: + + logger.info( + "Error calculating financial inclusion colors for all villages: %s", + str(e) + ) + + return {} + + +def get_all_villages_welfare_inclusion(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + village_ids = [ + str(v).strip() + for v in df_nrega["vill_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + result = {} + + for village_id in village_ids: + + welfare_data = get_welfare_inclusion( + state, + district, + block, + village_id, + df=df, + df_facilities=df_facilities + ) + + if not welfare_data: + + result[village_id] = { + "welfare_color": "black" + } + + continue + + result[village_id] = { + "welfare_color": welfare_data[2] + } + + return result + + except Exception as e: + + logger.info( + "Error calculating welfare inclusion colors for all villages: %s", + str(e) + ) + + return {} + + +def get_all_villages_community_institutes(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + community_info = get_community_institutes( + state, + district, + block, + village_id, + df=df + ) + + if not community_info: + + village_data[village_id] = { + "community_color": "black", + "civic_color": "black" + } + + continue + + village_data[village_id] = { + "community_color": community_info[2], + "civic_color": community_info[3] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access community institution data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_livestock_management(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + livestock_info = get_livestock_management( + state, + district, + block, + village_id, + df=df, + df_facilities=df_facilities + ) + + if not livestock_info: + + village_data[village_id] = { + "veterinary_color": "black", + "pasture_color": "black" + } + + continue + + village_data[village_id] = { + "veterinary_color": livestock_info[3], + "pasture_color": livestock_info[4] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access livestock management data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_irrigation_infra(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + irrigation_info = get_irrigation_Infra( + state, + district, + block, + village_id, + df=df + ) + + if not irrigation_info: + + village_data[village_id] = { + "irrigation_watershed_color": "black", + "modern_irrigation_color": "black" + } + + continue + + village_data[village_id] = { + "irrigation_watershed_color": irrigation_info[2], + "modern_irrigation_color": irrigation_info[3] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access irrigation infrastructure data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_agri_support_service(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + agri_info = get_agri_support_service( + state, + district, + block, + village_id, + df=df, + df_facilities=df_facilities + ) + + if not agri_info: + + village_data[village_id] = { + "agri_support_color": "black", + "agri_market_color": "black" + } + + continue + + village_data[village_id] = { + "agri_support_color": agri_info[4], + "agri_market_color": agri_info[5] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access agri support service data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_ecological_climate_resiliance(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df_nrega["vill_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + ecology_info = ( + get_ecological_climate_resiliance( + state, + district, + block, + village_id, + df=df, + df_nrega=df_nrega + ) + ) + + if not ecology_info: + + village_data[village_id] = { + "organic_farming_color": "black", + "nrega_work_color": "black" + } + + continue + + village_data[village_id] = { + "organic_farming_color": ecology_info[5], + "nrega_work_color": ecology_info[3] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access ecology data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 19a80786..f4237030 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -343,6 +343,7 @@ def resolve_env_path(name, default="", *, trailing_sep=False): # MARK: Report requirements OVERPASS_URL = env("OVERPASS_URL") +BASE_API_URL = env("BASE_API_URL") # MARK: Email Settings EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" diff --git a/templates/js/climate_vulnerability_section.js b/templates/js/climate_vulnerability_section.js new file mode 100644 index 00000000..26c1c3f5 --- /dev/null +++ b/templates/js/climate_vulnerability_section.js @@ -0,0 +1,292 @@ +// ===== CLIMATE VULNERABILITY MAP ===== +// Shows: Village boundary (yellow) + MWS boundaries (gradient green→red by stress) + Click popup with patterns + +if (document.getElementById('climateVulnerabilityMap')) { + + // Parse context data + let mwsIds = []; + let climatePatterns = {}; + + try { + mwsIds = JSON.parse('{{ mws_ids|escapejs }}'); + } catch (e) { + console.warn('Could not parse mws_ids:', e); + } + + let intensity = {}; + let mwsActivePatterns = {}; + let patternDisplayMapping = {}; + + try { + intensity = JSON.parse('{{ pattern_intensity|escapejs }}'); + } catch (e) { + console.warn('Could not parse pattern_intensity:', e); + } + + try { + mwsActivePatterns = JSON.parse('{{ mws_active_patterns|escapejs }}'); + } catch (e) { + console.warn('Could not parse mws_active_patterns:', e); + } + + try { + patternDisplayMapping = JSON.parse('{{ pattern_display_mapping|escapejs }}'); + } catch (e) { + console.warn('Could not parse pattern_display_mapping:', e); + } + + // Find max intensity for gradient normalization + const intensityValues = Object.values(intensity).filter(v => typeof v === 'number'); + const maxIntensity = Math.max(...intensityValues, 1); // At least 1 to avoid division by zero + + /** + * Get gradient color based on stress pattern count + * 0 = Green, mid = Yellow, max = Red + */ + function getStressColor(count) { + if (count === 0) return { fill: 'rgba(34, 197, 94, 0.45)', stroke: '#16a34a' }; + + const ratio = Math.min(count / maxIntensity, 1); + + if (ratio <= 0.5) { + // Green → Yellow (0 → 0.5) + const t = ratio * 2; + const r = Math.round(34 + (234 - 34) * t); + const g = Math.round(197 + (179 - 197) * t); + const b = Math.round(94 + (8 - 94) * t); + return { + fill: `rgba(${r}, ${g}, ${b}, 0.5)`, + stroke: `rgb(${Math.round(r * 0.8)}, ${Math.round(g * 0.8)}, ${Math.round(b * 0.8)})` + }; + } else { + // Yellow → Red (0.5 → 1) + const t = (ratio - 0.5) * 2; + const r = Math.round(234 + (239 - 234) * t); + const g = Math.round(179 + (68 - 179) * t); + const b = Math.round(8 + (68 - 8) * t); + return { + fill: `rgba(${r}, ${g}, ${b}, 0.5)`, + stroke: `rgb(${Math.round(r * 0.8)}, ${Math.round(g * 0.8)}, ${Math.round(b * 0.8)})` + }; + } + } + + // Create map + const climateVulnerabilityMap = new ol.Map({ + target: 'climateVulnerabilityMap', + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + attributions: 'Google Satellite Hybrid Contributors' + }), + zIndex: 1 + }) + ], + view: new ol.View({ + center: [0, 0], + zoom: 12, + projection: "EPSG:4326" + }) + }); + + // ===== 1. VILLAGE BOUNDARY (Yellow stroke, single village) ===== + try { + const villagePolygon = JSON.parse('{{ village_polygon|escapejs }}'); + + if (villagePolygon && villagePolygon.features && villagePolygon.features.length > 0) { + const villageSource = new ol.source.Vector({ + features: new ol.format.GeoJSON().readFeatures(villagePolygon, { + featureProjection: 'EPSG:4326' + }) + }); + + const villageLayer = new ol.layer.Vector({ + source: villageSource, + style: new ol.style.Style({ + fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.1)' }), + stroke: new ol.style.Stroke({ color: '#eab308', width: 2.5 }) + }), + zIndex: 10 + }); + + climateVulnerabilityMap.addLayer(villageLayer); + + const extent = villageSource.getExtent(); + if (extent && !ol.extent.isEmpty(extent)) { + climateVulnerabilityMap.getView().fit(extent, { + padding: [50, 50, 50, 50], + maxZoom: 14, + duration: 500 + }); + } + } + } catch (error) { + console.warn('Climate Vulnerability: Village polygon error:', error); + } + + // ===== 2. FETCH & COLOR MWS BOUNDARIES ===== + (async () => { + try { + if (!mwsIds || mwsIds.length === 0) { + console.warn('Climate Vulnerability: No MWS IDs provided'); + return; + } + + const mwsUrl = `https://geoserver.core-stack.org:8443/geoserver/mws_layers/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=mws_layers:deltaG_well_depth_{{ district|lower }}_{{ block|lower }}&outputFormat=application/json`; + + const response = await fetch(mwsUrl); + if (!response.ok) throw new Error(`GeoServer error: ${response.status}`); + + const mwsData = await response.json(); + + if (!mwsData.features || mwsData.features.length === 0) return; + + // Filter by mws_ids + const filteredFeatures = mwsData.features.filter(f => mwsIds.includes(f.properties.uid)); + + if (filteredFeatures.length === 0) return; + + const filteredFC = { type: "FeatureCollection", features: filteredFeatures }; + const mwsSource = new ol.source.Vector({ + features: new ol.format.GeoJSON().readFeatures(filteredFC, { + featureProjection: 'EPSG:4326' + }) + }); + + // Style each MWS based on stress intensity + const mwsLayer = new ol.layer.Vector({ + source: mwsSource, + style: function(feature) { + const uid = String(feature.get('uid')); + const count = intensity[uid] !== undefined ? intensity[uid] : 0; + const colors = getStressColor(count); + + return new ol.style.Style({ + fill: new ol.style.Fill({ color: colors.fill }), + stroke: new ol.style.Stroke({ color: colors.stroke, width: 2 }) + }); + }, + zIndex: 5 + }); + + climateVulnerabilityMap.addLayer(mwsLayer); + } catch (error) { + console.error('Climate Vulnerability: Error fetching MWS layer:', error); + } + })(); + + // ===== 3. POPUP WITH PATTERNS ===== + const popupContainer = document.createElement('div'); + popupContainer.id = 'mws-popup'; + popupContainer.style.cssText = ` + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + padding: 16px; + min-width: 250px; + max-width: 320px; + font-family: 'Georgia', 'Garamond', serif; + font-size: 13px; + display: none; + position: relative; + `; + document.body.appendChild(popupContainer); + + const popupOverlay = new ol.Overlay({ + element: popupContainer, + autoPan: true, + autoPanAnimation: { duration: 250 } + }); + climateVulnerabilityMap.addOverlay(popupOverlay); + + // Handle click + climateVulnerabilityMap.on('singleclick', function(evt) { + let featureFound = false; + + climateVulnerabilityMap.forEachFeatureAtPixel(evt.pixel, function(feature, layer) { + if (layer && layer.getZIndex && layer.getZIndex() === 5) { + const uid = String(feature.get('uid')); + const areaHa = feature.get('area_in_ha'); + + if (uid) { + const state = '{{ state|lower }}'; + const district = '{{ district|lower }}'; + const block = '{{ block|lower }}'; + const reportUrl = `{{ base_url }}/generate_mws_report/?state=${state}&district=${district}&block=${block}&uid=${uid}`; + + const count = intensity[uid] !== undefined ? intensity[uid] : 0; + const patterns = mwsActivePatterns[uid] || []; + + // Build patterns HTML + let patternsHtml = ''; + if (patterns.length > 0) { + const patternNames = patterns.map(p => patternDisplayMapping[p] || p); + patternsHtml = ` +
+ Active Stress Patterns: +
    + ${patternNames.map(name => `
  • ${name}
  • `).join('')} +
+
+ `; + } else { + patternsHtml = ` +
+ No active stress patterns +
+ `; + } + + // Stress badge color + const badgeColor = count === 0 ? '#16a34a' : count === 1 ? '#eab308' : '#ef4444'; + + popupContainer.innerHTML = ` +
+
+ MWS ID: + ${uid} +
+ + ${count} ${count === 1 ? 'pattern' : 'patterns'} + +
+ ${areaHa ? `
+ Area: + ${parseFloat(areaHa).toFixed(2)} ha +
` : ''} + ${patternsHtml} + + Show Report + +
+ × +
+ `; + + popupContainer.style.display = 'block'; + popupOverlay.setPosition(evt.coordinate); + featureFound = true; + } + } + }); + + if (!featureFound) { + popupContainer.style.display = 'none'; + } + }); + + // Cursor change on hover + climateVulnerabilityMap.on('pointermove', function(evt) { + const pixel = climateVulnerabilityMap.getEventPixel(evt.originalEvent); + let hit = false; + climateVulnerabilityMap.forEachFeatureAtPixel(pixel, function(feature, layer) { + if (layer && layer.getZIndex && layer.getZIndex() === 5) hit = true; + }); + climateVulnerabilityMap.getTargetElement().style.cursor = hit ? 'pointer' : ''; + }); + + console.log('Climate Vulnerability: Map initialized with stress gradient + pattern popups'); +} \ No newline at end of file diff --git a/templates/global_village_fetcher.js b/templates/js/global_village_fetcher.js similarity index 100% rename from templates/global_village_fetcher.js rename to templates/js/global_village_fetcher.js diff --git a/templates/js/village_map_utils.js b/templates/js/village_map_utils.js new file mode 100644 index 00000000..5b2f442b --- /dev/null +++ b/templates/js/village_map_utils.js @@ -0,0 +1,491 @@ +// ===== VILLAGE MAP UTILITY (Reusable Across All Sections) ===== + +// Shared color mapping +const VILLAGE_COLORS = { + fill: { + 'red': 'rgba(239, 68, 68, 0.6)', + 'yellow': 'rgba(234, 179, 8, 0.6)', + 'green': 'rgba(34, 197, 94, 0.6)', + 'black': 'rgba(0, 0, 0, 0.3)', + 'gray': 'rgba(200, 200, 200, 0.3)' + }, + stroke: { + 'red': '#000000', + 'yellow': '#000000', + 'green': '#000000', + 'black': '#000000', + 'gray': '#000000' + }, + currentVillageStroke: '#000000', + currentVillageStrokeWidth: 3.5, + defaultStrokeWidth: 1.0 +}; + +// Current village ID from context +const CURRENT_VILLAGE_ID = '{{ village_id }}'; +const CURRENT_VILLAGE_NAME = '{{ village_name }}'; + +// ===== SHARED: Create location pin as vector feature ===== +function createLocationPin(map, coordinate) { + // SVG pin encoded for ol.style.Icon + const svgPin = ` + + + + + `; + const svgBlob = new Blob([svgPin], { type: 'image/svg+xml' }); + const svgUrl = URL.createObjectURL(svgBlob); + + // Label feature + const labelFeature = new ol.Feature({ + geometry: new ol.geom.Point(coordinate) + }); + labelFeature.setStyle(new ol.style.Style({ + text: new ol.style.Text({ + text: 'You are here', + offsetY: -38, + font: '11px Georgia, serif', + fill: new ol.style.Fill({ color: '#ffffff' }), + backgroundFill: new ol.style.Fill({ color: 'rgba(62,39,35,0.85)' }), + padding: [3, 6, 3, 6], + backgroundStroke: new ol.style.Stroke({ color: 'transparent', width: 0 }), + textBaseline: 'bottom' + }) + })); + + // Pin icon feature + const pinFeature = new ol.Feature({ + geometry: new ol.geom.Point(coordinate) + }); + pinFeature.setStyle(new ol.style.Style({ + image: new ol.style.Icon({ + src: svgUrl, + anchor: [0.5, 1.0], // anchor at bottom-center of SVG + anchorXUnits: 'fraction', + anchorYUnits: 'fraction', + scale: 1.2 + }) + })); + + const pinSource = new ol.source.Vector({ + features: [labelFeature, pinFeature] + }); + + const pinLayer = new ol.layer.Vector({ + source: pinSource, + zIndex: 20 // above village layer + }); + + map.addLayer(pinLayer); + return pinLayer; +} + +// ===== SHARED: Create zoom toggle button overlay ===== +function createZoomToggle(map, currentFeature, villageName) { + if (!currentFeature) return; + + const tehsilExtent = null; // will be set after villages load — handled by caller + let isZoomedIn = false; + + const btnEl = document.createElement('button'); + btnEl.style.cssText = ` + position: absolute; + top: 10px; + right: 10px; + z-index: 100; + background: white; + border: 1px solid #ccc; + border-radius: 4px; + padding: 5px 10px; + font-size: 11px; + font-family: 'Georgia', serif; + color: var(--primary-dark, #3e2723); + cursor: pointer; + box-shadow: 0 2px 6px rgba(0,0,0,0.2); + white-space: nowrap; + display: flex; + align-items: center; + gap: 5px; + `; + btnEl.innerHTML = ` + + + + + Zoom to ${villageName} + `; + + // Append to map container (which must have position:relative) + map.getTargetElement().style.position = 'relative'; + map.getTargetElement().appendChild(btnEl); + + btnEl.addEventListener('click', function () { + if (!isZoomedIn) { + // Zoom in to current village + const villageExtent = currentFeature.getGeometry().getExtent(); + map.getView().fit(villageExtent, { + padding: [40, 40, 40, 40], + maxZoom: 14, + duration: 600 + }); + isZoomedIn = true; + btnEl.innerHTML = ` + + + + + Show full tehsil + `; + } else { + // Zoom out to full tehsil + map.getView().fit(map._tehsilExtent, { + padding: [30, 30, 30, 30], + duration: 600 + }); + isZoomedIn = false; + btnEl.innerHTML = ` + + + + + Zoom to ${villageName} + `; + } + }); + + return btnEl; +} + +// ===== SHARED: Get centroid of a geometry ===== +function getGeometryCentroid(geometry) { + const extent = geometry.getExtent(); + return [ + (extent[0] + extent[2]) / 2, + (extent[1] + extent[3]) / 2 + ]; +} + +/** + * Create a styled village map section + */ +function createVillageMap(config) { + const container = document.getElementById(config.targetId); + if (!container) return null; + + let mapData = {}; + try { + mapData = JSON.parse(config.mapDataJson); + } catch (e) { + console.warn(config.label + ' Map: Could not parse map data:', e); + } + + function styleFeature(feature) { + const villageId = feature.get('vill_ID'); + const isCurrentVillage = (String(villageId) === String(CURRENT_VILLAGE_ID)); + + let colorName = 'gray'; + if (mapData[villageId] && mapData[villageId][config.colorKey]) { + colorName = mapData[villageId][config.colorKey]; + } + + const fillColor = VILLAGE_COLORS.fill[colorName] || VILLAGE_COLORS.fill['gray']; + const strokeWidth = isCurrentVillage + ? VILLAGE_COLORS.currentVillageStrokeWidth + : VILLAGE_COLORS.defaultStrokeWidth; + + return new ol.style.Style({ + fill: new ol.style.Fill({ color: fillColor }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: strokeWidth + }) + }); + } + + const map = new ol.Map({ + target: config.targetId, + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + attributions: 'Google Satellite Hybrid Contributors' + }), + zIndex: 1 + }) + ], + view: new ol.View({ + center: [0, 0], + zoom: 12, + projection: 'EPSG:4326' + }) + }); + + (async () => { + try { + const geoserverData = await waitForGlobalVillages(); + + if (geoserverData && geoserverData.features) { + const vectorSource = new ol.source.Vector(); + const features = new ol.format.GeoJSON().readFeatures(geoserverData, { + featureProjection: 'EPSG:4326' + }); + + vectorSource.addFeatures(features); + + const villageLayer = new ol.layer.Vector({ + source: vectorSource, + style: styleFeature, + zIndex: 10 + }); + + map.addLayer(villageLayer); + + // Fit to full tehsil on load + const tehsilExtent = vectorSource.getExtent(); + map._tehsilExtent = tehsilExtent; + map.getView().fit(tehsilExtent, { + padding: [30, 30, 30, 30], + duration: 500 + }); + + console.log(config.label + ' Map: Added', features.length, 'village features'); + + // Location pin + zoom toggle + const currentFeature = features.find( + f => String(f.get('vill_ID')) === String(CURRENT_VILLAGE_ID) + ); + + if (currentFeature) { + const centroid = getGeometryCentroid(currentFeature.getGeometry()); + createLocationPin(map, centroid); + createZoomToggle(map, currentFeature, CURRENT_VILLAGE_NAME); + } + + map._villageLayer = villageLayer; + map._vectorSource = vectorSource; + + console.log(config.label + ' Map: Fitted to tehsil'); + } + } catch (error) { + console.error(config.label + ' Map: Error loading villages:', error); + } + })(); + + if (config.mwsesJson && config.villageJson) { + addMWSesLayer(map, config.mwsesJson, config.villageJson, config.label); + } + + return map; +} + +/** + * Create a tabbed village map + */ +function createTabbedVillageMap(config) { + const container = document.getElementById(config.targetId); + if (!container) return null; + + let mapData = {}; + try { + mapData = JSON.parse(config.mapDataJson); + } catch (e) { + console.warn(config.label + ' Map: Could not parse map data:', e); + } + + let currentTab = config.defaultTab; + + function styleFeature(feature) { + const villageId = feature.get('vill_ID'); + const isCurrentVillage = (String(villageId) === String(CURRENT_VILLAGE_ID)); + const tabConfig = config.tabs[currentTab]; + + let colorName = 'gray'; + if (mapData[villageId] && mapData[villageId][tabConfig.colorKey]) { + colorName = mapData[villageId][tabConfig.colorKey]; + } + + const fillColor = VILLAGE_COLORS.fill[colorName] || VILLAGE_COLORS.fill['gray']; + const strokeWidth = isCurrentVillage + ? VILLAGE_COLORS.currentVillageStrokeWidth + : VILLAGE_COLORS.defaultStrokeWidth; + + return new ol.style.Style({ + fill: new ol.style.Fill({ color: fillColor }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: strokeWidth + }) + }); + } + + const map = new ol.Map({ + target: config.targetId, + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + maxZoom: 30, + transition: 500, + }), + preload: 4, + }) + ], + view: new ol.View({ + center: [78.9, 23.6], + zoom: 10, + projection: 'EPSG:4326', + constrainResolution: true, + smoothExtentConstraint: true, + smoothResolutionConstraint: true, + }), + interactions: new ol.Collection([ + new ol.interaction.DragPan(), + new ol.interaction.KeyboardZoom(), + ]) + }); + + let villageLayer = null; + + (async () => { + try { + const geoserverData = await waitForGlobalVillages(); + + if (geoserverData && geoserverData.features) { + const vectorSource = new ol.source.Vector(); + const features = new ol.format.GeoJSON().readFeatures(geoserverData, { + featureProjection: 'EPSG:4326' + }); + + vectorSource.addFeatures(features); + + villageLayer = new ol.layer.Vector({ + source: vectorSource, + style: styleFeature, + zIndex: 10 + }); + + map.addLayer(villageLayer); + + // Fit to full tehsil on load + const tehsilExtent = vectorSource.getExtent(); + map._tehsilExtent = tehsilExtent; + map.getView().fit(tehsilExtent, { + padding: [30, 30, 30, 30], + duration: 500 + }); + + console.log(config.label + ' Map: Added', features.length, 'village features'); + + // Location pin + zoom toggle + const currentFeature = features.find( + f => String(f.get('vill_ID')) === String(CURRENT_VILLAGE_ID) + ); + + if (currentFeature) { + const centroid = getGeometryCentroid(currentFeature.getGeometry()); + createLocationPin(map, centroid); + createZoomToggle(map, currentFeature, CURRENT_VILLAGE_NAME); + } + } + } catch (error) { + console.error(config.label + ' Map: Error loading villages:', error); + } + })(); + + if (config.mwsesJson && config.villageJson) { + addMWSesLayer(map, config.mwsesJson, config.villageJson, config.label); + } + + function switchTab(tabKey) { + currentTab = tabKey; + const tabConfig = config.tabs[tabKey]; + + Object.entries(config.buttonIds).forEach(([key, btnId]) => { + const btn = document.getElementById(btnId); + if (btn) { + btn.style.backgroundColor = (key === tabKey) + ? 'var(--primary-accent)' + : '#bbb'; + } + }); + + if (config.infoIds) { + const titleEl = document.getElementById(config.infoIds.title); + const descEl = document.getElementById(config.infoIds.desc); + const legendEl = document.getElementById(config.infoIds.legend); + if (titleEl) titleEl.textContent = tabConfig.name; + if (descEl) descEl.textContent = tabConfig.desc; + if (legendEl) legendEl.innerHTML = tabConfig.legend; + } + + if (villageLayer) { + villageLayer.setStyle(styleFeature); + console.log(config.label + ' Map: Switched to', tabKey); + } + } + + return { map, switchTab }; +} + +/** + * Add MWSes layer to a map + */ +function addMWSesLayer(map, mwsesJson, villageJson, label) { + try { + const mwsesPolygon = JSON.parse(mwsesJson); + + if (!mwsesPolygon || !mwsesPolygon.features || mwsesPolygon.features.length === 0) { + console.warn(label + ' Map: No MWSes data available'); + return; + } + + let filteredMWSes = mwsesPolygon.features; + + try { + const villageData = JSON.parse(villageJson); + if (villageData && villageData.features && villageData.features.length > 0) { + const villageBoundary = villageData.features[0].geometry; + filteredMWSes = mwsesPolygon.features.filter(mwsFeature => { + try { + const villageBbox = getBoundingBox(villageBoundary); + const mwsBbox = getBoundingBox(mwsFeature.geometry); + return boxesIntersect(villageBbox, mwsBbox); + } catch (e) { + return true; + } + }); + } + } catch (filterError) { + console.warn(label + ' Map: Could not filter MWSes:', filterError); + } + + if (filteredMWSes.length > 0) { + const mwsesFC = { type: 'FeatureCollection', features: filteredMWSes }; + const mwsesSource = new ol.source.Vector({ + features: new ol.format.GeoJSON().readFeatures(mwsesFC, { + featureProjection: 'EPSG:4326' + }) + }); + + const mwsesLayer = new ol.layer.Vector({ + source: mwsesSource, + style: new ol.style.Style({ + fill: new ol.style.Fill({ color: 'rgba(59, 130, 246, 0.08)' }), + stroke: new ol.style.Stroke({ + color: '#2563eb', + width: 2, + lineDash: [5, 5] + }) + }), + zIndex: 5 + }); + + map.addLayer(mwsesLayer); + console.log('✓ ' + label + ' Map: MWSes layer added (' + filteredMWSes.length + ' features)'); + } + } catch (error) { + console.warn(label + ' Map: MWSes error:', error); + } +} \ No newline at end of file diff --git a/templates/mws-report.html b/templates/mws-report.html index 70c01f9f..dd17c297 100644 --- a/templates/mws-report.html +++ b/templates/mws-report.html @@ -1982,6 +1982,9 @@

MGNREGA works

+ + + + + + + +
+
+

Village Report

+

Comprehensive Analysis of Village Profile

+
+
+ +
+
+ +
+ + +
+ + + + + +
+ +

+ Data for this Village is Not Available +

+ +

+ We currently do not have sufficient data to generate a report for the selected village. This could be because the village boundary is not yet mapped, or development indicator data has not been collected for this location. +

+ + + {% if state or district or block %} +
+
+ Location: + {% if state %}{{ state }}{% endif %} + {% if district %}{{ district }}{% endif %} + {% if block %}{{ block }}{% endif %} +
+
+ {% endif %} + + +
+

What you can try:

+
    +
  • Select a different village from the map or list view.
  • +
  • Confirm that the village boundary appears correctly on the tehsil map.
  • +
  • Check back later as new village data is added periodically.
  • +
+
+ +
+ +
+
+ + +
+

CoreStack — A commoning approach — Tech stacks for communities

+
+ + \ No newline at end of file diff --git a/templates/village-report.html b/templates/village-report.html index f78a00c8..71067171 100644 --- a/templates/village-report.html +++ b/templates/village-report.html @@ -25,9 +25,6 @@ - - - - - - -
-
-

Resource and Demand Map Report

-
-
Plan Name: {{plan_name}}
-
Plan ID: {{plan_id}}
-
- -
-
- -
- -
-

Village Overview over LULC

-
-
-
-
-
- Barren Lands -
-
-
- Single Kharif -
-
-
- Single Non-Kharif -
-
-
- Double Cropping -
-
-
- Tripple/Annual/Perennial Cropping -
-
-
- Shrubs and Scrubs -
-
-
- -
- -
-

Settlement Overview for Plan : {{plan_name}}

-
-
- -
- -
- -
-

Well Overview for Plan : {{plan_name}}

-
-
-

Note: Well Distribution on Stream Order Raster

-
-
-
- 1 -
-
-
- 2 -
-
-
- 3 -
-
-
- 4 -
-
-
- 5 -
-
-
- 6 -
-
-
- 7 -
-
-
- 8 -
-
-
- 9 -
-
-
- 10 -
-
-
- 11 -
-
-
- -
- -
-

Water Structures Overview for Plan : {{plan_name}}

-
-
-
-
-
- Good recharge -
-
-
- Moderate recharge -
-
-
- Regeneration -
-
-
- High runoff zone -
-
-
- Surface water harvesting -
-
-
- -
- -
-

Demands Overview for Plan : {{plan_name}}

-
-
-
-
-
- V-shape river valleys, Deep narrow canyons -
-
-
- Lateral midslope incised drainages, Local valleys in plains -
-
-
- Upland incised drainages, Stream headwaters -
-
-
- U-shape valleys -
-
-
- Broad Flat Areas -
-
-
- Broad open slopes -
-
-
- Mesa tops -
-
-
- Upper Slopes -
-
-
- Local ridge/hilltops within broad valleys -
-
-
- Lateral midslope drainage divides, Local ridges in plains -
-
-
- Mountain tops, high ridges -
-
-
- -
-
-

Report generated on | CoRE Stack Team

-

- Please do share your feedback with - contact@core-stack.org. -

-
-
- - - - - + + + + + + + Resource Mapping Report + + + + + + + + + + + + + + + + + + + + +
+
+

Resource and Demand Map Report

+
+
Plan Name: {{plan_name}}
+
Plan ID: {{plan_id}}
+
+ +
+
+ +
+ +
+

Village Overview over LULC

+
+
+
+
+
+ Barren Lands +
+
+
+ Single Kharif +
+
+
+ Single Non-Kharif +
+
+
+ Double Cropping +
+
+
+ Tripple/Annual/Perennial Cropping +
+
+
+ Shrubs and Scrubs +
+
+
+ +
+ +
+

Settlement Overview for Plan : {{plan_name}}

+
+
+ +
+ +
+ +
+

Well Overview for Plan : {{plan_name}}

+
+
+

Note: Well Distribution on Stream Order Raster

+
+
+
+ 1 +
+
+
+ 2 +
+
+
+ 3 +
+
+
+ 4 +
+
+
+ 5 +
+
+
+ 6 +
+
+
+ 7 +
+
+
+ 8 +
+
+
+ 9 +
+
+
+ 10 +
+
+
+ 11 +
+
+
+ +
+ +
+

Water Structures Overview for Plan : {{plan_name}}

+
+
+
+
+
+ Good recharge +
+
+
+ Moderate recharge +
+
+
+ Regeneration +
+
+
+ High runoff zone +
+
+
+ Surface water harvesting +
+
+
+ +
+ +
+

Demands Overview for Plan : {{plan_name}}

+
+
+
+
+
+ V-shape river valleys, Deep narrow canyons +
+
+
+ Lateral midslope incised drainages, Local valleys in plains +
+
+
+ Upland incised drainages, Stream headwaters +
+
+
+ U-shape valleys +
+
+
+ Broad Flat Areas +
+
+
+ Broad open slopes +
+
+
+ Mesa tops +
+
+
+ Upper Slopes +
+
+
+ Local ridge/hilltops within broad valleys +
+
+
+ Lateral midslope drainage divides, Local ridges in plains +
+
+
+ Mountain tops, high ridges +
+
+
+ +
+
+

Report generated on | CoRE Stack Team

+

+ Please do share your feedback with + contact@core-stack.org. +

+
+
+ + + + + \ No newline at end of file diff --git a/templates/village-report.html b/templates/village-report.html index ce064093..f78a00c8 100644 --- a/templates/village-report.html +++ b/templates/village-report.html @@ -5,7 +5,10 @@ Village Report - {{ village_name }} - + + + + @@ -22,8 +25,11 @@ + + + + + - .chart-description { - font-size: 14px; - color: var(--text-secondary); - line-height: 1.8; - padding: 16px; - background-color: rgba(232, 220, 200, 0.4); - border-left: 4px solid var(--primary-accent); - margin-bottom: 24px; - border-radius: 2px; - } + + +
+
+

Village Development Report

+

Comprehensive Analysis of Village Profile & Development Metrics

+
+
- .chart-description p { - margin: 0; - } +
+
- /* ===== PRINT STYLES FOR CHARTS ===== */ - @media print { - .chart-container { - page-break-inside: avoid; - break-inside: avoid; - background-color: white; - border: 1px solid #ccc; - } - } - - - -
- -
-

Village Profile

+
+

Village Profile

-
+
+ Location: {{ state }} + {{ district }} + {{ block }} - ID: {{ village_id }} + + ID: {{ village_id }}
-
+
{% if village_name %} - Village {{ village_name }} + Village {{ village_name }} {% if gram_panchayat_name %} lies in Gram Panchayat {{ gram_panchayat_name }} {% endif %} @@ -320,233 +211,4489 @@

Village Profile

in {{ block }}, {{ district }}. {% endif %} {% else %} - Village in {{ block }}, {{ district }}, {{ state }}. + Village in {{ block }}, {{ district }}, {{ state }}. {% endif %}
-
-
-

Village Development Overview

+
+

Village Development Overview

+
-
-

The radar chart above illustrates the village's development across ten key dimensions, each rated on a scale of 0.25 to 1.00. This multidimensional assessment provides insights into infrastructure, health, education, financial inclusion, governance, livelihoods, agriculture, and environmental resilience.

+ +
+
+
+ High (0.67 - 1.00) +
+
+
+ Moderate (0.34 - 0.66) +
+
+
+ Low (0.00 - 0.33) +
- + +
+

Demographic Details

-
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValue
Total Population{{ demographic_data.TOT_P }}
Male{{ demographic_data.TOT_M }}
Female{{ demographic_data.TOT_F }}
Number of Households{{ demographic_data.No_HH }}
Scheduled Caste Population{{ demographic_data.P_SC }} ({{ demographic_data.sc_percentage }}%)
Male {{ demographic_data.M_SC }}
Female {{ demographic_data.F_SC }}
Scheduled Tribe Population{{ demographic_data.P_ST }} ({{ demographic_data.st_percentage }}%)
Male {{ demographic_data.M_ST }}
Female {{ demographic_data.F_ST }}
Literate Population{{ demographic_data.P_LIT }} ({{ demographic_data.literacy_percentage }}%)
Male{{ demographic_data.M_LIT }}
Female{{ demographic_data.F_LIT }}
Aggregate Development Index (ADI 2011){{ demographic_data.ADI_2011 }}
+
- + + From 83a3f669618f42f6d0dded2bec5b682a203e8993 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Thu, 18 Jun 2026 11:12:30 +0530 Subject: [PATCH 05/12] Chore : Updated Village Report and MWS Report --- dpr/api.py | 54 +- dpr/gen_mws_report.py | 80 +- dpr/gen_village_report.py | 1538 ++++++--- nrm_app/settings.py | 873 ++--- templates/js/climate_vulnerability_section.js | 292 ++ templates/{ => js}/global_village_fetcher.js | 0 templates/js/village_map_utils.js | 491 +++ templates/mws-report.html | 189 +- templates/village-report-unavailable.html | 106 + templates/village-report.html | 3064 ++++------------- 10 files changed, 3329 insertions(+), 3358 deletions(-) create mode 100644 templates/js/climate_vulnerability_section.js rename templates/{ => js}/global_village_fetcher.js (100%) create mode 100644 templates/js/village_map_utils.js create mode 100644 templates/village-report-unavailable.html diff --git a/dpr/api.py b/dpr/api.py index df35167c..242b4c6a 100644 --- a/dpr/api.py +++ b/dpr/api.py @@ -16,6 +16,7 @@ from utilities.auth_check_decorator import api_security_check from utilities.auth_utils import auth_free from utilities.logger import setup_logger +from nrm_app.settings import BASE_API_URL from .gen_dpr import ( get_plan_details, @@ -67,6 +68,7 @@ get_factory_data, get_mining_data, get_green_credit_data, + get_intersecting_village_ids ) from .gen_tehsil_report import ( get_tehsil_data, @@ -99,8 +101,16 @@ get_agri_support_service, get_ecological_climate_resiliance, get_all_villages_basic_infrastructure, + get_all_villages_health_and_wash, get_all_villages_education_institutions, - get_mwses_boundaries, + get_all_villages_financial_inclusion, + get_all_villages_welfare_inclusion, + get_all_villages_community_institutes, + get_all_villages_livestock_management, + get_all_villages_irrigation_infra, + get_all_villages_agri_support_service, + get_all_villages_ecological_climate_resiliance, + get_mwses_ids, ) from .gen_report_download import render_pdf_with_firefox from .utils import validate_email, transform_name @@ -324,10 +334,14 @@ def generate_mws_report(request): green_credits = get_green_credit_data(state, district, block, uid) + village_ids = get_intersecting_village_ids(state, district, block, uid) + context = { + "state" : state, "district": district, "block": block, "mws_id": uid, + "base_url" : BASE_API_URL, "block_osm": parameter_block, "mws_osm": parameter_mws, "terrain_mws": terrain_mws, @@ -389,6 +403,7 @@ def generate_mws_report(request): "factory_desc": factory_desc, "mining_desc": mining_desc, "green_credit_desc": green_credits, + "village_ids" : json.dumps(village_ids) } # print("Api Processing End 1", datetime.now()) @@ -593,6 +608,13 @@ def generate_village_report(request): district = request.GET.get('district') block = request.GET.get('block') village_id = request.GET.get('villageId') + + if village_id == '0': + return render(request, 'village-report-unavailable.html', { + 'state': state, + 'district': district, + 'block': block, + }) # Get village polygon and info from GeoServer village_data = get_village_polygon_and_info(state, district, block, village_id) @@ -658,9 +680,18 @@ def generate_village_report(request): #Map Data basic_infra_map = get_all_villages_basic_infrastructure(state, district, block) + health_wash_map = get_all_villages_health_and_wash(state, district, block) education_map = get_all_villages_education_institutions(state, district, block) - mwses_data = get_mwses_boundaries(state, district, block, village_id) - + financial_map = get_all_villages_financial_inclusion(state, district, block) + welfare_map = get_all_villages_welfare_inclusion(state, district, block) + community_map = get_all_villages_community_institutes(state, district, block) + livestock_map = get_all_villages_livestock_management(state, district, block) + irrigation_infra_map = get_all_villages_irrigation_infra(state, district, block) + agri_support_map = get_all_villages_agri_support_service(state, district, block) + climate_resiliance_map = get_all_villages_ecological_climate_resiliance(state, district, block) + + mws_ids = get_mwses_ids(state, district, block, village_id) + mws_pattern_intensity = get_pattern_intensity(state, district, block) # Build context for template context = { @@ -669,6 +700,7 @@ def generate_village_report(request): "district": district, "block": block, "village_id": village_id, + "base_url" : BASE_API_URL, "village_name": village_data['village_name'], "gram_panchayat_name": village_data['gram_panchayat_name'], "area_hectares": village_data['area_hectares'], @@ -717,10 +749,22 @@ def generate_village_report(request): "climate_resiliance_data" : json.dumps(climate_resiliance_data), "village_polygon": json.dumps(village_data['village_polygon']), - "mwses_polygon": json.dumps(mwses_data['polygon']), "basic_infra_map" : json.dumps(basic_infra_map), - "education_map" : json.dumps(education_map) + "health_wash_map" : json.dumps(health_wash_map), + "education_map" : json.dumps(education_map), + "financial_map" : json.dumps(financial_map), + "welfare_map" : json.dumps(welfare_map), + "community_map" : json.dumps(community_map), + "livestock_map" : json.dumps(livestock_map), + "irrigation_infra_map" : json.dumps(irrigation_infra_map), + "agri_support_map" : json.dumps(agri_support_map), + "climate_resiliance_map" : json.dumps(climate_resiliance_map), + + "mws_ids" : json.dumps(mws_ids), + "pattern_intensity": json.dumps(mws_pattern_intensity['intensity']), + "mws_active_patterns": json.dumps(mws_pattern_intensity['mws_active_patterns']), + "pattern_display_mapping": json.dumps(mws_pattern_intensity['pattern_display_mapping']), } return render(request, 'village-report.html', context) diff --git a/dpr/gen_mws_report.py b/dpr/gen_mws_report.py index 317f6a89..642d23ba 100644 --- a/dpr/gen_mws_report.py +++ b/dpr/gen_mws_report.py @@ -2615,4 +2615,82 @@ def get_village_data(state, district, block, uid): district, block, ) - return [], [], [], [], [], [], [], [], [], [], [] \ No newline at end of file + return [], [], [], [], [], [], [], [], [], [], [] + + + +import ast + +def get_intersecting_village_ids(state, district, block, mws_uid): + """ + Reads the mws_intersect_villages sheet and returns the list of + village IDs that intersect with the given MWS UID. + """ + try: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + available_sheets = excel_file.sheet_names + + if "mws_intersect_villages" not in available_sheets: + logger.info( + "mws_intersect_villages sheet not found for %s district, %s block", + district, + block + ) + return [] + + df = pd.read_excel(file_path, sheet_name="mws_intersect_villages") + + if "Village IDs" not in df.columns: + logger.info( + "Village IDs column not found in mws_intersect_villages sheet for %s district, %s block", + district, + block + ) + return [] + + matching = df.loc[df["MWS UID"] == mws_uid, "Village IDs"] + + if matching.empty: + return [] + + raw_value = matching.iloc[0] + + if pd.isna(raw_value): + return [] + + try: + village_ids = ast.literal_eval(raw_value) + except (ValueError, SyntaxError) as parse_error: + logger.info( + "Could not parse Village IDs for MWS UID %s in %s district, %s block: %s", + mws_uid, + district, + block, + parse_error + ) + return [] + + if not isinstance(village_ids, list): + return [] + + return village_ids + + except Exception as e: + logger.info( + "Error accessing excel for %s district, %s block: %s", + district, + block, + ) + return [] \ No newline at end of file diff --git a/dpr/gen_village_report.py b/dpr/gen_village_report.py index cf6e625d..fafcb67c 100644 --- a/dpr/gen_village_report.py +++ b/dpr/gen_village_report.py @@ -6,9 +6,6 @@ import pymannkendall as mk import json import ast -from shapely.geometry import shape, mapping -from shapely.wkt import dumps as shapely_to_wkt -from shapely.ops import unary_union from nrm_app.settings import EXCEL_DIR, GEOSERVER_URL, OVERPASS_URL from utilities.logger import setup_logger @@ -79,312 +76,6 @@ def calculate_demographics(properties): return demographic_data -def _convert_wkt_to_gml_coordinates(wkt): - """ - Convert WKT coordinates to GML format. - Example: "POLYGON ((x1 y1, x2 y2, ...))" → "x1 y1x2 y2..." - """ - try: - # Extract coordinates from WKT - # WKT format: "POLYGON ((lon lat, lon lat, ...))" - import re - - # Extract the coordinate string - coords_str = re.search(r'\(\((.*?)\)\)', wkt).group(1) - - # Split into individual coordinates - coords = coords_str.split(',') - - # Convert to GML format - gml_coords = [] - for coord in coords: - coord = coord.strip() - if coord: - gml_coords.append(f"{coord}") - - return '\n '.join(gml_coords) - - except Exception as e: - print(f"[WARN] Error converting WKT to GML: {str(e)}") - return "" - - -def _try_bbox_query(layer_name, bounds): - """ - Fallback: Query using bounding box instead of spatial intersection. - """ - - try: - minx, miny, maxx, maxy = bounds - - # Add small buffer - buffer = 0.01 - minx -= buffer - miny -= buffer - maxx += buffer - maxy += buffer - - print(f"[DEBUG] BBOX query bounds: {minx}, {miny}, {maxx}, {maxy}") - - wfs_url = f"{GEOSERVER_URL}/wfs" - - params = { - 'service': 'WFS', - 'version': '2.0.0', - 'request': 'GetFeature', - 'typeNames': f"mws_layers:{layer_name}", - 'outputFormat': 'application/json', - 'bbox': f"{minx},{miny},{maxx},{maxy},urn:ogc:def:crs:EPSG:4326", - 'srsName': 'EPSG:4326' - } - - response = requests.get(wfs_url, params=params, timeout=30) - - if response.status_code == 200: - mws_data = response.json() - - if mws_data.get('features'): - print(f"[SUCCESS] BBOX query found {len(mws_data['features'])} features") - return { - 'success': True, - 'features': mws_data['features'], - 'count': len(mws_data['features']), - 'error': None - } - else: - print(f"[INFO] BBOX query returned 0 features") - return { - 'success': True, - 'features': [], - 'count': 0, - 'error': 'No features in bbox' - } - else: - error_msg = f"Status {response.status_code}" - print(f"[ERROR] BBOX query failed: {error_msg}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': error_msg - } - - except Exception as e: - print(f"[ERROR] BBOX query exception: {str(e)}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': str(e) - } - - -def test_mws_layer(district, block): - """ - Test if the MWSes layer exists in GeoServer. - """ - - mws_layer_name = f"deltaG_well_depth_{district}_{block}".lower() - - try: - wfs_url = f"{GEOSERVER_URL}/wfs" - - params = { - 'service': 'WFS', - 'version': '2.0.0', - 'request': 'GetFeature', - 'typeNames': f"mws_layers:{mws_layer_name}", - 'outputFormat': 'application/json', - 'maxFeatures': 1 - } - - response = requests.get(wfs_url, params=params, timeout=10) - - if response.status_code == 200: - print(f"✓ Layer '{mws_layer_name}' exists") - data = response.json() - features_count = len(data.get('features', [])) - print(f" Features available: {features_count}") - if data.get('features'): - print(f" Geometry type: {data['features'][0].get('geometry', {}).get('type')}") - print(f" Sample properties: {list(data['features'][0].get('properties', {}).keys())[:5]}") - return True - else: - print(f"✗ Layer '{mws_layer_name}' returned status {response.status_code}") - return False - - except Exception as e: - print(f"✗ Error testing layer: {str(e)}") - return False - - -def _try_post_intersects_query(layer_name, village_wkt): - """ - Try INTERSECTS spatial filter using POST request. - POST avoids URL length limits for complex geometries. - """ - - try: - print(f"[DEBUG] Attempting POST INTERSECTS query...") - - wfs_url = f"{GEOSERVER_URL}/wfs" - - # Build WFS query with CQL filter in request body - wfs_query = f""" - - - - - the_geom - - - - {_convert_wkt_to_gml_coordinates(village_wkt)} - - - - - -""" - - headers = { - 'Content-Type': 'application/xml' - } - - response = requests.post(wfs_url, data=wfs_query, headers=headers, timeout=30) - print(f"[DEBUG] POST response status: {response.status_code}") - - if response.status_code == 200: - mws_data = response.json() - if mws_data.get('features'): - print(f"[SUCCESS] Found {len(mws_data['features'])} features") - return { - 'success': True, - 'features': mws_data['features'], - 'count': len(mws_data['features']), - 'error': None - } - else: - print(f"[INFO] No features found") - return { - 'success': True, - 'features': [], - 'count': 0, - 'error': 'No intersecting features' - } - else: - error_msg = f"Status {response.status_code}: {response.text[:200]}" - print(f"[WARN] POST query failed: {error_msg}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': error_msg - } - - except Exception as e: - print(f"[WARN] POST query exception: {str(e)}") - return { - 'success': False, - 'features': [], - 'count': 0, - 'error': str(e) - } - - -def get_mwses_boundaries(state, district, block, village_id): - """ - Get all MWSes (Micro-Watersheds) boundaries that intersect with the given village. - Uses POST requests to avoid URL length limits with complex geometries. - """ - - try: - # Step 1: Get village geometry - village_data = get_village_polygon_and_info(state, district, block, village_id) - - if not village_data or not village_data.get('village_polygon'): - return {'polygon': {}, 'count': 0, 'error': 'Village polygon not found'} - - village_geom = village_data['village_polygon'] - print(f"[DEBUG] village_geom type: {type(village_geom)}") - - # Step 2: Convert to Shapely - village_shapely = None - - if isinstance(village_geom, dict): - if village_geom.get('type') == 'FeatureCollection': - geometries = [] - for feature in village_geom.get('features', []): - if 'geometry' in feature: - geometries.append(shape(feature['geometry'])) - village_shapely = unary_union(geometries) - print(f"[DEBUG] Converted FeatureCollection to Shapely (unary_union)") - elif village_geom.get('type') == 'Feature': - village_shapely = shape(village_geom['geometry']) - print(f"[DEBUG] Converted Feature to Shapely") - elif village_geom.get('type') in ['Polygon', 'MultiPolygon', 'Point', 'LineString']: - village_shapely = shape(village_geom) - print(f"[DEBUG] Converted {village_geom.get('type')} to Shapely") - - if village_shapely is None: - return {'polygon': {}, 'count': 0, 'error': 'Could not convert village geometry'} - - # Step 3: Query MWSes layer - mws_layer_name = f"deltaG_well_depth_{district}_{block}".lower() - print(f"[DEBUG] Querying layer: {mws_layer_name}") - - # Convert to WKT - village_wkt = shapely_to_wkt(village_shapely) - print(f"[DEBUG] Village WKT length: {len(village_wkt)} characters") - - # Step 4: Try POST request with INTERSECTS (avoids URL length limits) - result = _try_post_intersects_query(mws_layer_name, village_wkt) - - if result['success']: - print(f"[SUCCESS] POST INTERSECTS query returned {result['count']} features") - return { - 'polygon': {'type': 'FeatureCollection', 'features': result['features']}, - 'count': result['count'], - 'error': None - } - - print(f"[WARNING] POST INTERSECTS query failed: {result['error']}") - - # Step 5: Fallback to BBOX query - print(f"[INFO] Trying fallback with BBOX query...") - bounds = village_shapely.bounds - result = _try_bbox_query(mws_layer_name, bounds) - - if result['success']: - print(f"[SUCCESS] BBOX query returned {result['count']} features") - return { - 'polygon': {'type': 'FeatureCollection', 'features': result['features']}, - 'count': result['count'], - 'error': None - } - - print(f"[ERROR] BBOX query also failed: {result['error']}") - return { - 'polygon': {}, - 'count': 0, - 'error': f"Both queries failed. BBOX error: {result['error']}" - } - - except Exception as e: - print(f"[EXCEPTION] {str(e)}") - import traceback - traceback.print_exc() - return { - 'polygon': {}, - 'count': 0, - 'error': f'Error: {str(e)}' - } - - def get_mwses_ids(state, district, block, village_id): try: @@ -1478,36 +1169,43 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_basic_infrastructure(state, district, block, village_id): - +def get_basic_infrastructure(state, district, block, village_id, df=None): + def safe_float(value, default=0): try: return float(value) except: return default - + try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - excel_file = pd.ExcelFile(file_path) + # Only load excel if dataframe not supplied + if df is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - df = pd.read_excel(excel_file, sheet_name="antyodaya") + excel_file = pd.ExcelFile(file_path) - df["village_id"] = ( - df["village_id"] - .astype(str) - .str.strip() - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -1516,35 +1214,38 @@ def safe_float(value, default=0): ] if matched_rows.empty: - logger.info( - "No data found for village_id %s in %s district, %s block", - village_id, - district, - block, - ) return [] row = matched_rows.iloc[0] - road_score = safe_float(row.get("road_connectivity_cat_value", 0)) - - energy_score = safe_float(row.get("energy_access_cat_value", 0)) - - housing_score = safe_float(row.get("housing_quality_cat_value", 0)) - return [ - road_score, - energy_score, - housing_score + safe_float( + row.get( + "road_connectivity_cat_value", + 0 + ) + ), + safe_float( + row.get( + "energy_access_cat_value", + 0 + ) + ), + safe_float( + row.get( + "housing_quality_cat_value", + 0 + ) + ) ] except Exception as e: + logger.info( - "Not able to access excel for %s district, %s block. Error: %s", - district, - block, + "Not able to access infrastructure data. Error: %s", str(e), ) + return [] @@ -1684,7 +1385,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_education_institutions(state, district, block, village_id): +def get_education_institutions(state, district, block, village_id, df_facilities=None): def get_numeric(row, column): return pd.to_numeric(row.get(column, None), errors="coerce") @@ -1705,30 +1406,32 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) - df_facilities["censuscode2011"] = ( - df_facilities["censuscode2011"] - .astype(str) - .str.strip() - ) + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -1802,7 +1505,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_financial_inclusion(state, district, block, village_id): +def get_financial_inclusion(state, district, block, village_id, df_facilities=None): def get_numeric(row, column): return pd.to_numeric(row.get(column, None), errors="coerce") @@ -1839,30 +1542,31 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df_facilities is None: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) - df_facilities["censuscode2011"] = ( - df_facilities["censuscode2011"] - .astype(str) - .str.strip() - ) + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -1920,7 +1624,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_welfare_inclusion(state, district, block, village_id): +def get_welfare_inclusion(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -1932,30 +1636,47 @@ def get_numeric(row, column): return pd.to_numeric(row.get(column, None), errors="coerce") try: + if df is None or df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + excel_file = pd.ExcelFile(file_path) - excel_file = pd.ExcelFile(file_path) + if df is None: - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + if df_facilities is None: + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) df["village_id"] = ( df["village_id"] @@ -2036,7 +1757,7 @@ def get_numeric(row, column): return [] -def get_community_institutes(state, district, block, village_id): +def get_community_institutes(state, district, block, village_id, df=None): def safe_float(value, default=0): try: @@ -2046,24 +1767,26 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) df["village_id"] = ( df["village_id"] @@ -2204,7 +1927,7 @@ def safe_float(value, default=0): return [] -def get_livestock_management(state, district, block, village_id): +def get_livestock_management(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -2217,29 +1940,33 @@ def get_numeric(row, column): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None or df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df_facilities is None: + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) df["village_id"] = ( df["village_id"] @@ -2386,7 +2113,7 @@ def safe_float(value, default=0): return [] -def get_irrigation_Infra(state, district, block, village_id): +def get_irrigation_Infra(state, district, block, village_id, df=None): def safe_float(value, default=0): try: @@ -2396,24 +2123,26 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) df["village_id"] = ( df["village_id"] @@ -2488,7 +2217,7 @@ def safe_float(value, default=0): return [] -def get_agri_support_service(state, district, block, village_id): +def get_agri_support_service(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -2515,29 +2244,33 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None or df_facilities is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df_facilities is None: + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) df["village_id"] = ( df["village_id"] @@ -2669,7 +2402,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_ecological_climate_resiliance(state,district,block,village_id): +def get_ecological_climate_resiliance(state, district, block, village_id, df=None, df_nrega=None): def safe_float(value, default=0): try: @@ -2679,29 +2412,33 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None or df_nrega is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) + if df_nrega is None: + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) df["village_id"] = ( df["village_id"] @@ -2757,8 +2494,10 @@ def safe_float(value, default=0): if nrega_match.empty: return [ - None, - {}, + None, # year_range + {}, # category_counts + 0, # total_work_count + "red", # nrega_work_color organic_farming_score, organic_farming_color ] @@ -2774,8 +2513,8 @@ def safe_float(value, default=0): if column in ["vill_id", "vill_name"]: continue - # Split from right to extract year try: + work_type, year = column.rsplit("_", 1) year = int(year) @@ -2806,11 +2545,23 @@ def safe_float(value, default=0): "to_year": max(years) if years else None, } + total_work_count = sum( + category_counts.values() + ) + + nrega_work_color = ( + "green" + if total_work_count > 100 + else "red" + ) + return [ - year_range, - category_counts, - organic_farming_score, - organic_farming_color + year_range, # index 0 + category_counts, # index 1 + total_work_count, # index 2 + nrega_work_color, # index 3 + organic_farming_score, # index 4 + organic_farming_color # index 5 ] except Exception as e: @@ -2826,12 +2577,9 @@ def safe_float(value, default=0): - #? Get Tehsil Map Data def get_all_villages_basic_infrastructure(state, district, block): - try: - file_path = ( DATA_DIR_TEMP + state.upper() @@ -2846,15 +2594,41 @@ def get_all_villages_basic_infrastructure(state, district, block): excel_file = pd.ExcelFile(file_path) - df_nrega = pd.read_excel(excel_file,sheet_name="nrega_assets_village") + # Read once + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + village_ids = ( + df_nrega["vill_id"] + .dropna() + .astype(str) + .str.strip() + ) - village_ids = (df_nrega["vill_id"].dropna().astype(str).unique()) + village_ids = [ + vid + for vid in village_ids.unique() + if vid and vid != "0" + ] result = {} for village_id in village_ids: - village_data = get_basic_infrastructure(state, district, block, village_id) + village_data = get_basic_infrastructure(state, district, block, village_id, df=df) if not village_data: @@ -2866,9 +2640,7 @@ def get_all_villages_basic_infrastructure(state, district, block): continue - road_score = village_data[0] - energy_score = village_data[1] - housing_score = village_data[2] + road_score, energy_score, housing_score = village_data result[village_id] = { @@ -2903,9 +2675,128 @@ def get_all_villages_basic_infrastructure(state, district, block): ) return {} - -def get_all_villages_education_institutions(state, district, block): + +def get_all_villages_health_and_wash(state, district, block): + + def safe_float(value, default=0): + try: + return float(value) + except: + return default + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + matched_rows = df[ + df["village_id"] == village_id + ] + + if matched_rows.empty: + + village_data[village_id] = { + "maternal_child_health_color": "black", + "water_sanitation_color": "black" + } + + continue + + row = matched_rows.iloc[0] + + maternal_child_score = safe_float( + row.get( + "maternal_child_health_cat_value", + 0 + ) + ) + + water_sanitation_score = safe_float( + row.get( + "water_sanitation_cat_value", + 0 + ) + ) + + # Maternal Child Health Color + + if maternal_child_score <= 0.33: + maternal_child_health_color = "red" + + elif maternal_child_score <= 0.66: + maternal_child_health_color = "yellow" + + else: + maternal_child_health_color = "green" + + # Water & Sanitation Color + + if water_sanitation_score <= 0.33: + water_sanitation_color = "red" + + elif water_sanitation_score <= 0.66: + water_sanitation_color = "yellow" + + else: + water_sanitation_color = "green" + + village_data[village_id] = { + "maternal_child_health_color": ( + maternal_child_health_color + ), + "water_sanitation_color": ( + water_sanitation_color + ) + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access health and wash data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_education_institutions( state, district, block): try: file_path = ( @@ -2922,20 +2813,40 @@ def get_all_villages_education_institutions(state, district, block): excel_file = pd.ExcelFile(file_path) - df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) village_ids = ( df_nrega["vill_id"] .dropna() .astype(str) - .unique() + .str.strip() ) + village_ids = [ + vid + for vid in village_ids.unique() + if vid and vid != "0" + ] + result = {} for village_id in village_ids: - education_data = get_education_institutions(state,district, block, village_id ) + education_data = get_education_institutions(state, district, block, village_id, df_facilities=df_facilities) if not education_data: @@ -2961,3 +2872,568 @@ def get_all_villages_education_institutions(state, district, block): return {} +def get_all_villages_financial_inclusion(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + village_ids = ( + df_nrega["vill_id"] + .dropna() + .astype(str) + .str.strip() + ) + + village_ids = [ + vid + for vid in village_ids.unique() + if vid and vid != "0" + ] + + result = {} + + for village_id in village_ids: + + financial_data = get_financial_inclusion( + state, + district, + block, + village_id, + df_facilities=df_facilities + ) + + if not financial_data: + + result[village_id] = { + "financial_color": "black" + } + + continue + + result[village_id] = { + "financial_color": financial_data[2] + } + + return result + + except Exception as e: + + logger.info( + "Error calculating financial inclusion colors for all villages: %s", + str(e) + ) + + return {} + + +def get_all_villages_welfare_inclusion(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df_facilities["censuscode2011"] = ( + df_facilities["censuscode2011"] + .astype(str) + .str.strip() + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + village_ids = [ + str(v).strip() + for v in df_nrega["vill_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + result = {} + + for village_id in village_ids: + + welfare_data = get_welfare_inclusion( + state, + district, + block, + village_id, + df=df, + df_facilities=df_facilities + ) + + if not welfare_data: + + result[village_id] = { + "welfare_color": "black" + } + + continue + + result[village_id] = { + "welfare_color": welfare_data[2] + } + + return result + + except Exception as e: + + logger.info( + "Error calculating welfare inclusion colors for all villages: %s", + str(e) + ) + + return {} + + +def get_all_villages_community_institutes(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + community_info = get_community_institutes( + state, + district, + block, + village_id, + df=df + ) + + if not community_info: + + village_data[village_id] = { + "community_color": "black", + "civic_color": "black" + } + + continue + + village_data[village_id] = { + "community_color": community_info[2], + "civic_color": community_info[3] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access community institution data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_livestock_management(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + livestock_info = get_livestock_management( + state, + district, + block, + village_id, + df=df, + df_facilities=df_facilities + ) + + if not livestock_info: + + village_data[village_id] = { + "veterinary_color": "black", + "pasture_color": "black" + } + + continue + + village_data[village_id] = { + "veterinary_color": livestock_info[3], + "pasture_color": livestock_info[4] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access livestock management data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_irrigation_infra(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + irrigation_info = get_irrigation_Infra( + state, + district, + block, + village_id, + df=df + ) + + if not irrigation_info: + + village_data[village_id] = { + "irrigation_watershed_color": "black", + "modern_irrigation_color": "black" + } + + continue + + village_data[village_id] = { + "irrigation_watershed_color": irrigation_info[2], + "modern_irrigation_color": irrigation_info[3] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access irrigation infrastructure data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_agri_support_service(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_facilities = pd.read_excel( + excel_file, + sheet_name="facilities_proximity" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + agri_info = get_agri_support_service( + state, + district, + block, + village_id, + df=df, + df_facilities=df_facilities + ) + + if not agri_info: + + village_data[village_id] = { + "agri_support_color": "black", + "agri_market_color": "black" + } + + continue + + village_data[village_id] = { + "agri_support_color": agri_info[4], + "agri_market_color": agri_info[5] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access agri support service data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_all_villages_ecological_climate_resiliance(state, district, block): + + try: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df_nrega = pd.read_excel( + excel_file, + sheet_name="nrega_assets_village" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df_nrega["vill_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + ecology_info = ( + get_ecological_climate_resiliance( + state, + district, + block, + village_id, + df=df, + df_nrega=df_nrega + ) + ) + + if not ecology_info: + + village_data[village_id] = { + "organic_farming_color": "black", + "nrega_work_color": "black" + } + + continue + + village_data[village_id] = { + "organic_farming_color": ecology_info[5], + "nrega_work_color": ecology_info[3] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access ecology data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + diff --git a/nrm_app/settings.py b/nrm_app/settings.py index f08fe9d6..c331d02f 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -1,436 +1,437 @@ -""" -Django settings for nrm_app project. - -Generated by 'django-admin startproject' using Django 4.2.2. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.2/ref/settings/ -""" - -import os -from datetime import timedelta -from pathlib import Path - -import environ -from corsheaders.defaults import default_headers - -env = environ.Env() -ENV_FILE = Path(__file__).resolve().parent / ".env" -environ.Env.read_env(str(ENV_FILE)) - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent -os.environ.setdefault("BACKEND_DIR", str(BASE_DIR)) - - -def resolve_env_path(name, default="", *, trailing_sep=False): - raw_value = str(os.environ.get(name, default) or "").strip() - if not raw_value: - return "" - - raw_value = os.path.expandvars(raw_value) - candidate = Path(raw_value).expanduser() - if not candidate.is_absolute(): - candidate = BASE_DIR / candidate - - resolved = os.path.normpath(str(candidate)) - if trailing_sep: - resolved = resolved.rstrip("/\\") + os.sep - return resolved - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env("SECRET_KEY") - - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env.bool("DEBUG", default=False) - -# TMP File location -TMP_LOCATION = resolve_env_path( - "TMP_LOCATION", default="$BACKEND_DIR/tmp", trailing_sep=True -) - -# MARK: ODK Login Creds -ODK_USERNAME = env("ODK_USERNAME") -AUTH_TOKEN_FB_META = env("AUTH_TOKEN_FB_META") -ODK_PASSWORD = env("ODK_PASSWORD") -DEPLOYMENT_DIR = resolve_env_path("DEPLOYMENT_DIR", default="$BACKEND_DIR") -# MARK: ODK Sync Creds -ODK_USER_EMAIL_SYNC = env("ODK_USER_EMAIL_SYNC") -ODK_USER_PASSWORD_SYNC = env("ODK_USER_PASSWORD_SYNC") - -# MARK: DB settings -DB_NAME = env("DB_NAME") -DB_USER = env("DB_USER") -DB_PASSWORD = env("DB_PASSWORD") - -USERNAME_GESDISC = env("USERNAME_GESDISC") -PASSWORD_GESDISC = env("PASSWORD_GESDISC") - -STATIC_ROOT = "static/" -GEE_HELPER_ACCOUNT_ID = env("GEE_HELPER_ACCOUNT_ID") -GEE_DEFAULT_ACCOUNT_ID = env("GEE_DEFAULT_ACCOUNT_ID") -ADMIN_GROUP_ID = env("ADMIN_GROUP_ID") -ALLOWED_HOSTS = [ - "geoserver.core-stack.org", - "127.0.0.1", - "localhost", - "0.0.0.0", - "api-doc.core-stack.org", - "2f2de623c34b.ngrok-free.app", - "odk.core-stack.org", - "unrecognizably-deft-aimee.ngrok-free.dev", -] -CE_API_URL = env("CE_API_URL") -CE_BUCKET_NAME = env("CE_BUCKET_NAME") -# MARK: Django Apps - -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "django_celery_beat", - # core apps - "computing", - "dpr", - "geoadmin", - "stats_generator", - # rest framework for APIs - "rest_framework", - "rest_framework_simplejwt", - "rest_framework_simplejwt.token_blacklist", - "corsheaders", - "drf_yasg", - "rest_framework_api_key", - # project applications - "organization.apps.OrganizationConfig", - "projects", - "plantations", - "plans", - "public_api", - "community_engagement", - "bot_interface", - "gee_computing", - "waterrejuvenation", - "apiadmin", - "moderation", - "users.apps.UsersConfig", - "status_monitor", -] - -# MARK: CORS Settings - -if DEBUG: - CORS_ALLOW_ALL_ORIGINS = True -else: - CORS_ALLOWED_ORIGINS = ( - env.list("CORS_ALLOWED_ORIGINS") if "CORS_ALLOWED_ORIGINS" in os.environ else [] - ) - -CORS_ALLOWED_ORIGIN_REGEXES = [ - r"^http://localhost:\d+$", - r"^http://127\.0\.0\.1:\d+$", - r"^http://192\.168\.\d{1,3}\.\d{1,3}(:\d+)?$", -] - -CORS_ALLOW_HEADERS = list(default_headers) + [ - "ngrok-skip-browser-warning", - "content-disposition", # Important for file uploads in form data - "X-API-Key", -] - -CORS_ALLOW_METHODS = [ - "DELETE", - "GET", - "OPTIONS", - "PATCH", - "POST", - "PUT", -] - -CORS_ALLOW_CREDENTIALS = True - -CSRF_TRUSTED_ORIGINS = ["http://localhost:3000"] - -# MARK: REST Framework - -REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework_simplejwt.authentication.JWTAuthentication", - ), - "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), - "DEFAULT_RENDERER_CLASSES": [ - "rest_framework.renderers.JSONRenderer", - ], -} - -# MARK: JWT settings -SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(days=90), - "REFRESH_TOKEN_LIFETIME": timedelta(days=120), - "ROTATE_REFRESH_TOKENS": True, - "BLACKLIST_AFTER_ROTATION": True, - "UPDATE_LAST_LOGIN": False, - "ALGORITHM": "HS256", - "SIGNING_KEY": SECRET_KEY, - "VERIFYING_KEY": None, - "AUDIENCE": None, - "ISSUER": None, - "AUTH_HEADER_TYPES": ("Bearer",), - "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", - "USER_ID_FIELD": "id", - "USER_ID_CLAIM": "user_id", - "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), - "TOKEN_TYPE_CLAIM": "token_type", - "JTI_CLAIM": "jti", -} - - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "corsheaders.middleware.CorsMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "django.middleware.gzip.GZipMiddleware", - # "apiadmin.middleware.ApiHitLoggerMiddleware", -] - -ROOT_URLCONF = "nrm_app.urls" - -# MARK: Templates -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "templates")], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "nrm_app.wsgi.application" - -DATA_UPLOAD_MAX_NUMBER_FILES = 1000 - -# MARK: Database -# https://docs.djangoproject.com/en/4.2/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "NAME": DB_NAME, - "USER": DB_USER, - "PASSWORD": DB_PASSWORD, - "HOST": "127.0.0.1", - "PORT": "", - } -} - -# Password validation -# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - -# Internationalization -# https://docs.djangoproject.com/en/4.2/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "Asia/Kolkata" - -USE_I18N = True - -USE_TZ = True - -# Celery -CELERY_TIMEZONE = "Asia/Kolkata" -CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.2/howto/static-files/ -AUTH_USER_MODEL = "users.User" - -STATIC_URL = "static/" -STATIC_ROOT = "static/" -ASSET_DIR = "/home/ubuntu/cfpt/core-stack-backend/assets/" - -# Media files (User uploaded content) -MEDIA_ROOT = os.path.join(BASE_DIR, "data/") -MEDIA_URL = "/media/" - -EXCEL_PATH = resolve_env_path("EXCEL_PATH", default="$BACKEND_DIR", trailing_sep=True) -EXCEL_DIR = resolve_env_path( - "EXCEL_DIR", - default="$BACKEND_DIR/data/excel_files", - trailing_sep=True, -) - -# Default primary key field type -# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -LOGGING = { - "version": 1, - "disable_existing_loggers": False, # keep Django's default loggers - "formatters": { - "verbose": { - "format": "[{levelname}] {asctime} {name} | {message}", - "style": "{", - }, - "simple": { - "format": "{levelname}: {message}", - "style": "{", - }, - }, - "handlers": { - "console": { - "level": "DEBUG", - "class": "logging.StreamHandler", - "formatter": "verbose", - }, - "mail_admins": { - "class": "django.utils.log.AdminEmailHandler", - "level": "ERROR", - }, - }, - "loggers": { - "django": { - "handlers": ["console"], - "level": "INFO", - "propagate": True, - }, - "geoadmin": { - "handlers": ["console"], - "level": "DEBUG", - "propagate": False, - }, - }, -} - -# MARK: Report requirements -OVERPASS_URL = env("OVERPASS_URL") - -# MARK: Email Settings -EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" -EMAIL_HOST = "smtpout.secureserver.net" -EMAIL_PORT = 465 -EMAIL_USE_SSL = True -EMAIL_USE_TLS = False -EMAIL_HOST_USER = env("EMAIL_HOST_USER") -EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") -EMAIL_TIMEOUT = 900 -MISSING_LAYER_RECIPIENTS = env.list("MISSING_LAYER_RECIPIENTS") -PASSWORD_RESET_TIMEOUT = 259200 # 3 days in seconds - -GEOSERVER_URL = env("GEOSERVER_URL", default="") -GEOSERVER_USERNAME = env("GEOSERVER_USERNAME", default="") -GEOSERVER_PASSWORD = env("GEOSERVER_PASSWORD", default="") - -PROD_GEOSERVER_URL = env("PROD_GEOSERVER_URL", default="") -PROD_GEOSERVER_USERNAME = env("PROD_GEOSERVER_USERNAME", default="") -PROD_GEOSERVER_PASSWORD = env("PROD_GEOSERVER_PASSWORD", default="") - -PROD_BACKEND_URL = env("PROD_BACKEND_URL", default="") -PROD_BACKEND_API_KEY = env("PROD_BACKEND_API_KEY", default="") - - -CE_BUCKET_URL = env("CE_BUCKET_URL") -EARTH_DATA_USER = env("EARTH_DATA_USER") -EARTH_DATA_PASSWORD = env("EARTH_DATA_PASSWORD") - -GEE_SERVICE_ACCOUNT_KEY_PATH = env("GEE_SERVICE_ACCOUNT_KEY_PATH") -GEE_HELPER_SERVICE_ACCOUNT_KEY_PATH = env("GEE_HELPER_SERVICE_ACCOUNT_KEY_PATH") -GEE_DATASETS_SERVICE_ACCOUNT_KEY_PATH = env("GEE_DATASETS_SERVICE_ACCOUNT_KEY_PATH") - -# gcs bucket -GCS_BUCKET_NAME = env("GCS_BUCKET_NAME") - -LOCAL_COMPUTE_API_URL = env("LOCAL_COMPUTE_API_URL") - -# NREGA settings -NREGA_BUCKET = env("NREGA_BUCKET") - -# S3 access keys -S3_SECRET_KEY = env("S3_SECRET_KEY") -S3_ACCESS_KEY = env("S3_ACCESS_KEY") - -# S3 settings -S3_BUCKET = env("S3_BUCKET") -S3_REGION = env("S3_REGION") - -# DPR S3 settings -DPR_S3_SECRET_KEY = env("DPR_S3_SECRET_KEY", default="") -DPR_S3_ACCESS_KEY = env("DPR_S3_ACCESS_KEY", default="") -DPR_S3_REGION = env("DPR_S3_REGION", default="") -DPR_S3_BUCKET = env("DPR_S3_BUCKET", default="") -DPR_S3_FOLDER = env("DPR_S3_FOLDER", default="") - -# bot_interface settings -AUTH_TOKEN_360 = env("AUTH_TOKEN_360") -ES_AUTH = env("ES_AUTH") -CALL_PATCH_API_KEY = env("CALL_PATCH_API_KEY") - -# Community Engagement API Configuration -WHATSAPP_MEDIA_PATH = resolve_env_path( - "WHATSAPP_MEDIA_PATH", - default="$BACKEND_DIR/bot_interface/whatsapp_media", - trailing_sep=True, -) - -BASE_URL = "https://geoserver.core-stack.org/" -DEFAULT_FROM_EMAIL = "CoRE Stack Support " - -PLAN_REPORT_RECIPIENTS = env.list("PLAN_REPORT_RECIPIENTS", default=[]) - -FERNET_KEY = env("FERNET_KEY") - -API_KEY = env("API_KEY", default="") - - -lulc_years = [ - "2017_2018", - "2018_2019", - "2019_2020", - "2020_2021", - "2021_2022", - "2022_2023", - "2023_2024", -] -water_classes = [2, 3, 4] - -GEE_STORAGE_PROJECT = env("GEE_STORAGE_PROJECT") -GEE_STORAGE_PROJECT_HELPER = env("GEE_STORAGE_PROJECT_HELPER") +""" +Django settings for nrm_app project. + +Generated by 'django-admin startproject' using Django 4.2.2. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +import os +from datetime import timedelta +from pathlib import Path + +import environ +from corsheaders.defaults import default_headers + +env = environ.Env() +ENV_FILE = Path(__file__).resolve().parent / ".env" +environ.Env.read_env(str(ENV_FILE)) + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent +os.environ.setdefault("BACKEND_DIR", str(BASE_DIR)) + + +def resolve_env_path(name, default="", *, trailing_sep=False): + raw_value = str(os.environ.get(name, default) or "").strip() + if not raw_value: + return "" + + raw_value = os.path.expandvars(raw_value) + candidate = Path(raw_value).expanduser() + if not candidate.is_absolute(): + candidate = BASE_DIR / candidate + + resolved = os.path.normpath(str(candidate)) + if trailing_sep: + resolved = resolved.rstrip("/\\") + os.sep + return resolved + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = env("SECRET_KEY") + + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = env.bool("DEBUG", default=False) + +# TMP File location +TMP_LOCATION = resolve_env_path( + "TMP_LOCATION", default="$BACKEND_DIR/tmp", trailing_sep=True +) + +# MARK: ODK Login Creds +ODK_USERNAME = env("ODK_USERNAME") +AUTH_TOKEN_FB_META = env("AUTH_TOKEN_FB_META") +ODK_PASSWORD = env("ODK_PASSWORD") +DEPLOYMENT_DIR = resolve_env_path("DEPLOYMENT_DIR", default="$BACKEND_DIR") +# MARK: ODK Sync Creds +ODK_USER_EMAIL_SYNC = env("ODK_USER_EMAIL_SYNC") +ODK_USER_PASSWORD_SYNC = env("ODK_USER_PASSWORD_SYNC") + +# MARK: DB settings +DB_NAME = env("DB_NAME") +DB_USER = env("DB_USER") +DB_PASSWORD = env("DB_PASSWORD") + +USERNAME_GESDISC = env("USERNAME_GESDISC") +PASSWORD_GESDISC = env("PASSWORD_GESDISC") + +STATIC_ROOT = "static/" +GEE_HELPER_ACCOUNT_ID = env("GEE_HELPER_ACCOUNT_ID") +GEE_DEFAULT_ACCOUNT_ID = env("GEE_DEFAULT_ACCOUNT_ID") +ADMIN_GROUP_ID = env("ADMIN_GROUP_ID") +ALLOWED_HOSTS = [ + "geoserver.core-stack.org", + "127.0.0.1", + "localhost", + "0.0.0.0", + "api-doc.core-stack.org", + "2f2de623c34b.ngrok-free.app", + "odk.core-stack.org", + "unrecognizably-deft-aimee.ngrok-free.dev", +] +CE_API_URL = env("CE_API_URL") +CE_BUCKET_NAME = env("CE_BUCKET_NAME") +# MARK: Django Apps + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_celery_beat", + # core apps + "computing", + "dpr", + "geoadmin", + "stats_generator", + # rest framework for APIs + "rest_framework", + "rest_framework_simplejwt", + "rest_framework_simplejwt.token_blacklist", + "corsheaders", + "drf_yasg", + "rest_framework_api_key", + # project applications + "organization.apps.OrganizationConfig", + "projects", + "plantations", + "plans", + "public_api", + "community_engagement", + "bot_interface", + "gee_computing", + "waterrejuvenation", + "apiadmin", + "moderation", + "users.apps.UsersConfig", + "status_monitor", +] + +# MARK: CORS Settings + +if DEBUG: + CORS_ALLOW_ALL_ORIGINS = True +else: + CORS_ALLOWED_ORIGINS = ( + env.list("CORS_ALLOWED_ORIGINS") if "CORS_ALLOWED_ORIGINS" in os.environ else [] + ) + +CORS_ALLOWED_ORIGIN_REGEXES = [ + r"^http://localhost:\d+$", + r"^http://127\.0\.0\.1:\d+$", + r"^http://192\.168\.\d{1,3}\.\d{1,3}(:\d+)?$", +] + +CORS_ALLOW_HEADERS = list(default_headers) + [ + "ngrok-skip-browser-warning", + "content-disposition", # Important for file uploads in form data + "X-API-Key", +] + +CORS_ALLOW_METHODS = [ + "DELETE", + "GET", + "OPTIONS", + "PATCH", + "POST", + "PUT", +] + +CORS_ALLOW_CREDENTIALS = True + +CSRF_TRUSTED_ORIGINS = ["http://localhost:3000"] + +# MARK: REST Framework + +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + ), + "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), + "DEFAULT_RENDERER_CLASSES": [ + "rest_framework.renderers.JSONRenderer", + ], +} + +# MARK: JWT settings +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(days=90), + "REFRESH_TOKEN_LIFETIME": timedelta(days=120), + "ROTATE_REFRESH_TOKENS": True, + "BLACKLIST_AFTER_ROTATION": True, + "UPDATE_LAST_LOGIN": False, + "ALGORITHM": "HS256", + "SIGNING_KEY": SECRET_KEY, + "VERIFYING_KEY": None, + "AUDIENCE": None, + "ISSUER": None, + "AUTH_HEADER_TYPES": ("Bearer",), + "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", + "USER_ID_FIELD": "id", + "USER_ID_CLAIM": "user_id", + "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), + "TOKEN_TYPE_CLAIM": "token_type", + "JTI_CLAIM": "jti", +} + + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django.middleware.gzip.GZipMiddleware", + # "apiadmin.middleware.ApiHitLoggerMiddleware", +] + +ROOT_URLCONF = "nrm_app.urls" + +# MARK: Templates +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "nrm_app.wsgi.application" + +DATA_UPLOAD_MAX_NUMBER_FILES = 1000 + +# MARK: Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": DB_NAME, + "USER": DB_USER, + "PASSWORD": DB_PASSWORD, + "HOST": "127.0.0.1", + "PORT": "", + } +} + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "Asia/Kolkata" + +USE_I18N = True + +USE_TZ = True + +# Celery +CELERY_TIMEZONE = "Asia/Kolkata" +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ +AUTH_USER_MODEL = "users.User" + +STATIC_URL = "static/" +STATIC_ROOT = "static/" +ASSET_DIR = "/home/ubuntu/cfpt/core-stack-backend/assets/" + +# Media files (User uploaded content) +MEDIA_ROOT = os.path.join(BASE_DIR, "data/") +MEDIA_URL = "/media/" + +EXCEL_PATH = resolve_env_path("EXCEL_PATH", default="$BACKEND_DIR", trailing_sep=True) +EXCEL_DIR = resolve_env_path( + "EXCEL_DIR", + default="$BACKEND_DIR/data/excel_files", + trailing_sep=True, +) + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, # keep Django's default loggers + "formatters": { + "verbose": { + "format": "[{levelname}] {asctime} {name} | {message}", + "style": "{", + }, + "simple": { + "format": "{levelname}: {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + "mail_admins": { + "class": "django.utils.log.AdminEmailHandler", + "level": "ERROR", + }, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "INFO", + "propagate": True, + }, + "geoadmin": { + "handlers": ["console"], + "level": "DEBUG", + "propagate": False, + }, + }, +} + +# MARK: Report requirements +OVERPASS_URL = env("OVERPASS_URL") +BASE_API_URL = env("BASE_API_URL") + +# MARK: Email Settings +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST = "smtpout.secureserver.net" +EMAIL_PORT = 465 +EMAIL_USE_SSL = True +EMAIL_USE_TLS = False +EMAIL_HOST_USER = env("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") +EMAIL_TIMEOUT = 900 +MISSING_LAYER_RECIPIENTS = env.list("MISSING_LAYER_RECIPIENTS") +PASSWORD_RESET_TIMEOUT = 259200 # 3 days in seconds + +GEOSERVER_URL = env("GEOSERVER_URL", default="") +GEOSERVER_USERNAME = env("GEOSERVER_USERNAME", default="") +GEOSERVER_PASSWORD = env("GEOSERVER_PASSWORD", default="") + +PROD_GEOSERVER_URL = env("PROD_GEOSERVER_URL", default="") +PROD_GEOSERVER_USERNAME = env("PROD_GEOSERVER_USERNAME", default="") +PROD_GEOSERVER_PASSWORD = env("PROD_GEOSERVER_PASSWORD", default="") + +PROD_BACKEND_URL = env("PROD_BACKEND_URL", default="") +PROD_BACKEND_API_KEY = env("PROD_BACKEND_API_KEY", default="") + + +CE_BUCKET_URL = env("CE_BUCKET_URL") +EARTH_DATA_USER = env("EARTH_DATA_USER") +EARTH_DATA_PASSWORD = env("EARTH_DATA_PASSWORD") + +GEE_SERVICE_ACCOUNT_KEY_PATH = env("GEE_SERVICE_ACCOUNT_KEY_PATH") +GEE_HELPER_SERVICE_ACCOUNT_KEY_PATH = env("GEE_HELPER_SERVICE_ACCOUNT_KEY_PATH") +GEE_DATASETS_SERVICE_ACCOUNT_KEY_PATH = env("GEE_DATASETS_SERVICE_ACCOUNT_KEY_PATH") + +# gcs bucket +GCS_BUCKET_NAME = env("GCS_BUCKET_NAME") + +LOCAL_COMPUTE_API_URL = env("LOCAL_COMPUTE_API_URL") + +# NREGA settings +NREGA_BUCKET = env("NREGA_BUCKET") + +# S3 access keys +S3_SECRET_KEY = env("S3_SECRET_KEY") +S3_ACCESS_KEY = env("S3_ACCESS_KEY") + +# S3 settings +S3_BUCKET = env("S3_BUCKET") +S3_REGION = env("S3_REGION") + +# DPR S3 settings +DPR_S3_SECRET_KEY = env("DPR_S3_SECRET_KEY", default="") +DPR_S3_ACCESS_KEY = env("DPR_S3_ACCESS_KEY", default="") +DPR_S3_REGION = env("DPR_S3_REGION", default="") +DPR_S3_BUCKET = env("DPR_S3_BUCKET", default="") +DPR_S3_FOLDER = env("DPR_S3_FOLDER", default="") + +# bot_interface settings +AUTH_TOKEN_360 = env("AUTH_TOKEN_360") +ES_AUTH = env("ES_AUTH") +CALL_PATCH_API_KEY = env("CALL_PATCH_API_KEY") + +# Community Engagement API Configuration +WHATSAPP_MEDIA_PATH = resolve_env_path( + "WHATSAPP_MEDIA_PATH", + default="$BACKEND_DIR/bot_interface/whatsapp_media", + trailing_sep=True, +) + +BASE_URL = "https://geoserver.core-stack.org/" +DEFAULT_FROM_EMAIL = "CoRE Stack Support " + +PLAN_REPORT_RECIPIENTS = env.list("PLAN_REPORT_RECIPIENTS", default=[]) + +FERNET_KEY = env("FERNET_KEY") + +API_KEY = env("API_KEY", default="") + + +lulc_years = [ + "2017_2018", + "2018_2019", + "2019_2020", + "2020_2021", + "2021_2022", + "2022_2023", + "2023_2024", +] +water_classes = [2, 3, 4] + +GEE_STORAGE_PROJECT = env("GEE_STORAGE_PROJECT") +GEE_STORAGE_PROJECT_HELPER = env("GEE_STORAGE_PROJECT_HELPER") diff --git a/templates/js/climate_vulnerability_section.js b/templates/js/climate_vulnerability_section.js new file mode 100644 index 00000000..26c1c3f5 --- /dev/null +++ b/templates/js/climate_vulnerability_section.js @@ -0,0 +1,292 @@ +// ===== CLIMATE VULNERABILITY MAP ===== +// Shows: Village boundary (yellow) + MWS boundaries (gradient green→red by stress) + Click popup with patterns + +if (document.getElementById('climateVulnerabilityMap')) { + + // Parse context data + let mwsIds = []; + let climatePatterns = {}; + + try { + mwsIds = JSON.parse('{{ mws_ids|escapejs }}'); + } catch (e) { + console.warn('Could not parse mws_ids:', e); + } + + let intensity = {}; + let mwsActivePatterns = {}; + let patternDisplayMapping = {}; + + try { + intensity = JSON.parse('{{ pattern_intensity|escapejs }}'); + } catch (e) { + console.warn('Could not parse pattern_intensity:', e); + } + + try { + mwsActivePatterns = JSON.parse('{{ mws_active_patterns|escapejs }}'); + } catch (e) { + console.warn('Could not parse mws_active_patterns:', e); + } + + try { + patternDisplayMapping = JSON.parse('{{ pattern_display_mapping|escapejs }}'); + } catch (e) { + console.warn('Could not parse pattern_display_mapping:', e); + } + + // Find max intensity for gradient normalization + const intensityValues = Object.values(intensity).filter(v => typeof v === 'number'); + const maxIntensity = Math.max(...intensityValues, 1); // At least 1 to avoid division by zero + + /** + * Get gradient color based on stress pattern count + * 0 = Green, mid = Yellow, max = Red + */ + function getStressColor(count) { + if (count === 0) return { fill: 'rgba(34, 197, 94, 0.45)', stroke: '#16a34a' }; + + const ratio = Math.min(count / maxIntensity, 1); + + if (ratio <= 0.5) { + // Green → Yellow (0 → 0.5) + const t = ratio * 2; + const r = Math.round(34 + (234 - 34) * t); + const g = Math.round(197 + (179 - 197) * t); + const b = Math.round(94 + (8 - 94) * t); + return { + fill: `rgba(${r}, ${g}, ${b}, 0.5)`, + stroke: `rgb(${Math.round(r * 0.8)}, ${Math.round(g * 0.8)}, ${Math.round(b * 0.8)})` + }; + } else { + // Yellow → Red (0.5 → 1) + const t = (ratio - 0.5) * 2; + const r = Math.round(234 + (239 - 234) * t); + const g = Math.round(179 + (68 - 179) * t); + const b = Math.round(8 + (68 - 8) * t); + return { + fill: `rgba(${r}, ${g}, ${b}, 0.5)`, + stroke: `rgb(${Math.round(r * 0.8)}, ${Math.round(g * 0.8)}, ${Math.round(b * 0.8)})` + }; + } + } + + // Create map + const climateVulnerabilityMap = new ol.Map({ + target: 'climateVulnerabilityMap', + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + attributions: 'Google Satellite Hybrid Contributors' + }), + zIndex: 1 + }) + ], + view: new ol.View({ + center: [0, 0], + zoom: 12, + projection: "EPSG:4326" + }) + }); + + // ===== 1. VILLAGE BOUNDARY (Yellow stroke, single village) ===== + try { + const villagePolygon = JSON.parse('{{ village_polygon|escapejs }}'); + + if (villagePolygon && villagePolygon.features && villagePolygon.features.length > 0) { + const villageSource = new ol.source.Vector({ + features: new ol.format.GeoJSON().readFeatures(villagePolygon, { + featureProjection: 'EPSG:4326' + }) + }); + + const villageLayer = new ol.layer.Vector({ + source: villageSource, + style: new ol.style.Style({ + fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.1)' }), + stroke: new ol.style.Stroke({ color: '#eab308', width: 2.5 }) + }), + zIndex: 10 + }); + + climateVulnerabilityMap.addLayer(villageLayer); + + const extent = villageSource.getExtent(); + if (extent && !ol.extent.isEmpty(extent)) { + climateVulnerabilityMap.getView().fit(extent, { + padding: [50, 50, 50, 50], + maxZoom: 14, + duration: 500 + }); + } + } + } catch (error) { + console.warn('Climate Vulnerability: Village polygon error:', error); + } + + // ===== 2. FETCH & COLOR MWS BOUNDARIES ===== + (async () => { + try { + if (!mwsIds || mwsIds.length === 0) { + console.warn('Climate Vulnerability: No MWS IDs provided'); + return; + } + + const mwsUrl = `https://geoserver.core-stack.org:8443/geoserver/mws_layers/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=mws_layers:deltaG_well_depth_{{ district|lower }}_{{ block|lower }}&outputFormat=application/json`; + + const response = await fetch(mwsUrl); + if (!response.ok) throw new Error(`GeoServer error: ${response.status}`); + + const mwsData = await response.json(); + + if (!mwsData.features || mwsData.features.length === 0) return; + + // Filter by mws_ids + const filteredFeatures = mwsData.features.filter(f => mwsIds.includes(f.properties.uid)); + + if (filteredFeatures.length === 0) return; + + const filteredFC = { type: "FeatureCollection", features: filteredFeatures }; + const mwsSource = new ol.source.Vector({ + features: new ol.format.GeoJSON().readFeatures(filteredFC, { + featureProjection: 'EPSG:4326' + }) + }); + + // Style each MWS based on stress intensity + const mwsLayer = new ol.layer.Vector({ + source: mwsSource, + style: function(feature) { + const uid = String(feature.get('uid')); + const count = intensity[uid] !== undefined ? intensity[uid] : 0; + const colors = getStressColor(count); + + return new ol.style.Style({ + fill: new ol.style.Fill({ color: colors.fill }), + stroke: new ol.style.Stroke({ color: colors.stroke, width: 2 }) + }); + }, + zIndex: 5 + }); + + climateVulnerabilityMap.addLayer(mwsLayer); + } catch (error) { + console.error('Climate Vulnerability: Error fetching MWS layer:', error); + } + })(); + + // ===== 3. POPUP WITH PATTERNS ===== + const popupContainer = document.createElement('div'); + popupContainer.id = 'mws-popup'; + popupContainer.style.cssText = ` + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + padding: 16px; + min-width: 250px; + max-width: 320px; + font-family: 'Georgia', 'Garamond', serif; + font-size: 13px; + display: none; + position: relative; + `; + document.body.appendChild(popupContainer); + + const popupOverlay = new ol.Overlay({ + element: popupContainer, + autoPan: true, + autoPanAnimation: { duration: 250 } + }); + climateVulnerabilityMap.addOverlay(popupOverlay); + + // Handle click + climateVulnerabilityMap.on('singleclick', function(evt) { + let featureFound = false; + + climateVulnerabilityMap.forEachFeatureAtPixel(evt.pixel, function(feature, layer) { + if (layer && layer.getZIndex && layer.getZIndex() === 5) { + const uid = String(feature.get('uid')); + const areaHa = feature.get('area_in_ha'); + + if (uid) { + const state = '{{ state|lower }}'; + const district = '{{ district|lower }}'; + const block = '{{ block|lower }}'; + const reportUrl = `{{ base_url }}/generate_mws_report/?state=${state}&district=${district}&block=${block}&uid=${uid}`; + + const count = intensity[uid] !== undefined ? intensity[uid] : 0; + const patterns = mwsActivePatterns[uid] || []; + + // Build patterns HTML + let patternsHtml = ''; + if (patterns.length > 0) { + const patternNames = patterns.map(p => patternDisplayMapping[p] || p); + patternsHtml = ` +
+ Active Stress Patterns: +
    + ${patternNames.map(name => `
  • ${name}
  • `).join('')} +
+
+ `; + } else { + patternsHtml = ` +
+ No active stress patterns +
+ `; + } + + // Stress badge color + const badgeColor = count === 0 ? '#16a34a' : count === 1 ? '#eab308' : '#ef4444'; + + popupContainer.innerHTML = ` +
+
+ MWS ID: + ${uid} +
+ + ${count} ${count === 1 ? 'pattern' : 'patterns'} + +
+ ${areaHa ? `
+ Area: + ${parseFloat(areaHa).toFixed(2)} ha +
` : ''} + ${patternsHtml} + + Show Report + +
+ × +
+ `; + + popupContainer.style.display = 'block'; + popupOverlay.setPosition(evt.coordinate); + featureFound = true; + } + } + }); + + if (!featureFound) { + popupContainer.style.display = 'none'; + } + }); + + // Cursor change on hover + climateVulnerabilityMap.on('pointermove', function(evt) { + const pixel = climateVulnerabilityMap.getEventPixel(evt.originalEvent); + let hit = false; + climateVulnerabilityMap.forEachFeatureAtPixel(pixel, function(feature, layer) { + if (layer && layer.getZIndex && layer.getZIndex() === 5) hit = true; + }); + climateVulnerabilityMap.getTargetElement().style.cursor = hit ? 'pointer' : ''; + }); + + console.log('Climate Vulnerability: Map initialized with stress gradient + pattern popups'); +} \ No newline at end of file diff --git a/templates/global_village_fetcher.js b/templates/js/global_village_fetcher.js similarity index 100% rename from templates/global_village_fetcher.js rename to templates/js/global_village_fetcher.js diff --git a/templates/js/village_map_utils.js b/templates/js/village_map_utils.js new file mode 100644 index 00000000..5b2f442b --- /dev/null +++ b/templates/js/village_map_utils.js @@ -0,0 +1,491 @@ +// ===== VILLAGE MAP UTILITY (Reusable Across All Sections) ===== + +// Shared color mapping +const VILLAGE_COLORS = { + fill: { + 'red': 'rgba(239, 68, 68, 0.6)', + 'yellow': 'rgba(234, 179, 8, 0.6)', + 'green': 'rgba(34, 197, 94, 0.6)', + 'black': 'rgba(0, 0, 0, 0.3)', + 'gray': 'rgba(200, 200, 200, 0.3)' + }, + stroke: { + 'red': '#000000', + 'yellow': '#000000', + 'green': '#000000', + 'black': '#000000', + 'gray': '#000000' + }, + currentVillageStroke: '#000000', + currentVillageStrokeWidth: 3.5, + defaultStrokeWidth: 1.0 +}; + +// Current village ID from context +const CURRENT_VILLAGE_ID = '{{ village_id }}'; +const CURRENT_VILLAGE_NAME = '{{ village_name }}'; + +// ===== SHARED: Create location pin as vector feature ===== +function createLocationPin(map, coordinate) { + // SVG pin encoded for ol.style.Icon + const svgPin = ` + + + + + `; + const svgBlob = new Blob([svgPin], { type: 'image/svg+xml' }); + const svgUrl = URL.createObjectURL(svgBlob); + + // Label feature + const labelFeature = new ol.Feature({ + geometry: new ol.geom.Point(coordinate) + }); + labelFeature.setStyle(new ol.style.Style({ + text: new ol.style.Text({ + text: 'You are here', + offsetY: -38, + font: '11px Georgia, serif', + fill: new ol.style.Fill({ color: '#ffffff' }), + backgroundFill: new ol.style.Fill({ color: 'rgba(62,39,35,0.85)' }), + padding: [3, 6, 3, 6], + backgroundStroke: new ol.style.Stroke({ color: 'transparent', width: 0 }), + textBaseline: 'bottom' + }) + })); + + // Pin icon feature + const pinFeature = new ol.Feature({ + geometry: new ol.geom.Point(coordinate) + }); + pinFeature.setStyle(new ol.style.Style({ + image: new ol.style.Icon({ + src: svgUrl, + anchor: [0.5, 1.0], // anchor at bottom-center of SVG + anchorXUnits: 'fraction', + anchorYUnits: 'fraction', + scale: 1.2 + }) + })); + + const pinSource = new ol.source.Vector({ + features: [labelFeature, pinFeature] + }); + + const pinLayer = new ol.layer.Vector({ + source: pinSource, + zIndex: 20 // above village layer + }); + + map.addLayer(pinLayer); + return pinLayer; +} + +// ===== SHARED: Create zoom toggle button overlay ===== +function createZoomToggle(map, currentFeature, villageName) { + if (!currentFeature) return; + + const tehsilExtent = null; // will be set after villages load — handled by caller + let isZoomedIn = false; + + const btnEl = document.createElement('button'); + btnEl.style.cssText = ` + position: absolute; + top: 10px; + right: 10px; + z-index: 100; + background: white; + border: 1px solid #ccc; + border-radius: 4px; + padding: 5px 10px; + font-size: 11px; + font-family: 'Georgia', serif; + color: var(--primary-dark, #3e2723); + cursor: pointer; + box-shadow: 0 2px 6px rgba(0,0,0,0.2); + white-space: nowrap; + display: flex; + align-items: center; + gap: 5px; + `; + btnEl.innerHTML = ` + + + + + Zoom to ${villageName} + `; + + // Append to map container (which must have position:relative) + map.getTargetElement().style.position = 'relative'; + map.getTargetElement().appendChild(btnEl); + + btnEl.addEventListener('click', function () { + if (!isZoomedIn) { + // Zoom in to current village + const villageExtent = currentFeature.getGeometry().getExtent(); + map.getView().fit(villageExtent, { + padding: [40, 40, 40, 40], + maxZoom: 14, + duration: 600 + }); + isZoomedIn = true; + btnEl.innerHTML = ` + + + + + Show full tehsil + `; + } else { + // Zoom out to full tehsil + map.getView().fit(map._tehsilExtent, { + padding: [30, 30, 30, 30], + duration: 600 + }); + isZoomedIn = false; + btnEl.innerHTML = ` + + + + + Zoom to ${villageName} + `; + } + }); + + return btnEl; +} + +// ===== SHARED: Get centroid of a geometry ===== +function getGeometryCentroid(geometry) { + const extent = geometry.getExtent(); + return [ + (extent[0] + extent[2]) / 2, + (extent[1] + extent[3]) / 2 + ]; +} + +/** + * Create a styled village map section + */ +function createVillageMap(config) { + const container = document.getElementById(config.targetId); + if (!container) return null; + + let mapData = {}; + try { + mapData = JSON.parse(config.mapDataJson); + } catch (e) { + console.warn(config.label + ' Map: Could not parse map data:', e); + } + + function styleFeature(feature) { + const villageId = feature.get('vill_ID'); + const isCurrentVillage = (String(villageId) === String(CURRENT_VILLAGE_ID)); + + let colorName = 'gray'; + if (mapData[villageId] && mapData[villageId][config.colorKey]) { + colorName = mapData[villageId][config.colorKey]; + } + + const fillColor = VILLAGE_COLORS.fill[colorName] || VILLAGE_COLORS.fill['gray']; + const strokeWidth = isCurrentVillage + ? VILLAGE_COLORS.currentVillageStrokeWidth + : VILLAGE_COLORS.defaultStrokeWidth; + + return new ol.style.Style({ + fill: new ol.style.Fill({ color: fillColor }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: strokeWidth + }) + }); + } + + const map = new ol.Map({ + target: config.targetId, + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + attributions: 'Google Satellite Hybrid Contributors' + }), + zIndex: 1 + }) + ], + view: new ol.View({ + center: [0, 0], + zoom: 12, + projection: 'EPSG:4326' + }) + }); + + (async () => { + try { + const geoserverData = await waitForGlobalVillages(); + + if (geoserverData && geoserverData.features) { + const vectorSource = new ol.source.Vector(); + const features = new ol.format.GeoJSON().readFeatures(geoserverData, { + featureProjection: 'EPSG:4326' + }); + + vectorSource.addFeatures(features); + + const villageLayer = new ol.layer.Vector({ + source: vectorSource, + style: styleFeature, + zIndex: 10 + }); + + map.addLayer(villageLayer); + + // Fit to full tehsil on load + const tehsilExtent = vectorSource.getExtent(); + map._tehsilExtent = tehsilExtent; + map.getView().fit(tehsilExtent, { + padding: [30, 30, 30, 30], + duration: 500 + }); + + console.log(config.label + ' Map: Added', features.length, 'village features'); + + // Location pin + zoom toggle + const currentFeature = features.find( + f => String(f.get('vill_ID')) === String(CURRENT_VILLAGE_ID) + ); + + if (currentFeature) { + const centroid = getGeometryCentroid(currentFeature.getGeometry()); + createLocationPin(map, centroid); + createZoomToggle(map, currentFeature, CURRENT_VILLAGE_NAME); + } + + map._villageLayer = villageLayer; + map._vectorSource = vectorSource; + + console.log(config.label + ' Map: Fitted to tehsil'); + } + } catch (error) { + console.error(config.label + ' Map: Error loading villages:', error); + } + })(); + + if (config.mwsesJson && config.villageJson) { + addMWSesLayer(map, config.mwsesJson, config.villageJson, config.label); + } + + return map; +} + +/** + * Create a tabbed village map + */ +function createTabbedVillageMap(config) { + const container = document.getElementById(config.targetId); + if (!container) return null; + + let mapData = {}; + try { + mapData = JSON.parse(config.mapDataJson); + } catch (e) { + console.warn(config.label + ' Map: Could not parse map data:', e); + } + + let currentTab = config.defaultTab; + + function styleFeature(feature) { + const villageId = feature.get('vill_ID'); + const isCurrentVillage = (String(villageId) === String(CURRENT_VILLAGE_ID)); + const tabConfig = config.tabs[currentTab]; + + let colorName = 'gray'; + if (mapData[villageId] && mapData[villageId][tabConfig.colorKey]) { + colorName = mapData[villageId][tabConfig.colorKey]; + } + + const fillColor = VILLAGE_COLORS.fill[colorName] || VILLAGE_COLORS.fill['gray']; + const strokeWidth = isCurrentVillage + ? VILLAGE_COLORS.currentVillageStrokeWidth + : VILLAGE_COLORS.defaultStrokeWidth; + + return new ol.style.Style({ + fill: new ol.style.Fill({ color: fillColor }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: strokeWidth + }) + }); + } + + const map = new ol.Map({ + target: config.targetId, + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + maxZoom: 30, + transition: 500, + }), + preload: 4, + }) + ], + view: new ol.View({ + center: [78.9, 23.6], + zoom: 10, + projection: 'EPSG:4326', + constrainResolution: true, + smoothExtentConstraint: true, + smoothResolutionConstraint: true, + }), + interactions: new ol.Collection([ + new ol.interaction.DragPan(), + new ol.interaction.KeyboardZoom(), + ]) + }); + + let villageLayer = null; + + (async () => { + try { + const geoserverData = await waitForGlobalVillages(); + + if (geoserverData && geoserverData.features) { + const vectorSource = new ol.source.Vector(); + const features = new ol.format.GeoJSON().readFeatures(geoserverData, { + featureProjection: 'EPSG:4326' + }); + + vectorSource.addFeatures(features); + + villageLayer = new ol.layer.Vector({ + source: vectorSource, + style: styleFeature, + zIndex: 10 + }); + + map.addLayer(villageLayer); + + // Fit to full tehsil on load + const tehsilExtent = vectorSource.getExtent(); + map._tehsilExtent = tehsilExtent; + map.getView().fit(tehsilExtent, { + padding: [30, 30, 30, 30], + duration: 500 + }); + + console.log(config.label + ' Map: Added', features.length, 'village features'); + + // Location pin + zoom toggle + const currentFeature = features.find( + f => String(f.get('vill_ID')) === String(CURRENT_VILLAGE_ID) + ); + + if (currentFeature) { + const centroid = getGeometryCentroid(currentFeature.getGeometry()); + createLocationPin(map, centroid); + createZoomToggle(map, currentFeature, CURRENT_VILLAGE_NAME); + } + } + } catch (error) { + console.error(config.label + ' Map: Error loading villages:', error); + } + })(); + + if (config.mwsesJson && config.villageJson) { + addMWSesLayer(map, config.mwsesJson, config.villageJson, config.label); + } + + function switchTab(tabKey) { + currentTab = tabKey; + const tabConfig = config.tabs[tabKey]; + + Object.entries(config.buttonIds).forEach(([key, btnId]) => { + const btn = document.getElementById(btnId); + if (btn) { + btn.style.backgroundColor = (key === tabKey) + ? 'var(--primary-accent)' + : '#bbb'; + } + }); + + if (config.infoIds) { + const titleEl = document.getElementById(config.infoIds.title); + const descEl = document.getElementById(config.infoIds.desc); + const legendEl = document.getElementById(config.infoIds.legend); + if (titleEl) titleEl.textContent = tabConfig.name; + if (descEl) descEl.textContent = tabConfig.desc; + if (legendEl) legendEl.innerHTML = tabConfig.legend; + } + + if (villageLayer) { + villageLayer.setStyle(styleFeature); + console.log(config.label + ' Map: Switched to', tabKey); + } + } + + return { map, switchTab }; +} + +/** + * Add MWSes layer to a map + */ +function addMWSesLayer(map, mwsesJson, villageJson, label) { + try { + const mwsesPolygon = JSON.parse(mwsesJson); + + if (!mwsesPolygon || !mwsesPolygon.features || mwsesPolygon.features.length === 0) { + console.warn(label + ' Map: No MWSes data available'); + return; + } + + let filteredMWSes = mwsesPolygon.features; + + try { + const villageData = JSON.parse(villageJson); + if (villageData && villageData.features && villageData.features.length > 0) { + const villageBoundary = villageData.features[0].geometry; + filteredMWSes = mwsesPolygon.features.filter(mwsFeature => { + try { + const villageBbox = getBoundingBox(villageBoundary); + const mwsBbox = getBoundingBox(mwsFeature.geometry); + return boxesIntersect(villageBbox, mwsBbox); + } catch (e) { + return true; + } + }); + } + } catch (filterError) { + console.warn(label + ' Map: Could not filter MWSes:', filterError); + } + + if (filteredMWSes.length > 0) { + const mwsesFC = { type: 'FeatureCollection', features: filteredMWSes }; + const mwsesSource = new ol.source.Vector({ + features: new ol.format.GeoJSON().readFeatures(mwsesFC, { + featureProjection: 'EPSG:4326' + }) + }); + + const mwsesLayer = new ol.layer.Vector({ + source: mwsesSource, + style: new ol.style.Style({ + fill: new ol.style.Fill({ color: 'rgba(59, 130, 246, 0.08)' }), + stroke: new ol.style.Stroke({ + color: '#2563eb', + width: 2, + lineDash: [5, 5] + }) + }), + zIndex: 5 + }); + + map.addLayer(mwsesLayer); + console.log('✓ ' + label + ' Map: MWSes layer added (' + filteredMWSes.length + ' features)'); + } + } catch (error) { + console.warn(label + ' Map: MWSes error:', error); + } +} \ No newline at end of file diff --git a/templates/mws-report.html b/templates/mws-report.html index 70c01f9f..dd17c297 100644 --- a/templates/mws-report.html +++ b/templates/mws-report.html @@ -1982,6 +1982,9 @@

MGNREGA works

+ + + + + + + +
+
+

Village Report

+

Comprehensive Analysis of Village Profile

+
+
+ +
+
+ +
+ + +
+ + + + + +
+ +

+ Data for this Village is Not Available +

+ +

+ We currently do not have sufficient data to generate a report for the selected village. This could be because the village boundary is not yet mapped, or development indicator data has not been collected for this location. +

+ + + {% if state or district or block %} +
+
+ Location: + {% if state %}{{ state }}{% endif %} + {% if district %}{{ district }}{% endif %} + {% if block %}{{ block }}{% endif %} +
+
+ {% endif %} + + +
+

What you can try:

+
    +
  • Select a different village from the map or list view.
  • +
  • Confirm that the village boundary appears correctly on the tehsil map.
  • +
  • Check back later as new village data is added periodically.
  • +
+
+ +
+ +
+
+ + +
+

CoreStack — A commoning approach — Tech stacks for communities

+
+ + \ No newline at end of file diff --git a/templates/village-report.html b/templates/village-report.html index f78a00c8..71067171 100644 --- a/templates/village-report.html +++ b/templates/village-report.html @@ -25,9 +25,6 @@ - - - - - - -
-
-

Resource and Demand Map Report

-
-
Plan Name: {{plan_name}}
-
Plan ID: {{plan_id}}
-
- -
-
- -
- -
-

Village Overview over LULC

-
-
-
-
-
- Barren Lands -
-
-
- Single Kharif -
-
-
- Single Non-Kharif -
-
-
- Double Cropping -
-
-
- Tripple/Annual/Perennial Cropping -
-
-
- Shrubs and Scrubs -
-
-
- -
- -
-

Settlement Overview for Plan : {{plan_name}}

-
-
- -
- -
- -
-

Well Overview for Plan : {{plan_name}}

-
-
-

Note: Well Distribution on Stream Order Raster

-
-
-
- 1 -
-
-
- 2 -
-
-
- 3 -
-
-
- 4 -
-
-
- 5 -
-
-
- 6 -
-
-
- 7 -
-
-
- 8 -
-
-
- 9 -
-
-
- 10 -
-
-
- 11 -
-
-
- -
- -
-

Water Structures Overview for Plan : {{plan_name}}

-
-
-
-
-
- Good recharge -
-
-
- Moderate recharge -
-
-
- Regeneration -
-
-
- High runoff zone -
-
-
- Surface water harvesting -
-
-
- -
- -
-

Demands Overview for Plan : {{plan_name}}

-
-
-
-
-
- V-shape river valleys, Deep narrow canyons -
-
-
- Lateral midslope incised drainages, Local valleys in plains -
-
-
- Upland incised drainages, Stream headwaters -
-
-
- U-shape valleys -
-
-
- Broad Flat Areas -
-
-
- Broad open slopes -
-
-
- Mesa tops -
-
-
- Upper Slopes -
-
-
- Local ridge/hilltops within broad valleys -
-
-
- Lateral midslope drainage divides, Local ridges in plains -
-
-
- Mountain tops, high ridges -
-
-
- -
-
-

Report generated on | CoRE Stack Team

-

- Please do share your feedback with - contact@core-stack.org. -

-
-
- - - - - + + + + + + + Resource Mapping Report + + + + + + + + + + + + + + + + + + + + +
+
+

Resource and Demand Map Report

+
+
Plan Name: {{plan_name}}
+
Plan ID: {{plan_id}}
+
+ +
+
+ +
+ +
+

Village Overview over LULC

+
+
+
+
+
+ Barren Lands +
+
+
+ Single Kharif +
+
+
+ Single Non-Kharif +
+
+
+ Double Cropping +
+
+
+ Tripple/Annual/Perennial Cropping +
+
+
+ Shrubs and Scrubs +
+
+
+ +
+ +
+

Settlement Overview for Plan : {{plan_name}}

+
+
+ +
+ +
+ +
+

Well Overview for Plan : {{plan_name}}

+
+
+

Note: Well Distribution on Stream Order Raster

+
+
+
+ 1 +
+
+
+ 2 +
+
+
+ 3 +
+
+
+ 4 +
+
+
+ 5 +
+
+
+ 6 +
+
+
+ 7 +
+
+
+ 8 +
+
+
+ 9 +
+
+
+ 10 +
+
+
+ 11 +
+
+
+ +
+ +
+

Water Structures Overview for Plan : {{plan_name}}

+
+
+
+
+
+ Good recharge +
+
+
+ Moderate recharge +
+
+
+ Regeneration +
+
+
+ High runoff zone +
+
+
+ Surface water harvesting +
+
+
+ +
+ +
+

Demands Overview for Plan : {{plan_name}}

+
+
+
+
+
+ V-shape river valleys, Deep narrow canyons +
+
+
+ Lateral midslope incised drainages, Local valleys in plains +
+
+
+ Upland incised drainages, Stream headwaters +
+
+
+ U-shape valleys +
+
+
+ Broad Flat Areas +
+
+
+ Broad open slopes +
+
+
+ Mesa tops +
+
+
+ Upper Slopes +
+
+
+ Local ridge/hilltops within broad valleys +
+
+
+ Lateral midslope drainage divides, Local ridges in plains +
+
+
+ Mountain tops, high ridges +
+
+
+ +
+
+

Report generated on | CoRE Stack Team

+

+ Please do share your feedback with + contact@core-stack.org. +

+
+
+ + + + + \ No newline at end of file From f43940d6fc778ace78fc35bde70d485fd96e2d55 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Thu, 18 Jun 2026 11:12:30 +0530 Subject: [PATCH 08/12] Chore : Updated Village Report and MWS Report --- templates/global_village_fetcher.js | 114 ---------------------------- 1 file changed, 114 deletions(-) delete mode 100644 templates/global_village_fetcher.js diff --git a/templates/global_village_fetcher.js b/templates/global_village_fetcher.js deleted file mode 100644 index 8f36824d..00000000 --- a/templates/global_village_fetcher.js +++ /dev/null @@ -1,114 +0,0 @@ -// ===== GLOBAL VILLAGE BOUNDARIES FETCHER (Fetch Once, Reuse Everywhere) ===== - -// Global storage for villages -window.globalVillageData = { - villages: {}, // Features by village ID - geoserverData: null, // Full GeoServer response - isLoading: false, - isLoaded: false, - error: null -}; - -/** - * Fetch all villages from GeoServer once at page load - * This data is reused by all map sections - */ -async function initializeGlobalVillages() { - if (window.globalVillageData.isLoading || window.globalVillageData.isLoaded) { - return; - } - - window.globalVillageData.isLoading = true; - - try { - const geoserverUrl = `https://geoserver.core-stack.org:8443/geoserver/panchayat_boundaries/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=panchayat_boundaries:{{ district|lower }}_{{ block|lower }}&outputFormat=application/json`; - - console.log('🌐 Fetching villages from GeoServer (Global)...'); - - const response = await fetch(geoserverUrl); - if (!response.ok) { - throw new Error(`GeoServer error: ${response.status}`); - } - - const geoserverData = await response.json(); - console.log('✓ GeoServer response received:', geoserverData.features?.length, 'villages'); - - // Store GeoServer response - window.globalVillageData.geoserverData = geoserverData; - - // Parse features and store by village ID - const features = new ol.format.GeoJSON().readFeatures(geoserverData, { - featureProjection: 'EPSG:4326' - }); - - features.forEach(feature => { - const villageId = feature.get('vill_ID'); - window.globalVillageData.villages[villageId] = feature; - }); - - console.log('✓ Stored', Object.keys(window.globalVillageData.villages).length, 'village features globally'); - - window.globalVillageData.isLoaded = true; - window.globalVillageData.isLoading = false; - - return geoserverData; - - } catch (error) { - console.error('✗ Error fetching villages:', error); - window.globalVillageData.error = error; - window.globalVillageData.isLoading = false; - return null; - } -} - -/** - * Get parsed features for a specific map section - * Parameters: - * - mapDataString: Context data (e.g., '{{ education_map|escapejs }}') - * - Returns: Array of OL features ready to add to map - */ -function getVillageFeatures(mapDataString) { - if (!window.globalVillageData.geoserverData) { - console.warn('Global villages not loaded yet'); - return []; - } - - try { - const mapData = JSON.parse(mapDataString); - const features = new ol.format.GeoJSON().readFeatures(window.globalVillageData.geoserverData, { - featureProjection: 'EPSG:4326' - }); - - return features; - } catch (error) { - console.warn('Error parsing map data:', error); - return []; - } -} - -/** - * Wait for global villages to load - */ -async function waitForGlobalVillages(maxWait = 10000) { - const startTime = Date.now(); - - while (!window.globalVillageData.isLoaded && !window.globalVillageData.error) { - if (Date.now() - startTime > maxWait) { - throw new Error('Timeout waiting for global villages'); - } - await new Promise(resolve => setTimeout(resolve, 100)); - } - - if (window.globalVillageData.error) { - throw window.globalVillageData.error; - } - - return window.globalVillageData.geoserverData; -} - -// Initialize global villages when page loads -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializeGlobalVillages); -} else { - initializeGlobalVillages(); -} \ No newline at end of file From 981110c44a131bffefdc9ea6c7a5864de570e349 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Wed, 3 Jun 2026 17:42:47 +0530 Subject: [PATCH 09/12] Chore : Working on Village Report --- templates/global_village_fetcher.js | 114 ++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 templates/global_village_fetcher.js diff --git a/templates/global_village_fetcher.js b/templates/global_village_fetcher.js new file mode 100644 index 00000000..8f36824d --- /dev/null +++ b/templates/global_village_fetcher.js @@ -0,0 +1,114 @@ +// ===== GLOBAL VILLAGE BOUNDARIES FETCHER (Fetch Once, Reuse Everywhere) ===== + +// Global storage for villages +window.globalVillageData = { + villages: {}, // Features by village ID + geoserverData: null, // Full GeoServer response + isLoading: false, + isLoaded: false, + error: null +}; + +/** + * Fetch all villages from GeoServer once at page load + * This data is reused by all map sections + */ +async function initializeGlobalVillages() { + if (window.globalVillageData.isLoading || window.globalVillageData.isLoaded) { + return; + } + + window.globalVillageData.isLoading = true; + + try { + const geoserverUrl = `https://geoserver.core-stack.org:8443/geoserver/panchayat_boundaries/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=panchayat_boundaries:{{ district|lower }}_{{ block|lower }}&outputFormat=application/json`; + + console.log('🌐 Fetching villages from GeoServer (Global)...'); + + const response = await fetch(geoserverUrl); + if (!response.ok) { + throw new Error(`GeoServer error: ${response.status}`); + } + + const geoserverData = await response.json(); + console.log('✓ GeoServer response received:', geoserverData.features?.length, 'villages'); + + // Store GeoServer response + window.globalVillageData.geoserverData = geoserverData; + + // Parse features and store by village ID + const features = new ol.format.GeoJSON().readFeatures(geoserverData, { + featureProjection: 'EPSG:4326' + }); + + features.forEach(feature => { + const villageId = feature.get('vill_ID'); + window.globalVillageData.villages[villageId] = feature; + }); + + console.log('✓ Stored', Object.keys(window.globalVillageData.villages).length, 'village features globally'); + + window.globalVillageData.isLoaded = true; + window.globalVillageData.isLoading = false; + + return geoserverData; + + } catch (error) { + console.error('✗ Error fetching villages:', error); + window.globalVillageData.error = error; + window.globalVillageData.isLoading = false; + return null; + } +} + +/** + * Get parsed features for a specific map section + * Parameters: + * - mapDataString: Context data (e.g., '{{ education_map|escapejs }}') + * - Returns: Array of OL features ready to add to map + */ +function getVillageFeatures(mapDataString) { + if (!window.globalVillageData.geoserverData) { + console.warn('Global villages not loaded yet'); + return []; + } + + try { + const mapData = JSON.parse(mapDataString); + const features = new ol.format.GeoJSON().readFeatures(window.globalVillageData.geoserverData, { + featureProjection: 'EPSG:4326' + }); + + return features; + } catch (error) { + console.warn('Error parsing map data:', error); + return []; + } +} + +/** + * Wait for global villages to load + */ +async function waitForGlobalVillages(maxWait = 10000) { + const startTime = Date.now(); + + while (!window.globalVillageData.isLoaded && !window.globalVillageData.error) { + if (Date.now() - startTime > maxWait) { + throw new Error('Timeout waiting for global villages'); + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + + if (window.globalVillageData.error) { + throw window.globalVillageData.error; + } + + return window.globalVillageData.geoserverData; +} + +// Initialize global villages when page loads +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeGlobalVillages); +} else { + initializeGlobalVillages(); +} \ No newline at end of file From b976aab8c6dec73a9053bfbd0f5b01a981838db9 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Thu, 18 Jun 2026 11:12:30 +0530 Subject: [PATCH 10/12] Chore : Updated Village Report and MWS Report --- templates/global_village_fetcher.js | 114 ---------------------------- 1 file changed, 114 deletions(-) delete mode 100644 templates/global_village_fetcher.js diff --git a/templates/global_village_fetcher.js b/templates/global_village_fetcher.js deleted file mode 100644 index 8f36824d..00000000 --- a/templates/global_village_fetcher.js +++ /dev/null @@ -1,114 +0,0 @@ -// ===== GLOBAL VILLAGE BOUNDARIES FETCHER (Fetch Once, Reuse Everywhere) ===== - -// Global storage for villages -window.globalVillageData = { - villages: {}, // Features by village ID - geoserverData: null, // Full GeoServer response - isLoading: false, - isLoaded: false, - error: null -}; - -/** - * Fetch all villages from GeoServer once at page load - * This data is reused by all map sections - */ -async function initializeGlobalVillages() { - if (window.globalVillageData.isLoading || window.globalVillageData.isLoaded) { - return; - } - - window.globalVillageData.isLoading = true; - - try { - const geoserverUrl = `https://geoserver.core-stack.org:8443/geoserver/panchayat_boundaries/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=panchayat_boundaries:{{ district|lower }}_{{ block|lower }}&outputFormat=application/json`; - - console.log('🌐 Fetching villages from GeoServer (Global)...'); - - const response = await fetch(geoserverUrl); - if (!response.ok) { - throw new Error(`GeoServer error: ${response.status}`); - } - - const geoserverData = await response.json(); - console.log('✓ GeoServer response received:', geoserverData.features?.length, 'villages'); - - // Store GeoServer response - window.globalVillageData.geoserverData = geoserverData; - - // Parse features and store by village ID - const features = new ol.format.GeoJSON().readFeatures(geoserverData, { - featureProjection: 'EPSG:4326' - }); - - features.forEach(feature => { - const villageId = feature.get('vill_ID'); - window.globalVillageData.villages[villageId] = feature; - }); - - console.log('✓ Stored', Object.keys(window.globalVillageData.villages).length, 'village features globally'); - - window.globalVillageData.isLoaded = true; - window.globalVillageData.isLoading = false; - - return geoserverData; - - } catch (error) { - console.error('✗ Error fetching villages:', error); - window.globalVillageData.error = error; - window.globalVillageData.isLoading = false; - return null; - } -} - -/** - * Get parsed features for a specific map section - * Parameters: - * - mapDataString: Context data (e.g., '{{ education_map|escapejs }}') - * - Returns: Array of OL features ready to add to map - */ -function getVillageFeatures(mapDataString) { - if (!window.globalVillageData.geoserverData) { - console.warn('Global villages not loaded yet'); - return []; - } - - try { - const mapData = JSON.parse(mapDataString); - const features = new ol.format.GeoJSON().readFeatures(window.globalVillageData.geoserverData, { - featureProjection: 'EPSG:4326' - }); - - return features; - } catch (error) { - console.warn('Error parsing map data:', error); - return []; - } -} - -/** - * Wait for global villages to load - */ -async function waitForGlobalVillages(maxWait = 10000) { - const startTime = Date.now(); - - while (!window.globalVillageData.isLoaded && !window.globalVillageData.error) { - if (Date.now() - startTime > maxWait) { - throw new Error('Timeout waiting for global villages'); - } - await new Promise(resolve => setTimeout(resolve, 100)); - } - - if (window.globalVillageData.error) { - throw window.globalVillageData.error; - } - - return window.globalVillageData.geoserverData; -} - -// Initialize global villages when page loads -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializeGlobalVillages); -} else { - initializeGlobalVillages(); -} \ No newline at end of file From a63cd7eb38034307323886c32e6742a491464e1b Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Wed, 1 Jul 2026 17:39:26 +0530 Subject: [PATCH 11/12] Chore : Completed Village report --- dpr/api.py | 10 +- dpr/gen_village_report.py | 877 +++++++++--------- templates/js/vertical_charts_fixes.js | 604 +++++++++++++ templates/resource-report.html | 126 ++- templates/village-report.html | 1206 ++++++------------------- 5 files changed, 1460 insertions(+), 1363 deletions(-) create mode 100644 templates/js/vertical_charts_fixes.js diff --git a/dpr/api.py b/dpr/api.py index a3c9ffab..387ad100 100644 --- a/dpr/api.py +++ b/dpr/api.py @@ -85,6 +85,7 @@ get_agroforestry_transition_data, ) from .gen_village_report import ( + load_block_sheets, get_village_polygon_and_info, get_development_data, get_block_development_data, @@ -97,6 +98,9 @@ get_community_institutes, get_livelihood_diversification, get_livestock_management, + get_livestock_count, + get_land_cultivation, + get_all_villages_land_cultivation, get_irrigation_Infra, get_agri_support_service, get_ecological_climate_resiliance, @@ -633,7 +637,7 @@ def generate_village_report(request): demographic_data = calculate_demographics(village_data["properties"]) # Calculate Basic Infra - basic_infra_data = get_basic_infrastructure(state, district, block, village_id) + basic_infra_data = get_basic_infrastructure(state, district, block, village_id, df=df) # Convert scores to performance categories basic_infra_performance = [ "Low" if score <= 0.33 else "High" if score > 0.66 else "Medium" @@ -641,7 +645,7 @@ def generate_village_report(request): ] # Calculate Health and Wash - health_wash_data = get_health_and_wash(state, district, block, village_id) + health_wash_data = get_health_and_wash(state, district, block, village_id, df=df, df_facilities=df_facilities) health_wash_performance = [ # Maternal & Child Health (High / Low only) "High" if health_wash_data[0] > 0.66 else "Low", @@ -657,7 +661,7 @@ def generate_village_report(request): education_data = get_education_institutions(state, district, block, village_id) # Calculate Financial Inclusion - finance_data = get_financial_inclusion(state, district, block, village_id) + finance_data = get_financial_inclusion(state, district, block, village_id, df_facilities=df_facilities) # Calculate Welfare welfare_data = get_welfare_inclusion(state, district, block, village_id) diff --git a/dpr/gen_village_report.py b/dpr/gen_village_report.py index fafcb67c..6a17ecea 100644 --- a/dpr/gen_village_report.py +++ b/dpr/gen_village_report.py @@ -15,6 +15,35 @@ DATA_DIR_TEMP = EXCEL_DIR +def _build_file_path(state, district, block): + return ( + DATA_DIR_TEMP + + state.upper() + "/" + + district.upper() + "/" + + district.lower() + "_" + block.lower() + ".xlsx" + ) + + +def load_block_sheets(state, district, block): + """Load all Excel sheets for a block once. Returns (df, df_facilities, df_nrega, df_livestock).""" + try: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + df = pd.read_excel(excel_file, sheet_name="antyodaya") + df["village_id"] = df["village_id"].astype(str).str.strip() + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") + df_facilities["censuscode2011"] = df_facilities["censuscode2011"].astype(str).str.strip() + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") + df_nrega["vill_id"] = df_nrega["vill_id"].astype(str).str.strip() + df_livestock = pd.read_excel(excel_file, sheet_name="livestock") + df_livestock["pc11_village_id"] = df_livestock["pc11_village_id"].astype(str).str.strip() + return df, df_facilities, df_nrega, df_livestock + except Exception as e: + logger.error( + "Failed to load block sheets for %s/%s/%s: %s", state, district, block, str(e) + ) + return None, None, None, None + + # ? MARK: HELPER FUNCTIONS def get_geojson(workspace, layer_name): """Construct the GeoServer WFS request URL for fetching GeoJSON data.""" @@ -230,7 +259,7 @@ def get_village_polygon_and_info(state, district, block, village_id): } -def get_development_data(state, district, block, village_id): +def get_development_data(state, district, block, village_id, df=None, df_facilities=None, df_nrega=None): def normalize_column(df, column): df[column] = df[column].astype(str).str.strip() @@ -281,25 +310,13 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) + if df is None or df_facilities is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) - df = pd.read_excel(excel_file, sheet_name="antyodaya") - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") normalize_column(df, "village_id") normalize_column(df_facilities, "censuscode2011") @@ -363,8 +380,8 @@ def get_distance_logic(row, columns, logic="max"): essential_distance = get_distance_logic( facility_row, [ - "health_sub_cen_distance", - "health_phc_distance" + "health_sub_cen_distance_in_km", + "health_phc_distance_in_km" ], logic="max" ) @@ -378,9 +395,9 @@ def get_distance_logic(row, columns, logic="max"): advanced_distance = get_distance_logic( facility_row, [ - "health_chc_distance", - "health_dis_h_distance", - "health_s_t_h_distance" + "health_chc_distance_in_km", + "health_dis_h_distance_in_km", + "health_s_t_h_distance_in_km" ], logic="min" ) @@ -408,9 +425,9 @@ def get_distance_logic(row, columns, logic="max"): essential_education_distance = get_distance_logic( facility_row, [ - "school_primary_distance", - "school_upper_primary_distance", - "school_secondary_distance" + "school_primary_distance_in_km", + "school_upper_primary_distance_in_km", + "school_secondary_distance_in_km" ], logic="max" ) @@ -418,9 +435,9 @@ def get_distance_logic(row, columns, logic="max"): higher_education_distance = get_distance_logic( facility_row, [ - "school_higher_secondary_distance", - "college_distance", - "universities_distance" + "school_higher_secondary_distance_in_km", + "college_distance_in_km", + "universities_distance_in_km" ], logic="min" ) @@ -455,10 +472,10 @@ def get_distance_logic(row, columns, logic="max"): financial_distance = get_distance_logic( facility_row, [ - "csc_distance", - "bank_mitra_distance", - "bank_branch_distance", - "bank_atm_distance" + "csc_distance_in_km", + "bank_mitra_distance_in_km", + "bank_branch_distance_in_km", + "bank_atm_distance_in_km" ], logic="max" ) @@ -561,22 +578,22 @@ def get_distance_logic(row, columns, logic="max"): agri_facility_configs = [ { - "column": "agri_industry_agri_support_infrastructure_distance", + "column": "agri_industry_agri_support_infrastructure_distance_in_km", "high_limit": 10, "medium_limit": 50, }, { - "column": "agri_industry_agri_processing_distance", + "column": "agri_industry_agri_processing_distance_in_km", "high_limit": 5, "medium_limit": 20, }, { - "column": "agri_industry_co_operatives_societies_distance", + "column": "agri_industry_co_operatives_societies_distance_in_km", "high_limit": 10, "medium_limit": 30, }, { - "column": "agri_industry_markets_trading_distance", + "column": "agri_industry_markets_trading_distance_in_km", "high_limit": 3, "medium_limit": 10, }, @@ -598,10 +615,11 @@ def get_distance_logic(row, columns, logic="max"): #* Ecology and Climate Resilience organic_farm_score = safe_float(row.get("agriculture_organic_farming_cat_value", 0)) - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) + if df_nrega is None: + df_nrega = pd.read_excel( + pd.ExcelFile(_build_file_path(state, district, block)), + sheet_name="nrega_assets_village" + ) normalize_column(df_nrega, "vill_id") @@ -663,7 +681,7 @@ def get_distance_logic(row, columns, logic="max"): return [] -def get_block_development_data(state, district, block): +def get_block_development_data(state, district, block, df=None, df_facilities=None, df_nrega=None): def normalize_column(df, column): df[column] = df[column].astype(str).str.strip() @@ -716,30 +734,15 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel(excel_file, sheet_name="antyodaya") - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df is None or df_facilities is None or df_nrega is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") + if df_nrega is None: + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") normalize_column(df, "village_id") normalize_column(df_facilities, "censuscode2011") @@ -790,8 +793,8 @@ def get_distance_logic(row, columns, logic="max"): essential_distance = get_distance_logic( facility_row, [ - "health_sub_cen_distance", - "health_phc_distance" + "health_sub_cen_distance_in_km", + "health_phc_distance_in_km" ], logic="max" ) @@ -807,9 +810,9 @@ def get_distance_logic(row, columns, logic="max"): advanced_distance = get_distance_logic( facility_row, [ - "health_chc_distance", - "health_dis_h_distance", - "health_s_t_h_distance" + "health_chc_distance_in_km", + "health_dis_h_distance_in_km", + "health_s_t_h_distance_in_km" ], logic="min" ) @@ -844,9 +847,9 @@ def get_distance_logic(row, columns, logic="max"): essential_education_distance = get_distance_logic( facility_row, [ - "school_primary_distance", - "school_upper_primary_distance", - "school_secondary_distance" + "school_primary_distance_in_km", + "school_upper_primary_distance_in_km", + "school_secondary_distance_in_km" ], logic="max" ) @@ -854,9 +857,9 @@ def get_distance_logic(row, columns, logic="max"): higher_education_distance = get_distance_logic( facility_row, [ - "school_higher_secondary_distance", - "college_distance", - "universities_distance" + "school_higher_secondary_distance_in_km", + "college_distance_in_km", + "universities_distance_in_km" ], logic="min" ) @@ -1068,22 +1071,22 @@ def get_distance_logic(row, columns, logic="max"): agri_facility_configs = [ { - "column": "agri_industry_agri_support_infrastructure_distance", + "column": "agri_industry_agri_support_infrastructure_distance_in_km", "high_limit": 10, "medium_limit": 50, }, { - "column": "agri_industry_agri_processing_distance", + "column": "agri_industry_agri_processing_distance_in_km", "high_limit": 5, "medium_limit": 20, }, { - "column": "agri_industry_co_operatives_societies_distance", + "column": "agri_industry_co_operatives_societies_distance_in_km", "high_limit": 10, "medium_limit": 30, }, { - "column": "agri_industry_markets_trading_distance", + "column": "agri_industry_markets_trading_distance_in_km", "high_limit": 3, "medium_limit": 10, }, @@ -1249,7 +1252,7 @@ def safe_float(value, default=0): return [] -def get_health_and_wash(state, district, block, village_id): +def get_health_and_wash(state, district, block, village_id, df=None, df_facilities=None): def safe_float(value, default=0): try: @@ -1276,29 +1279,13 @@ def get_distance_logic(row, columns, logic="max"): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None or df_facilities is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") df["village_id"] = ( df["village_id"] @@ -1352,8 +1339,8 @@ def get_distance_logic(row, columns, logic="max"): essential_distance = get_distance_logic( facility_row, [ - "health_sub_cen_distance", - "health_phc_distance" + "health_sub_cen_distance_in_km", + "health_phc_distance_in_km" ], logic="max" ) @@ -1361,9 +1348,9 @@ def get_distance_logic(row, columns, logic="max"): advanced_distance = get_distance_logic( facility_row, [ - "health_chc_distance", - "health_dis_h_distance", - "health_s_t_h_distance" + "health_chc_distance_in_km", + "health_dis_h_distance_in_km", + "health_s_t_h_distance_in_km" ], logic="min" ) @@ -1453,9 +1440,9 @@ def get_distance_logic(row, columns, logic="max"): essential_education_distance = get_distance_logic( facility_row, [ - "school_primary_distance", - "school_upper_primary_distance", - "school_secondary_distance" + "school_primary_distance_in_km", + "school_upper_primary_distance_in_km", + "school_secondary_distance_in_km" ], logic="max" ) @@ -1463,9 +1450,9 @@ def get_distance_logic(row, columns, logic="max"): higher_education_distance = get_distance_logic( facility_row, [ - "school_higher_secondary_distance", - "college_distance", - "universities_distance" + "school_higher_secondary_distance_in_km", + "college_distance_in_km", + "universities_distance_in_km" ], logic="min" ) @@ -1586,10 +1573,10 @@ def get_distance_logic(row, columns, logic="max"): financial_distance = get_distance_logic( facility_row, [ - "csc_distance", - "bank_mitra_distance", - "bank_branch_distance", - "bank_atm_distance" + "csc_distance_in_km", + "bank_mitra_distance_in_km", + "bank_branch_distance_in_km", + "bank_atm_distance_in_km" ], logic="max" ) @@ -1725,7 +1712,7 @@ def get_numeric(row, column): pds_distance = get_numeric( facility_row, - "pds_distance" + "pds_distance_in_km" ) if social_protection_score <= 0.33: @@ -1855,7 +1842,7 @@ def safe_float(value, default=0): return [] -def get_livelihood_diversification(state, district, block, village_id): +def get_livelihood_diversification(state, district, block, village_id, df=None): def safe_float(value, default=0): try: @@ -1865,24 +1852,9 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + df = pd.read_excel(excel_file, sheet_name="antyodaya") df["village_id"] = ( df["village_id"] @@ -2017,7 +1989,7 @@ def get_numeric(row, column): husbandry_distance = get_numeric( facility_row, - "agri_industry_dairy_animal_husbandry_distance" + "agri_industry_dairy_animal_husbandry_distance_in_km" ) veterinary_color = ( @@ -2054,33 +2026,137 @@ def get_numeric(row, column): return [] -def get_land_cultivation(state, district, block, village_id): - +def get_livestock_count(state, district, block, village_id, df_livestock=None): + + def safe_int(value): + try: + if value is None or pd.isna(value): + return None + v = int(float(value)) + return v if v >= 0 else None + except: + return None + + try: + + if df_livestock is None: + + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) + + excel_file = pd.ExcelFile(file_path) + + df_livestock = pd.read_excel( + excel_file, + sheet_name="livestock" + ) + + df_livestock["pc11_village_id"] = ( + df_livestock["pc11_village_id"] + .astype(str) + .str.strip() + ) + + village_id = str(village_id).strip() + + matched_rows = df_livestock[ + df_livestock["pc11_village_id"] == village_id + ] + + if matched_rows.empty: + logger.info( + "No livestock count data found for village_id %s in %s district, %s block", + village_id, + district, + block, + ) + return {} + + row = matched_rows.iloc[0] + + return { + "cattle": { + "male": safe_int(row.get("cattle_male")), + "female": safe_int(row.get("cattle_female")), + "total": safe_int(row.get("cattle_total")), + }, + "buffalo": { + "male": safe_int(row.get("buffalo_male")), + "female": safe_int(row.get("buffalo_female")), + "total": safe_int(row.get("buffalo_total")), + }, + "sheep": { + "male": safe_int(row.get("sheep_male")), + "female": safe_int(row.get("sheep_female")), + "total": safe_int(row.get("sheep_total")), + }, + "goat": { + "male": safe_int(row.get("goat_male")), + "female": safe_int(row.get("goat_female")), + "total": safe_int(row.get("goat_total")), + }, + "pig": { + "male": safe_int(row.get("pig_male")), + "female": safe_int(row.get("pig_female")), + "total": safe_int(row.get("pig_total")), + }, + } + + except Exception as e: + + logger.info( + "Not able to access livestock count data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + +def get_land_cultivation(state, district, block, village_id, df=None): + def safe_float(value, default=0): try: return float(value) except: return default - + try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) + if df is None: + file_path = ( + DATA_DIR_TEMP + + state.upper() + + "/" + + district.upper() + + "/" + + district.lower() + + "_" + + block.lower() + + ".xlsx" + ) - excel_file = pd.ExcelFile(file_path) + excel_file = pd.ExcelFile(file_path) - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + df = pd.read_excel( + excel_file, + sheet_name="antyodaya" + ) + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) village_id = str(village_id).strip() @@ -2097,10 +2173,50 @@ def safe_float(value, default=0): row = matched_rows.iloc[0] - land_utilization_score = safe_float(row.get("agriculture_land_cultivation_cat_value", 0)) + land_utilization_score = safe_float( + row.get("land_utilization_feat_value", 0) + ) + # Seasonal cultivation score: kharif sown / total cultivable, clamped to [0, 1] + kharif_ha = safe_float(row.get("net_sown_area_kharif_in_ha", 0)) + cultivable_ha = safe_float(row.get("total_cultivable_area_in_ha", 0)) + if cultivable_ha > 0: + seasonal_cultivation_score = min(kharif_ha / cultivable_ha, 1.0) + else: + seasonal_cultivation_score = 0.0 + + # Map color from land_utilization_feat_cluster + land_utilization_cluster = str( + row.get("land_utilization_feat_cluster", "Low") + ).strip() + if land_utilization_cluster == "High": + land_utilization_color = "green" + elif land_utilization_cluster == "Medium": + land_utilization_color = "yellow" + else: + land_utilization_color = "red" + + # Seasonal cultivation color (score-based, 3 levels) + if seasonal_cultivation_score <= 0.33: + seasonal_cultivation_color = "red" + elif seasonal_cultivation_score <= 0.66: + seasonal_cultivation_color = "yellow" + else: + seasonal_cultivation_color = "green" + + # Text cluster + cultivation_cluster = str( + row.get("agriculture_land_cultivation_cat_cluster", "Low") + ).strip() + + return [ + round(land_utilization_score, 4), # index 0 + round(seasonal_cultivation_score, 4), # index 1 + land_utilization_color, # index 2 + cultivation_cluster, # index 3 + seasonal_cultivation_color, # index 4 + ] - except Exception as e: logger.info( @@ -2113,6 +2229,64 @@ def safe_float(value, default=0): return [] +def get_all_villages_land_cultivation(state, district, block, df=None): + + try: + + if df is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + df = pd.read_excel(excel_file, sheet_name="antyodaya") + + df["village_id"] = ( + df["village_id"] + .astype(str) + .str.strip() + ) + + village_ids = [ + str(v).strip() + for v in df["village_id"].dropna().unique() + if str(v).strip() not in ("", "0") + ] + + village_data = {} + + for village_id in village_ids: + + cultivation_info = get_land_cultivation( + state, + district, + block, + village_id, + df=df + ) + + if not cultivation_info: + village_data[village_id] = { + "land_utilization_color": "black", + "seasonal_cultivation_color": "black" + } + continue + + village_data[village_id] = { + "land_utilization_color": cultivation_info[2], + "seasonal_cultivation_color": cultivation_info[4] + } + + return village_data + + except Exception as e: + + logger.info( + "Not able to access land cultivation map data for %s district, %s block. Error: %s", + district, + block, + str(e), + ) + + return {} + + def get_irrigation_Infra(state, district, block, village_id, df=None): def safe_float(value, default=0): @@ -2174,35 +2348,16 @@ def safe_float(value, default=0): ) ) - modern_irrigation_score = safe_float( - row.get( - "modern_irrigation_feat_value", - 0 - ) - ) - - # Irrigation Watershed Color - irrigation_watershed_color = ( - "green" - if irrigation_watershed_score > 0.66 - else "red" - ) - - # Modern Irrigation Color - if modern_irrigation_score <= 0.33: - modern_irrigation_color = "red" - - elif modern_irrigation_score <= 0.66: - modern_irrigation_color = "yellow" - + if irrigation_watershed_score <= 0.33: + irrigation_watershed_color = "red" + elif irrigation_watershed_score <= 0.66: + irrigation_watershed_color = "yellow" else: - modern_irrigation_color = "green" + irrigation_watershed_color = "green" return [ irrigation_watershed_score, - modern_irrigation_score, - irrigation_watershed_color, - modern_irrigation_color + irrigation_watershed_color ] except Exception as e: @@ -2335,16 +2490,19 @@ def get_distance_logic(row, columns, logic="max"): post_harvest_distance = None apmc_access_distance = None + agri_support_socities_distance = None + agri_support_infra_distance = None + agri_processing_distance = None if facility_row is not None: post_harvest_distance = get_distance_logic( facility_row, [ - "agri_industry_storage_warehousing_distance", - "agri_industry_distribution_utilities_distance", - "agri_industry_agri_processing_distance", - "agri_industry_industrial_manufacturing_distance" + "agri_industry_storage_warehousing_distance_in_km", + "agri_industry_distribution_utilities_distance_in_km", + "agri_industry_agri_processing_distance_in_km", + "agri_industry_industrial_manufacturing_distance_in_km" ], logic="min" ) @@ -2352,8 +2510,32 @@ def get_distance_logic(row, columns, logic="max"): apmc_access_distance = get_distance_logic( facility_row, [ - "apmc_distance", - "agri_industry_markets_trading_distance" + "apmc_distance_in_km", + "agri_industry_markets_trading_distance_in_km" + ], + logic="min" + ) + + agri_support_socities_distance = get_distance_logic( + facility_row, + [ + "agri_industry_co_operatives_societies_distance_in_km", + ], + logic="min" + ) + + agri_support_infra_distance = get_distance_logic( + facility_row, + [ + "agri_industry_agri_support_infrastructure_distance_in_km", + ], + logic="min" + ) + + agri_processing_distance = get_distance_logic( + facility_row, + [ + "agri_industry_agri_processing_distance_in_km", ], logic="min" ) @@ -2387,7 +2569,16 @@ def get_distance_logic(row, columns, logic="max"): if apmc_access_distance is not None else None, # index 3 agri_support_color, # index 4 - agri_market_color # index 5 + agri_market_color, # index 5 + round(agri_support_socities_distance, 2) + if agri_support_socities_distance is not None + else None, # index 6 + round(agri_support_infra_distance, 2) + if agri_support_infra_distance is not None + else None, # index 7 + round(agri_processing_distance, 2) + if agri_processing_distance is not None + else None, # index 8 ] except Exception as e: @@ -2578,27 +2769,14 @@ def safe_float(value, default=0): #? Get Tehsil Map Data -def get_all_villages_basic_infrastructure(state, district, block): +def get_all_villages_basic_infrastructure(state, district, block, df=None, df_nrega=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - # Read once - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None or df_nrega is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_nrega is None: + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") df["village_id"] = ( df["village_id"] @@ -2606,11 +2784,6 @@ def get_all_villages_basic_infrastructure(state, district, block): .str.strip() ) - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) - village_ids = ( df_nrega["vill_id"] .dropna() @@ -2677,7 +2850,7 @@ def get_all_villages_basic_infrastructure(state, district, block): return {} -def get_all_villages_health_and_wash(state, district, block): +def get_all_villages_health_and_wash(state, district, block, df=None): def safe_float(value, default=0): try: @@ -2687,24 +2860,9 @@ def safe_float(value, default=0): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + df = pd.read_excel(excel_file, sheet_name="antyodaya") df["village_id"] = ( df["village_id"] @@ -2796,32 +2954,15 @@ def safe_float(value, default=0): return {} -def get_all_villages_education_institutions( state, district, block): +def get_all_villages_education_institutions(state, district, block, df_facilities=None, df_nrega=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) - - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df_facilities is None or df_nrega is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df_nrega is None: + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") df_facilities["censuscode2011"] = ( df_facilities["censuscode2011"] @@ -2872,33 +3013,16 @@ def get_all_villages_education_institutions( state, district, block): return {} -def get_all_villages_financial_inclusion(state, district, block): +def get_all_villages_financial_inclusion(state, district, block, df_facilities=None, df_nrega=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) - - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df_facilities is None or df_nrega is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df_nrega is None: + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") df_facilities["censuscode2011"] = ( df_facilities["censuscode2011"] @@ -2955,28 +3079,18 @@ def get_all_villages_financial_inclusion(state, district, block): return {} -def get_all_villages_welfare_inclusion(state, district, block): +def get_all_villages_welfare_inclusion(state, district, block, df=None, df_facilities=None, df_nrega=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None or df_facilities is None or df_nrega is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") + if df_nrega is None: + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") df["village_id"] = ( df["village_id"] @@ -2984,22 +3098,12 @@ def get_all_villages_welfare_inclusion(state, district, block): .str.strip() ) - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) - df_facilities["censuscode2011"] = ( df_facilities["censuscode2011"] .astype(str) .str.strip() ) - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) - village_ids = [ str(v).strip() for v in df_nrega["vill_id"].dropna().unique() @@ -3043,28 +3147,13 @@ def get_all_villages_welfare_inclusion(state, district, block): return {} -def get_all_villages_community_institutes(state, district, block): +def get_all_villages_community_institutes(state, district, block, df=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + df = pd.read_excel(excel_file, sheet_name="antyodaya") df["village_id"] = ( df["village_id"] @@ -3118,33 +3207,16 @@ def get_all_villages_community_institutes(state, district, block): return {} -def get_all_villages_livestock_management(state, district, block): +def get_all_villages_livestock_management(state, district, block, df=None, df_facilities=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) - - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df is None or df_facilities is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") df["village_id"] = ( df["village_id"] @@ -3199,28 +3271,13 @@ def get_all_villages_livestock_management(state, district, block): return {} -def get_all_villages_irrigation_infra(state, district, block): +def get_all_villages_irrigation_infra(state, district, block, df=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) + if df is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + df = pd.read_excel(excel_file, sheet_name="antyodaya") df["village_id"] = ( df["village_id"] @@ -3249,15 +3306,13 @@ def get_all_villages_irrigation_infra(state, district, block): if not irrigation_info: village_data[village_id] = { - "irrigation_watershed_color": "black", - "modern_irrigation_color": "black" + "irrigation_watershed_color": "black" } continue village_data[village_id] = { - "irrigation_watershed_color": irrigation_info[2], - "modern_irrigation_color": irrigation_info[3] + "irrigation_watershed_color": irrigation_info[1] } return village_data @@ -3274,33 +3329,16 @@ def get_all_villages_irrigation_infra(state, district, block): return {} -def get_all_villages_agri_support_service(state, district, block): +def get_all_villages_agri_support_service(state, district, block, df=None, df_facilities=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) - - df_facilities = pd.read_excel( - excel_file, - sheet_name="facilities_proximity" - ) + if df is None or df_facilities is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_facilities is None: + df_facilities = pd.read_excel(excel_file, sheet_name="facilities_proximity") df["village_id"] = ( df["village_id"] @@ -3355,33 +3393,16 @@ def get_all_villages_agri_support_service(state, district, block): return {} -def get_all_villages_ecological_climate_resiliance(state, district, block): +def get_all_villages_ecological_climate_resiliance(state, district, block, df=None, df_nrega=None): try: - file_path = ( - DATA_DIR_TEMP - + state.upper() - + "/" - + district.upper() - + "/" - + district.lower() - + "_" - + block.lower() - + ".xlsx" - ) - - excel_file = pd.ExcelFile(file_path) - - df = pd.read_excel( - excel_file, - sheet_name="antyodaya" - ) - - df_nrega = pd.read_excel( - excel_file, - sheet_name="nrega_assets_village" - ) + if df is None or df_nrega is None: + excel_file = pd.ExcelFile(_build_file_path(state, district, block)) + if df is None: + df = pd.read_excel(excel_file, sheet_name="antyodaya") + if df_nrega is None: + df_nrega = pd.read_excel(excel_file, sheet_name="nrega_assets_village") df["village_id"] = ( df["village_id"] diff --git a/templates/js/vertical_charts_fixes.js b/templates/js/vertical_charts_fixes.js new file mode 100644 index 00000000..a0752eb2 --- /dev/null +++ b/templates/js/vertical_charts_fixes.js @@ -0,0 +1,604 @@ +// ===== VERTICAL BAR CHART FIXES ===== +// Replace the chart initialization code with these updated versions + +// 1. BASIC INFRASTRUCTURE CHART - VERTICAL +function initBasicInfraChart(basicInfraScores, basicInfraLabels) { + const infraCtx = document.getElementById('basicInfraChart'); + if (!infraCtx) return; + + const ctx = infraCtx.getContext('2d'); + new Chart(ctx, { + type: 'bar', + data: { + labels: basicInfraLabels, + datasets: [{ + label: 'Infrastructure Score', + data: basicInfraScores, + backgroundColor: basicInfraScores.map(value => { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + }), + borderColor: basicInfraScores.map(value => { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + layout: {padding: {top: 10, right: 20, bottom: 10, left: 10}}, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + let performance = 'Low'; + if (value > 0.33 && value <= 0.66) performance = 'Medium'; + if (value > 0.66) performance = 'High'; + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.25, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.5) return 'Medium'; + if (value === 1) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 11, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} + +// 2. COMMUNITY CHART - VERTICAL +function initCommunityChart(communityScores, communityLabels) { + const ctx = document.getElementById('communityChart'); + if (!ctx) return; + + const chartCtx = ctx.getContext('2d'); + new Chart(chartCtx, { + type: 'bar', + data: { + labels: communityLabels, + datasets: [{ + label: 'Community Score', + data: communityScores, + backgroundColor: communityScores.map(value => { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + }), + borderColor: communityScores.map(value => { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + let performance = 'Low'; + if (value > 0.33 && value <= 0.66) performance = 'Medium'; + if (value > 0.66) performance = 'High'; + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.25, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.5) return 'Medium'; + if (value === 1) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 11, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} + +// 3. LIVELIHOOD CHART - VERTICAL +function initLivelihoodChart(livelihoodScores, livelihoodLabels) { + const ctx = document.getElementById('livelihoodChart'); + if (!ctx) return; + + const chartCtx = ctx.getContext('2d'); + new Chart(chartCtx, { + type: 'bar', + data: { + labels: livelihoodLabels, + datasets: [{ + label: 'Livelihood Score', + data: livelihoodScores, + backgroundColor: livelihoodScores.map((value, index) => { + const isFarm = index === 0; + if (isFarm) { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + } else { + if (value < 0.5) return '#ef4444'; + return '#22c55e'; + } + }), + borderColor: livelihoodScores.map((value, index) => { + const isFarm = index === 0; + if (isFarm) { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + } else { + if (value < 0.5) return '#dc2626'; + return '#16a34a'; + } + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + const isFarm = context.dataIndex === 0; + let performance = 'Low'; + if (isFarm) { + if (value > 0.33 && value <= 0.66) performance = 'Medium'; + if (value > 0.66) performance = 'High'; + } else { + if (value >= 0.5) performance = 'High'; + } + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.25, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.5) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 10, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} + +// 4. LIVESTOCK CHART - VERTICAL +function initLivestockChart(livestockScores, livestockLabels) { + const ctx = document.getElementById('livestockChart'); + if (!ctx) return; + + const chartCtx = ctx.getContext('2d'); + new Chart(chartCtx, { + type: 'bar', + data: { + labels: livestockLabels, + datasets: [{ + label: 'Livestock Score', + data: livestockScores, + backgroundColor: livestockScores.map((value, index) => { + const isService = index === 0; + if (isService) { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + } else { + if (value < 0.5) return '#ef4444'; + return '#22c55e'; + } + }), + borderColor: livestockScores.map((value, index) => { + const isService = index === 0; + if (isService) { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + } else { + if (value < 0.5) return '#dc2626'; + return '#16a34a'; + } + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + const isService = context.dataIndex === 0; + let performance = 'Low'; + if (isService) { + if (value > 0.33 && value <= 0.66) performance = 'Medium'; + if (value > 0.66) performance = 'High'; + } else { + if (value >= 0.5) performance = 'High'; + } + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.25, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.5) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 11, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} + +// 5. IRRIGATION CHART - VERTICAL +// 5a. LAND CULTIVATION CHART - VERTICAL +function initLandCultivationChart(scores, labels) { + const ctx = document.getElementById('landCultivationChart'); + if (!ctx) return; + + const chartCtx = ctx.getContext('2d'); + new Chart(chartCtx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Score', + data: scores, + backgroundColor: scores.map((value) => { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + }), + borderColor: scores.map((value) => { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + let performance = 'Low'; + if (value > 0.66) performance = 'High'; + else if (value > 0.33) performance = 'Medium'; + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.33, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.33) return 'Medium'; + if (value === 0.66) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 11, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} + +function initIrrigationChart(irrigationScores, irrigationLabels) { + const ctx = document.getElementById('irrigationChart'); + if (!ctx) return; + + const chartCtx = ctx.getContext('2d'); + new Chart(chartCtx, { + type: 'bar', + data: { + labels: irrigationLabels, + datasets: [{ + label: 'Irrigation Score', + data: irrigationScores, + backgroundColor: irrigationScores.map((value) => { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + }), + borderColor: irrigationScores.map((value) => { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + let performance = 'Low'; + if (value > 0.66) performance = 'High'; + else if (value > 0.33) performance = 'Medium'; + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.33, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.33) return 'Medium'; + if (value === 0.66) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 11, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} + +// 6. AGRICULTURAL SUPPORT CHART - VERTICAL +function initAgriSupportChart(agriScores, agriSupportLabels) { + const ctx = document.getElementById('agriSupportChart'); + if (!ctx) return; + + const chartCtx = ctx.getContext('2d'); + new Chart(chartCtx, { + type: 'bar', + data: { + labels: agriSupportLabels, + datasets: [{ + label: 'Agricultural Support Score', + data: agriScores, + backgroundColor: agriScores.map((value, index) => { + const isSupport = index === 0; + if (isSupport) { + if (value <= 0.33) return '#ef4444'; + if (value <= 0.66) return '#eab308'; + return '#22c55e'; + } else { + if (value < 0.5) return '#ef4444'; + return '#22c55e'; + } + }), + borderColor: agriScores.map((value, index) => { + const isSupport = index === 0; + if (isSupport) { + if (value <= 0.33) return '#dc2626'; + if (value <= 0.66) return '#ca8a04'; + return '#16a34a'; + } else { + if (value < 0.5) return '#dc2626'; + return '#16a34a'; + } + }), + borderWidth: 1.5, + borderRadius: 4, + minBarLength: 5 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: 'rgba(62, 39, 35, 0.9)', + titleFont: {family: "'Georgia', 'Garamond', serif", size: 12, weight: 'bold'}, + bodyFont: {family: "'Georgia', 'Garamond', serif", size: 12}, + padding: 12, + displayColors: true, + callbacks: { + label: function(context) { + const value = context.parsed.y.toFixed(2); + const isSupport = context.dataIndex === 0; + let performance = 'Low'; + if (isSupport) { + if (value > 0.33 && value <= 0.66) performance = 'Medium'; + if (value > 0.66) performance = 'High'; + } else { + if (value >= 0.5) performance = 'High'; + } + return 'Score: ' + value + ' (' + performance + ')'; + } + } + } + }, + scales: { + y: { + min: 0, + max: 1, + ticks: { + stepSize: 0.25, + font: {family: "'Georgia', 'Garamond', serif", size: 11}, + color: '#6b7280', + callback: function(value) { + if (value === 0) return 'Low'; + if (value === 0.5) return 'High'; + return ''; + } + }, + grid: {color: 'rgba(0, 0, 0, 0.1)', drawBorder: true, borderColor: '#000000'} + }, + x: { + ticks: { + font: {family: "'Georgia', 'Garamond', serif", size: 11, weight: '500'}, + color: '#3e2723' + }, + grid: {display: false, drawBorder: false} + } + } + } + }); +} \ No newline at end of file diff --git a/templates/resource-report.html b/templates/resource-report.html index d5deda26..53f36660 100644 --- a/templates/resource-report.html +++ b/templates/resource-report.html @@ -329,6 +329,7 @@

Settlement Overview for Plan : {{plan_name}}

Well Overview for Plan : {{plan_name}}

+

Note: Well Distribution on Stream Order Raster

@@ -416,6 +417,16 @@

Water Structures Overview for Plan : {{plan_name}}

Demands Overview for Plan : {{plan_name}}

+
+
+
+ Irrigation Demand +
+
+
+ Groundwater Recharge +
+
@@ -464,6 +475,15 @@

Demands Overview for Plan : {{plan_name}}

+
+ +
+

Livelihood Demand Overview for Plan : {{plan_name}}

+
+
+ +
+

Report generated on | CoRE Stack Team

@@ -646,6 +666,28 @@

Demands Overview for Plan : {{plan_name}}

}) }); + const livelihoodMap = new ol.Map({ + target: 'livelihoodMap', + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: `https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}`, + maxZoom: 30, + transition: 500, + }), + preload: 4, + }) + ], + view: new ol.View({ + center: [78.9, 23.6], + zoom: 10, + projection: "EPSG:4326", + constrainResolution: true, + smoothExtentConstraint: true, + smoothResolutionConstraint: true, + }) + }); + function readFeaturesFromGeoJSON(data) { return new ol.format.GeoJSON().readFeatures(data, { dataProjection: 'EPSG:4326', // WFS usually returns EPSG:4326 @@ -688,6 +730,7 @@

Demands Overview for Plan : {{plan_name}}

wellMap.addLayer(mwsLayer); waterStructureMap.addLayer(mwsLayer); demandStructureMap.addLayer(mwsLayer); + livelihoodMap.addLayer(mwsLayer); // Center on MWS extent const arr = vectorSource.getExtent(); @@ -696,6 +739,7 @@

Demands Overview for Plan : {{plan_name}}

wellMap.getView().setCenter(mapcenter); waterStructureMap.getView().setCenter(mapcenter); demandStructureMap.getView().setCenter(mapcenter); + livelihoodMap.getView().setCenter(mapcenter); } async function addVillageBoundary() { @@ -788,24 +832,25 @@

Demands Overview for Plan : {{plan_name}}

const resp = await fetch(url); const data = await resp.json(); - const vectorSource = new ol.source.Vector({ - features: readFeaturesFromGeoJSON(data) // ensure projections match your map - }); + const features = readFeaturesFromGeoJSON(data); + + if (features.length === 0) { + document.getElementById('noWellsNote').style.display = 'block'; + document.getElementById('wellMap').style.display = 'none'; + return; + } + + const vectorSource = new ol.source.Vector({ features }); const wellLayer = new ol.layer.Vector({ source: vectorSource, style: (feature) => { const name = (feature.get('ben_settle') || '').toString(); const status = feature.values_; - let wellMaintenance = false; - if (status.Well_condi !== undefined) { - const m = status.Well_condi.match(/'select_one_maintenance'\s*:\s*'([^']*)'/i); - wellMaintenance = m ? m[1].toLowerCase() === 'yes' : null; - } - else { - const m = status.Well_usage.match(/'select_one_maintenance'\s*:\s*'([^']*)'/i); - wellMaintenance = m ? m[1].toLowerCase() === 'yes' : null; - } + const m = status.Well_usage.match( + /'is_maintenance_required'\s*:\s*'([^']*)'/i, + ); + const wellMaintenance = m ? m[1].toLowerCase() === "yes" : null; if (wellMaintenance) { return new ol.style.Style({ @@ -934,6 +979,7 @@

Demands Overview for Plan : {{plan_name}}

console.log(error); } } + async function addDemandLayer() { const svgMarkupIrrigation = `{% include 'icons/irrigation_icon.svg' %}`; const svgMarkupRecharge = `{% include 'icons/recharge_icon.svg' %}`; @@ -1058,7 +1104,58 @@

Demands Overview for Plan : {{plan_name}}

console.log(error) } + } + + async function addLivelihoodLayer() { + const svgMarkupLivelihood = `{% include 'icons/livelihood_proposed.svg' %}`; + const LivelihoodIconsUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgMarkupLivelihood); + + const urlLivelihood = + "https://geoserver.core-stack.org:8443/geoserver/works/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=works:livelihood_{{plan_id}}_{{district}}_{{block}}&outputFormat=application/json"; + + try { + const respLvlh = await fetch(urlLivelihood); + const dataLvlh = await respLvlh.json(); + + const vectorSourceLvlh = new ol.source.Vector({ + features: readFeaturesFromGeoJSON(dataLvlh) + }); + + const LivelihoodLayer = new ol.layer.Vector({ + source: vectorSourceLvlh, + style: (feature) => { + const name = (feature.get('beneficiar') || '').toString(); + return new ol.style.Style({ + image: new ol.style.Icon({ + src: LivelihoodIconsUrl, + anchor: [0.5, 1], + anchorXUnits: 'fraction', + anchorYUnits: 'fraction', + scale: 1.0 + }), + text: new ol.style.Text({ + text: name, + font: '12px sans-serif', + textAlign: 'center', + fill: new ol.style.Fill({ color: '#111' }), + stroke: new ol.style.Stroke({ color: '#fff', width: 3 }), + overflow: true + }) + }); + }, + zIndex: 20 + }); + livelihoodMap.addLayer(LivelihoodLayer); + if (vectorSourceLvlh.getFeatures().length > 0) { + const arrLvlh = vectorSourceLvlh.getExtent(); + const mapcenterLvlh = [(arrLvlh[0] + arrLvlh[2]) / 2, (arrLvlh[1] + arrLvlh[3]) / 2]; + livelihoodMap.getView().setZoom(15); + livelihoodMap.getView().setCenter(mapcenterLvlh); + } + } catch (error) { + console.log(error) + } } // Run in order @@ -1069,7 +1166,8 @@

Demands Overview for Plan : {{plan_name}}

await addSettlementLayer(); await addWellLayer(); await addWaterLayer(); - await addDemandLayer() + await addDemandLayer(); + await addLivelihoodLayer(); } catch (err) { console.error('Error building layers:', err); } @@ -1095,7 +1193,7 @@

Demands Overview for Plan : {{plan_name}}

}); (function () { - const maps = [villageMap, mainMap, wellMap, waterStructureMap, demandStructureMap].filter(Boolean); + const maps = [villageMap, mainMap, wellMap, waterStructureMap, demandStructureMap, livelihoodMap].filter(Boolean); let pending = 0; const renderedOnce = new Set(); diff --git a/templates/village-report.html b/templates/village-report.html index 71067171..09578286 100644 --- a/templates/village-report.html +++ b/templates/village-report.html @@ -335,15 +335,7 @@

Literate Population - {{ demographic_data.P_LIT }} ({{ demographic_data.literacy_percentage }}%) - - - Male - {{ demographic_data.M_LIT }} - - - Female - {{ demographic_data.F_LIT }} + {{ demographic_data.literacy_percentage }} {% if demographic_data.ADI_2011 >= 6 and demographic_data.ADI_2011 <= 18 %} @@ -389,20 +381,13 @@

-
-
+
+
- -
-

Road Connectivity

-

The road connectivity indicator takes into account the availability of all weather roads, internal pucca roads, public transport system and access to railway stations.

-
-

Road Connectivity

@@ -410,9 +395,9 @@

Road {% if basic_infra_performance.0 == 'Low' %}

This village has issues with road connectivity such as potential absence of an all-weather road, pucca roads, and lack of public transport. The village likely faces significant mobility constraints affecting service delivery, employment access, and market participation.

{% elif basic_infra_performance.0 == 'High' %} -

This village has good public transport infrastructure such as availability of all-weather roads, internal pucca road networks, and railway connectivity.

+

This village has good public transport infrastructure such as availability of all-weather roads, and internal pucca road networks.

{% else %} -

This village has moderate road connectivity with some all-weather roads and basic public transport access, though there may be room for improvement in last-mile connectivity.

+

This village may have an external road connection but weaker internal roads, or they may have some transport availability but uneven road quality.

{% endif %}

@@ -421,11 +406,11 @@

Road

Electricity Supply

{% if basic_infra_performance.1 == 'Low' %} -

The village has poor access to electricity such as lower levels of electrification, limited energy supply for enterprises, and minimal usage of clean cooking fuel.

+

The village has poor access to electricity and clean energy such as lower levels of electrification, limited energy supply for enterprises, and minimal usage of clean cooking fuel.

{% elif basic_infra_performance.1 == 'High' %}

This village likely has good electrification, access to energy for enterprises, and high adoption of clean cooking solutions.

{% else %} -

This village has moderate electricity access and moderate adoption of clean energy, with opportunities for further expansion of electrification and clean cooking fuel adoption.

+

This village may have moderate domestic electricity but incomplete clean fuel coverage or limited productive electricity.

{% endif %}
@@ -434,7 +419,7 @@

Elect

Housing Quality

{% if basic_infra_performance.2 == 'Low' %} -

Many housing demands under PMAY seem to be unmet in this village along with limited coverage under PMAY and Ujjwala schemes.

+

Many housing demands under PMAY seem to be unmet in this village along with limited coverage under PMAY and Ujjwala schemes. As a result, there is a possibility that many households are living in kutcha houses.

{% elif basic_infra_performance.2 == 'Medium' %}

This village exhibits moderate housing permanence and scheme coverage. While pucca housing rates seem to be decent, gaps remain in PMAY saturation and clean cooking fuel adoption.

{% elif basic_infra_performance.2 == 'High' %} @@ -469,28 +454,16 @@

- -
-

Maternal and Child Health

-

Captures Anganwadi coverage, maternity service utilization, institutional deliveries, child registration and immunization coverage.

-

- Green: High | - Yellow: Moderate | - Red: Low -

-
-

Maternal and Child Health

{% if health_wash_performance.0 == 'Low' %} -

There are concerns with access to maternal and child health services, including weak Anganwadi coverage, low service utilization for pregnant and lactating mothers, and poor health outcomes like anemia and low birth weight. The village may be prioritized for strengthening ICDS services, Anganwadi functionality, nutrition programs, and maternal care outreach.

+

There are concerns with access to maternal and child health services, including weak Anganwadi coverage, low service utilization for pregnant and lactating mothers, and poor health outcomes like anemia and low birth weight.

{% elif health_wash_performance.0 == 'High' %}

This village likely has adequate Anganwadi availability, good coverage of maternity services, high institutional deliveries, high rates of child registration and enrollment, and effective immunization coverage.

{% else %} -

This village shows moderate maternal and child health service coverage. While some infrastructure exists, there is room for improvement in Anganwadi services, maternal care, and immunization programs.

+

This village likely has functioning maternal-child systems in terms of moderate service utilization for pregnant and lactating mothers and Anganwadi coverage. However, outcome translation in terms of newborn health and sustained utilization of health schemes might be weak in this village.

{% endif %}

Healthcare Infrastructure Access: Primary health centers / sub-centers in this village are accessible within -- kms. Community health centers, district hospitals and tertiary hospitals are accessible at a distance of -- kms and beyond. @@ -503,9 +476,9 @@

Mater

Water and Sanitation

{% if health_wash_performance.1 == 'Low' %} -

The village has weak infrastructure for piped water and sanitation facilities. Drainage and waste management systems might need strengthening.

+

The village has weak infrastructure for piped water and sanitation facilities. Drainage and waste management systems should be investigated.

{% elif health_wash_performance.1 == 'Medium' %} -

This village shows moderate piped water access and sanitation coverage. The drainage and waste management infrastructure should be checked for quality and accordingly prioritized for improvement.

+

This village shows moderate piped water access and sanitation coverage.

{% elif health_wash_performance.1 == 'High' %}

This village has good access to piped water, sanitation facilities, covered drainage systems and better adoption of waste recycling or biogas facilities, indicating advanced WASH outcomes.

{% endif %} @@ -523,7 +496,7 @@

-

This village has primary education institutions covered within the reach of -- kms. Higher educational institutions, including access to higher secondary schools, colleges, and universities are accessible at a distance of -- kms and beyond.

+

This village has primary education institutions covered within the reach of -- kms. Primary education comprises access to primary, upper primary, and secondary education. Higher educational institutions, including access to higher secondary schools, colleges, and universities are accessible at a distance of -- kms and beyond.

@@ -559,18 +532,6 @@

- - -
-

Financial Inclusion - Access Quality:

-

- Financial inclusion indicates presence of a bank branch, presence of ATM services, availability of business correspondent services, proportion of SHGs accessing bank loans, minority households receiving bank credit, proportion of households with PMJDY accounts. -

-

- Green: Strong access to financial services (High financial inclusion)
- Red: Limited access to formal banking services (Low financial inclusion) -

-

@@ -591,19 +552,6 @@

- - -
-

Social Protection - Scheme Coverage Quality:

-

- Social protection accounts for BPL, NFSA, and pension coverage along with PDS utilization. -

-

- Green: Good coverage of welfare schemes (High social protection)
- Yellow: Moderate coverage with scope for improvement (Medium social protection)
- Red: Weak coverage of welfare schemes (Low social protection) -

-

@@ -633,25 +581,13 @@

-
-
+
+
- -
-

Institutionalization Strength

-

Captured in terms of the proportion of households mobilized into Self-Help Groups, the share of SHGs federated into Village Organizations, household participation in producer groups, presence of a Farmer Producer Organization.

-

- Green: High | - Yellow: Moderate | - Red: Low -

-
-

SHG and FPO led Federation Networks

@@ -676,23 +612,6 @@

- -
-
-
- High -
-
-
- Medium -
-
-
- Low -
-
- @@ -762,24 +681,13 @@

-
-
+
+
- -
-

Livestock Service Quality

-

Availability of veterinary services, livestock development projects, and milk collection routes.

-

- Green: High | - Red: Low -

-
-

Livestock Management Service Quality

@@ -790,58 +698,86 @@

Commo

+ +
-

Note: Indicators describing the common pasture access of this village are taken from publicly available data from Mission Antyodaya, 2020. Indicator on distance from dairy processing and animal husbandry centers is fetched from the Pradhan Mantri Gram Sadak Yojana (PMGSY) project, released publicly in 2023.

+

Note: Indicators describing the common pasture access of this village are taken from publicly available data from Mission Antyodaya, 2020. Indicator on distance from dairy processing and animal husbandry centers is fetched from the Pradhan Mantri Gram Sadak Yojana (PMGSY) project, released publicly in 2023. The livestock count for the village is derived from the 20th Livestock Census held in 2019.

- -
-

Irrigation Infrastructure and Farming Techniques

+ +
+

Agricultural Land Cultivation Patterns

- -
-
+
-
-
- +
+
+
- -
-

Irrigation Watershed Access

-

Presence of irrigation canals, rainwater harvesting structures, and watershed projects supporting water management.

-

- Green: Good | - Red: Poor -

+
+

Agricultural Land Cultivation Patterns

+
-
-

Irrigation Watershed Access

-
+
+

Note: Source of these indicators is the publicly available data from Mission Antyodaya, 2020.

+
+
+ + +
+

Irrigation Infrastructure and Farming Techniques

+ + +
+
+
+
+
+
+ +
+
-

Modern Irrigation Adoption

-
+

Irrigation Watershed Access

+
@@ -870,25 +806,13 @@

-
-
+
+
- -
-

Agricultural Support Services

-

Agricultural support services are assessed in terms of proportion of farmers registered under farmer pension schemes, coverage of pension support for young farmers, and proportion of farmers who have undertaken soil testing.

-

- Green: High | - Yellow: Moderate | - Red: Low -

-
-

Agricultural Support Services

@@ -921,13 +845,6 @@

Natural
-
-

MGNREGA Work Uptake:

-

- Green: Good uptake (>100 assets)
- Red: Poor uptake (≤100 assets) -

-
@@ -984,14 +901,6 @@

Organic
-
-

Organic Farming Adoption:

-

- Green: High adoption - Yellow: Moderate adoption - Red: Low adoption -

-
@@ -1004,13 +913,6 @@

Organic

- {% comment %}
-

Climate Vulnerability

- -
- -
{% endcomment %} -
@@ -1020,6 +922,8 @@

+ +