From 717eb583250b9f5779dec663f3772824360cb440 Mon Sep 17 00:00:00 2001 From: Ksheetiz Agrahari Date: Mon, 25 May 2026 11:27:45 +0530 Subject: [PATCH 01/38] Fix : Updated LULC Legend --- templates/mws-report.html | 12 ++++++------ templates/resource-report.html | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/templates/mws-report.html b/templates/mws-report.html index 4e82ca3f..172114ba 100644 --- a/templates/mws-report.html +++ b/templates/mws-report.html @@ -1764,23 +1764,23 @@

Average percentage of double cropped area

Barren Lands
-
+
Single Kharif
-
+
Single Non-Kharif
-
+
Double Cropping
-
- Triple Cropping +
+ Tripple/Annual/Perennial Cropping
-
+
Shrubs and Scrubs
diff --git a/templates/resource-report.html b/templates/resource-report.html index 61be061c..2e9f399b 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
From 2f41eea57c2812c6701f07ce195dda01ab1371f4 Mon Sep 17 00:00:00 2001 From: Ksheetiz-24 Date: Mon, 25 May 2026 21:03:59 +0530 Subject: [PATCH 02/38] Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --- templates/mws-report.html | 14 +++++++------- templates/resource-report.html | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/templates/mws-report.html b/templates/mws-report.html index b5cb7647..6993d9f1 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
@@ -1764,23 +1764,23 @@

Average percentage of double cropped area

Barren Lands
-
+
Single Kharif
-
+
Single Non-Kharif
-
+
Double Cropping
-
+
Triple Cropping
-
+
Shrubs and Scrubs
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
From 2e965b9bb4d7512b85ef4489c11dec976313d617 Mon Sep 17 00:00:00 2001 From: Ksheetiz-24 Date: Fri, 5 Jun 2026 12:03:17 +0530 Subject: [PATCH 03/38] Dev (#994) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari --------- Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Aman Verma Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: kapildadheech --- .../lulc_X_terrain/lulc_on_plain_cluster.py | 68 ++++++++-- computing/misc/facilities_proximity.py | 35 +++-- stats_generator/mws_indicators.py | 43 +++--- stats_generator/utils.py | 20 +++ stats_generator/village_indicators.py | 123 +++++++++++++----- templates/resource-report.html | 10 +- 6 files changed, 208 insertions(+), 91 deletions(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index a46796e5..00c073ed 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -4,6 +4,8 @@ sync_layer_to_geoserver, save_layer_info_to_db, update_layer_sync_status, + create_chunk, + merge_chunks, ) from utilities.gee_utils import ( ee_initialize, @@ -13,9 +15,10 @@ is_gee_asset_exists, export_vector_asset_to_gee, make_asset_public, + get_gee_dir_path, ) from .utils import aez_lulcXterrain_cluster_centroids, process_mws, calculate_area -from utilities.constants import AEZ +from utilities.constants import AEZ, GEE_HELPER_PATH @app.task(bind=True) @@ -28,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters" + + "_lulcXplains_clusters_bk02_june" ) asset_id = get_gee_asset_path(state, district, block) + asset_description @@ -81,13 +84,62 @@ def lulc_on_plain_cluster( ) plain_centroids = aez_lulcXterrain_cluster_centroids[f"aez{aez_no}"]["plains"] - result = process_feature_collection( - plain_mwsheds, study_area_landforms, study_area_lulc, plain_centroids + chunk_size = 50 + rois, descs = create_chunk(mwsheds, asset_description, chunk_size) + + + tasks = [] + temp_assets = [] + for roi, desc in zip(rois, descs): + chunk_with_clusters = process_mws(roi) + plain_chunk = chunk_with_clusters.filter( + ee.Filter.neq("terrain_cluster", 2) + ) + + + result_chunk = process_feature_collection( + plain_chunk, study_area_landforms, study_area_lulc, plain_centroids + ) + + chunk_asset_id = get_gee_dir_path([state, district, block], GEE_HELPER_PATH) + desc + temp_assets.append(chunk_asset_id) + + + task = export_vector_asset_to_gee( + result_chunk, desc, chunk_asset_id + ) + if task: + tasks.append(task) + + + print("Started all chunk tasks") + task_id_list = check_task_status(tasks) + print("All chunk tasks completed:", task_id_list) + + + # Merge all chunks into one feature collection + print("Starting merge task") + final_task_id = merge_chunks( + mwsheds, + [state, district, block], + asset_description, + chunk_size, + merge_asset_id=asset_id, ) - print("Processing completed successfully") - task = export_vector_asset_to_gee(result, asset_description, asset_id) - task_id_list = check_task_status([task]) - print("lulc_on_slope_cluster task completed - task_id_list:", task_id_list) + if final_task_id: + final_task_status = check_task_status([final_task_id]) + print("Final merge task completed:", final_task_status) + + + # Clean up temporary assets + for chunk_id in temp_assets: + if is_gee_asset_exists(chunk_id): + try: + ee.data.deleteAsset(chunk_id) + print(f"Deleted temp asset {chunk_id}") + except Exception as e: + print(f"Failed to delete {chunk_id}: {e}") + layer_at_geoserver = False if is_gee_asset_exists(asset_id): diff --git a/computing/misc/facilities_proximity.py b/computing/misc/facilities_proximity.py index 5eaa468b..9dae5b08 100644 --- a/computing/misc/facilities_proximity.py +++ b/computing/misc/facilities_proximity.py @@ -4,16 +4,6 @@ Filters village facilities data from GEE by tehsil boundary and exports to GEE asset + GeoServer. Uses admin boundary clipping (spatial filtering) for fast server-side processing. -Usage: - python -c " - import os - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nrm_app.settings') - import django - django.setup() - from computing.misc.facilities_proximity import generate_facilities_proximity - generate_facilities_proximity('Odisha', 'Koraput', 'Jaypur', gee_account_id=1) - " - GEE Asset: projects/corestack-datasets/assets/datasets/pan_india_facilities """ @@ -65,9 +55,22 @@ def _dissolve_admin_boundary(admin_boundary): Merge repeated admin rows with the same village properties into one geometry. This preserves full village shapes while preventing split polygon parts from - producing repeated output rows with identical attributes. + producing repeated output rows with identical attributes. Includes a schema + validation check to discard malformed village rows missing expected properties. """ - admin_export_fc = admin_boundary.select( + # Filter out any feature that does not contain ALL required source fields + def filter_complete_schemas(feature): + props = feature.propertyNames() + has_all_fields = ee.List(ADMIN_BOUNDARY_SOURCE_FIELDS).map( + lambda field: props.contains(field) + ).reduce(ee.Reducer.min()) + return feature.set('has_complete_schema', has_all_fields) + + filtered_admin = admin_boundary.map(filter_complete_schemas).filter( + ee.Filter.eq('has_complete_schema', 1) + ) + + admin_export_fc = filtered_admin.select( ADMIN_BOUNDARY_SOURCE_FIELDS, ADMIN_BOUNDARY_EXPORT_FIELDS, ) @@ -143,14 +146,6 @@ def generate_facilities_proximity(state, district, block, gee_account_id): """ Generate facilities proximity layer for a tehsil/block. - Steps: - 1. Initialize GEE - 2. Create Output Asset ID - 3. Filter facilities by admin boundary (spatial clipping) - 4. Export as GEE asset - 5. Make asset public and Register in database - 6. Sync to GeoServer - Args: state: State name (e.g., "Odisha") district: District name (e.g., "Koraput") diff --git a/stats_generator/mws_indicators.py b/stats_generator/mws_indicators.py index bd8c93a2..bf8f311b 100644 --- a/stats_generator/mws_indicators.py +++ b/stats_generator/mws_indicators.py @@ -785,7 +785,7 @@ def sens_slope(data): ############ MWS Intersect Swb ######################## try: - swb_df = sheets.get("mws_intersect_swb") + swb_df = sheets["mws_intersect_swb"] if swb_df is not -1 and not swb_df.empty: mws_swb_data = swb_df[swb_df["UID"] == specific_mws_id] @@ -820,7 +820,7 @@ def sens_slope(data): ############ DEM (Digital Elevation Model) ######################## try: - dem_df = sheets.get("dem") + dem_df = sheets["dem"] if dem_df is not -1 and not dem_df.empty: mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] @@ -859,7 +859,7 @@ def sens_slope(data): ############ Canal ######################## try: - canal_df = sheets.get("canal") + canal_df = sheets["canal"] if canal_df is not -1 and not canal_df.empty: mws_canal_data = canal_df[canal_df["UID"] == specific_mws_id] if not mws_canal_data.empty: @@ -876,7 +876,7 @@ def sens_slope(data): ############ Canal ######################## try: - river_df = sheets.get("river") + river_df = sheets["river"] if river_df is not -1 and not river_df.empty: mws_river_data = river_df[river_df["UID"] == specific_mws_id] if not mws_river_data.empty: @@ -897,7 +897,7 @@ def sens_slope(data): lulc_forest_percent = 0 lulc_crop_percent = 0 - lulc_df = sheets.get("lulc_vector") + lulc_df = sheets["lulc_vector"] if lulc_df is not -1 and not lulc_df.empty: @@ -933,23 +933,6 @@ def sens_slope(data): 2, ) - # Crop - crop_cols = [ - col - for col in lulc_df.columns - if ( - col.startswith("single_kharif_in_ha_") - or col.startswith("single_non_kharif_in_ha_") - or col.startswith("double_crop_in_ha_") - or col.startswith("triple_crop_in_ha_") - ) - ] - - lulc_crop_area = round( - sum(row[col] for col in crop_cols) / len(crop_cols), 2 - ) - - # Percentage calculation if area_in_ha > 0: lulc_shrub_percent = round( (lulc_shrub_area / area_in_ha) * 100, 2 @@ -959,9 +942,17 @@ def sens_slope(data): (lulc_forest_area / area_in_ha) * 100, 2 ) - lulc_crop_percent = round( - (lulc_crop_area / area_in_ha) * 100, 2 - ) + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + + crp_row = df_crp_intensity_mws_data.iloc[0] + area_in_ha = float(crp_row.get("area_in_ha", 0)) + cropped_area_in_ha = float(crp_row.get("sum_area_in_ha", 0)) + + lulc_crop_percent = round( + (cropped_area_in_ha / area_in_ha) * 100, 2 + ) except Exception as e: print(f"Error in LULC vector: {e}") @@ -972,7 +963,7 @@ def sens_slope(data): ############ Canal ######################## try: - drainage_density_df = sheets.get("drainage_density") + drainage_density_df = sheets["drainage_density"] if drainage_density_df is not -1 and not drainage_density_df.empty: mws_drainage_density_data = drainage_density_df[ drainage_density_df["UID"] == specific_mws_id diff --git a/stats_generator/utils.py b/stats_generator/utils.py index edadbd03..6a38a648 100644 --- a/stats_generator/utils.py +++ b/stats_generator/utils.py @@ -244,6 +244,8 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) elif workspace == "drainage_density": create_excel_for_drainage_density(geojson_data, writer) + elif workspace == "antyodaya_analysis": + create_excel_for_antyodaya_20(geojson_data, writer) results.append( {"layer": layer_name, "status": "success", "workspace": workspace} @@ -252,6 +254,23 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): return results +def create_excel_for_antyodaya_20(data, writer): + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Keep important columns first if they exist + first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + # Round numeric columns + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="antyodaya", index=False) + print("Excel file created for antyodaya") + + def create_excel_for_drainage_density(data, writer): print("Inside create_excel_for Drainage Density") df_data = [] @@ -2159,6 +2178,7 @@ def add_sheets_to_excel(state, district, block, sheets): from .mws_indicators import generate_mws_data_for_kyl_filters from .village_indicators import get_generate_filter_data_village + from public_api.views import get_tehsil_json get_tehsil_json(state, district, block, 1) diff --git a/stats_generator/village_indicators.py b/stats_generator/village_indicators.py index 4634a9ea..b3bbdee7 100644 --- a/stats_generator/village_indicators.py +++ b/stats_generator/village_indicators.py @@ -147,7 +147,9 @@ def extract_soc_eco(df_soc_eco_indi, v_id): def get_generate_filter_data_village(state, district, block, regenerate=0): + print("Generation of village filter json") + state_folder = state.replace(" ", "_").upper() district_folder = district.replace(" ", "_").upper() @@ -162,21 +164,45 @@ def get_generate_filter_data_village(state, district, block, regenerate=0): xlsx_file = file_xl_path + ".xlsx" json_path = file_xl_path + "_KYL_village_data.json" + # Return existing json if already generated if not regenerate and os.path.exists(json_path): with open(json_path, "rb") as file: response = HttpResponse(file.read(), content_type="application/json") response["Content-Disposition"] = ( - f"attachment; filename={district}_{block}_KYL_village_data.json" + f"attachment; " f"filename={district}_{block}_KYL_village_data.json" ) + return response + # -------------------------------------------------- + # Mandatory sheet check + # -------------------------------------------------- try: df_soc_eco_indi = pd.read_excel( xlsx_file, sheet_name="social_economic_indicator" ) + + if df_soc_eco_indi.empty: + raise ValueError("Empty social_economic_indicator sheet") except Exception as e: - print("Failed to load social_economic_indicator:", e) - df_soc_eco_indi = pd.DataFrame() + print("No data found for panchayat boundary:", e) + + empty_data = [] + + # Save empty json + with open(json_path, "w") as f: + json.dump(empty_data, f, indent=4) + + return HttpResponse( + json.dumps( + { + "message": "No data found for the panchayat boundary", + "data": empty_data, + } + ), + content_type="application/json", + status=200, + ) try: df_nrega_village = pd.read_excel(xlsx_file, sheet_name="nrega_assets_village") @@ -190,40 +216,73 @@ def get_generate_filter_data_village(state, district, block, regenerate=0): print("Failed to load facilities_proximity:", e) df_facilities = pd.DataFrame() + # -------------------------------------------------- + # Generate village json + # -------------------------------------------------- results = [] - if not df_soc_eco_indi.empty: - for v_id in df_soc_eco_indi["village_id"].unique(): - if v_id == 0: - continue + for v_id in df_soc_eco_indi["village_id"].dropna().unique(): + if v_id == 0: + continue + try: soc_eco = extract_soc_eco(df_soc_eco_indi, v_id) - total_assets = extract_nrega(df_nrega_village, v_id) - fac_data = extract_facilities(df_facilities, v_id) - - results.append( - { - "village_id": v_id, - **soc_eco, - "total_assets": total_assets, - **fac_data, - } + except Exception as e: + print(f"extract_soc_eco failed " f"for village {v_id}: {e}") + soc_eco = {} + + # ---------------------------------------------- + # NREGA data + # ---------------------------------------------- + try: + total_assets = ( + extract_nrega(df_nrega_village, v_id) + if not df_nrega_village.empty + else 0 ) - - results_list = pd.DataFrame(results).to_dict(orient="records") - - with open(json_path, "w") as f: - json.dump(results_list, f, indent=4) - - if os.path.exists(json_path): - with open(json_path, "rb") as file: - response = HttpResponse(file.read(), content_type="application/json") - response["Content-Disposition"] = ( - f"attachment; filename={district}_{block}_KYL_village_data.json" + except Exception as e: + print(f"extract_nrega failed " f"for village {v_id}: {e}") + total_assets = 0 + + # ---------------------------------------------- + # Facilities data + # ---------------------------------------------- + try: + fac_data = ( + extract_facilities(df_facilities, v_id) + if not df_facilities.empty + else {} ) - return response + except Exception as e: + print(f"extract_facilities failed " f"for village {v_id}: {e}") + fac_data = {} + + # ---------------------------------------------- + # Final village object + # ---------------------------------------------- + results.append( + { + "village_id": int(v_id), + **soc_eco, + "total_assets": total_assets, + **fac_data, + } + ) - return Response( - {"status": "error", "message": "Failed to generate village data file"}, - status=status.HTTP_404_NOT_FOUND, + # -------------------------------------------------- + # Save generated json + # -------------------------------------------------- + with open(json_path, "w") as f: + json.dump(results, f, indent=4, default=str) + + # -------------------------------------------------- + # Return response + # -------------------------------------------------- + return HttpResponse( + json.dumps( + {"message": "Village data generated successfully", "data": results}, + default=str, + ), + content_type="application/json", + status=200, ) 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
From 72ff13cc1ff6db6b9422a4dbe5fdda0d91f242b9 Mon Sep 17 00:00:00 2001 From: Pawan Date: Tue, 9 Jun 2026 18:15:05 +0530 Subject: [PATCH 04/38] Embed google recaptcha --- nrm_app/settings.py | 2 +- users/views.py | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 19a80786..f8a30ce0 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -418,7 +418,7 @@ def resolve_env_path(name, default="", *, trailing_sep=False): FERNET_KEY = env("FERNET_KEY") API_KEY = env("API_KEY", default="") - +RECAPTCHA_SECRET_KEY = env("RECAPTCHA_SECRET_KEY", default="") lulc_years = [ "2017_2018", diff --git a/users/views.py b/users/views.py index 0949e77e..29a74bbe 100644 --- a/users/views.py +++ b/users/views.py @@ -1,4 +1,5 @@ import logging +import requests from django.conf import settings from django.contrib.auth.models import Group @@ -96,6 +97,18 @@ def post(self, request, *args, **kwargs): status=status.HTTP_201_CREATED, ) +def verify_recaptcha(token): + response = requests.post( + "https://www.google.com/recaptcha/api/siteverify", + data={ + "secret": settings.RECAPTCHA_SECRET_KEY, + "response": token, + }, + ) + + result = response.json() + + return result.get("success", False) class LoginView(TokenObtainPairView): """ @@ -107,16 +120,30 @@ class LoginView(TokenObtainPairView): def post(self, request, *args, **kwargs): # Call parent class method to validate credentials and get tokens - response = super().post(request, *args, **kwargs) + captcha_token = request.data.get("captcha") + + if not captcha_token: + return Response( + {"message": "Captcha is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if not verify_recaptcha(captcha_token): + return Response( + {"message": "Invalid captcha"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + response = super().post(request, *args, **kwargs) - token = response.data.get("access") - jwt_auth = JWTAuthentication() - validated_token = jwt_auth.get_validated_token(token) - user = jwt_auth.get_user(validated_token) + token = response.data.get("access") + jwt_auth = JWTAuthentication() + validated_token = jwt_auth.get_validated_token(token) + user = jwt_auth.get_user(validated_token) - response.data["user"] = UserSerializer(user).data + response.data["user"] = UserSerializer(user).data - return response + return response class LogoutView(generics.GenericAPIView): From cef380564f8b92cebcbacbe6ddae5d075a5ab13f Mon Sep 17 00:00:00 2001 From: "Ankit K." Date: Wed, 10 Jun 2026 13:54:53 +0530 Subject: [PATCH 05/38] Dev (#1008) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari * Feature/runtime layer (#998) * runtime layers using CSDB * crop grid layer * runtime layers using CSDB * runtime layers using CSDB -- merge resolved --------- Co-authored-by: Ksheetiz-24 Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Aman Verma Co-authored-by: pawangramvaani Co-authored-by: kapildadheech --- dpr/models.py | 3 + moderation/tests.py | 65 +- moderation/utils/update_csdb.py | 83 +- moderation/utils/utils.py | 11 + plans/api.py | 1268 ++++++++++------- plans/build_layer.py | 277 +++- plans/tests.py | 242 ++++ plans/utils.py | 523 ++++--- plans/views.py | 2 +- .../admin/dpr/dpr_report/change_list.html | 11 + 10 files changed, 1677 insertions(+), 808 deletions(-) create mode 100644 templates/admin/dpr/dpr_report/change_list.html diff --git a/dpr/models.py b/dpr/models.py index 82340ec5..83f7b33e 100644 --- a/dpr/models.py +++ b/dpr/models.py @@ -305,7 +305,10 @@ class ODK_crop(models.Model): plan_id = models.TextField() plan_name = models.TextField() status_re = models.TextField() + latitude = models.FloatField(null=True, blank=True) + longitude = models.FloatField(null=True, blank=True) system = models.JSONField() + gps_point = models.JSONField(default=dict, null=True, blank=True) data_crop = models.JSONField(default=dict, null=True, blank=True) is_moderated = models.BooleanField(default=False, blank=True, null=True) moderated_at = models.DateTimeField(null=True, blank=True) diff --git a/moderation/tests.py b/moderation/tests.py index 7ce503c2..fbdf1b9f 100644 --- a/moderation/tests.py +++ b/moderation/tests.py @@ -1,3 +1,66 @@ +from unittest.mock import MagicMock, patch + from django.test import TestCase -# Create your tests here. +from moderation.utils.update_csdb import sync_form_type + + +class SyncFormTypeTest(TestCase): + + def test_returns_false_for_unknown_resource_type(self): + result = sync_form_type("unknown_type") + self.assertFalse(result) + + @patch("moderation.utils.update_csdb.get_edited_updated_all_submissions") + @patch("moderation.utils.update_csdb.get_dynamic_filter_query", return_value="$filter=...") + def test_returns_true_and_calls_resync_on_success(self, mock_filter, mock_client_cls): + mock_client = MagicMock() + mock_client.get_edited_updated_submissions.return_value = [] + mock_client_cls.return_value = mock_client + + with patch("moderation.utils.update_csdb.resync_settlement") as mock_resync: + result = sync_form_type("settlement") + + self.assertTrue(result) + mock_resync.assert_called_once_with([]) + + @patch("moderation.utils.update_csdb.get_edited_updated_all_submissions") + @patch("moderation.utils.update_csdb.get_dynamic_filter_query", return_value="$filter=...") + def test_passes_correct_form_id_for_each_type(self, mock_filter, mock_client_cls): + mock_client = MagicMock() + mock_client.get_edited_updated_submissions.return_value = [] + mock_client_cls.return_value = mock_client + + from moderation.utils.update_csdb import _FORM_SYNC_CONFIG + from moderation.utils.form_mapping import corestack + + for resource_type, (form_key, _) in _FORM_SYNC_CONFIG.items(): + mock_client.get_edited_updated_submissions.reset_mock() + sync_form_type(resource_type) + call_kwargs = mock_client.get_edited_updated_submissions.call_args[1] + self.assertEqual( + call_kwargs["form_id"], + corestack[form_key], + msg=f"Wrong form_id for resource_type='{resource_type}'", + ) + + @patch("moderation.utils.update_csdb.get_edited_updated_all_submissions") + @patch("moderation.utils.update_csdb.get_dynamic_filter_query", return_value="$filter=...") + def test_returns_false_and_does_not_raise_on_odk_error(self, mock_filter, mock_client_cls): + mock_client_cls.side_effect = Exception("ODK connection refused") + + result = sync_form_type("settlement") + + self.assertFalse(result) + + @patch("moderation.utils.update_csdb.get_edited_updated_all_submissions") + @patch("moderation.utils.update_csdb.get_dynamic_filter_query", return_value="$filter=...") + def test_does_not_update_sync_metadata(self, mock_filter, mock_client_cls): + mock_client = MagicMock() + mock_client.get_edited_updated_submissions.return_value = [] + mock_client_cls.return_value = mock_client + + with patch("moderation.models.SyncMetadata.objects") as mock_meta: + sync_form_type("settlement") + mock_meta.update.assert_not_called() + mock_meta.filter.assert_not_called() diff --git a/moderation/utils/update_csdb.py b/moderation/utils/update_csdb.py index eea910e9..d161847a 100644 --- a/moderation/utils/update_csdb.py +++ b/moderation/utils/update_csdb.py @@ -1,14 +1,20 @@ +import logging +from datetime import timezone as dt_tz + import requests -from nrm_app.settings import ODK_USERNAME, ODK_PASSWORD -from .utils import * -from .get_submissions import get_edited_updated_all_submissions -from .form_mapping import corestack -from utilities.constants import ODK_BASE_URL, project_id + from moderation.models import SyncMetadata +from nrm_app.settings import ODK_PASSWORD, ODK_USERNAME +from utilities.constants import ODK_BASE_URL, project_id + +from .form_mapping import corestack +from .get_submissions import get_edited_updated_all_submissions +from .utils import * + +_sync_logger = logging.getLogger(__name__) def get_dynamic_filter_query(): - from datetime import timezone as dt_tz metadata = SyncMetadata.get_odk_sync_metadata() filter_date = metadata.get_filter_date() @@ -239,6 +245,71 @@ def resync_agrohorticulture(agrohorticulture_submissions): print(f"{count} agrohorticulture submissions synced") +_FORM_SYNC_CONFIG = { + # Resources + "settlement": ("Settlement Form", resync_settlement), + "well": ("Well Form", resync_well), + "waterbody": ("water body form", resync_waterbody), + "cropping": ("cropping pattern form", resync_cropping), + # New works + "plan_gw": ("new recharge structure form", resync_gw), + "plan_agri": ("new irrigation form", resync_agri), + "livelihood": ("livelihood form", resync_livelihood), + # Maintenance works + "main_swb": ("propose maintenance on water structure form", resync_swb_maintenance), + "main_gw": ("propose maintenance on existing water recharge form", resync_gw_maintenance), + "main_swb_rs": ("propose maintenance of remotely sensed water structure form", resync_swb_rs_maintenance), + "main_agri": ("propose maintenance on existing irrigation form", resync_agri_maintenance), + # Other works + "agrohorticulture": ("Agrohorticulture", resync_agrohorticulture), +} + + +def sync_form_type(resource_type: str) -> bool: + """ + Incremental sync for a single ODK form type to DB, using the same + last_synced_at cursor as the full sync. Does not advance the cursor — + that is reserved for the full resync_db_odk() run. + """ + config = _FORM_SYNC_CONFIG.get(resource_type) + if not config: + _sync_logger.warning(f"sync_form_type: unknown resource_type '{resource_type}'") + return False + form_key, resync_fn = config + _sync_logger.info( + f"sync_form_type: starting incremental sync for resource_type='{resource_type}' " + f"form='{form_key}'" + ) + try: + filter_query = get_dynamic_filter_query() + _sync_logger.info(f"sync_form_type: using filter_query='{filter_query}'") + client = get_edited_updated_all_submissions( + username=ODK_USERNAME, + password=ODK_PASSWORD, + base_url=ODK_BASE_URL, + ) + submissions = client.get_edited_updated_submissions( + project_id=project_id, + form_id=corestack[form_key], + filter_query=filter_query, + ) + _sync_logger.info( + f"sync_form_type: fetched {len(submissions)} submission(s) from ODK " + f"for resource_type='{resource_type}'" + ) + resync_fn(submissions) + _sync_logger.info( + f"sync_form_type: completed sync for resource_type='{resource_type}'" + ) + return True + except Exception as e: + _sync_logger.error( + f"sync_form_type: failed for resource_type='{resource_type}': {e}", + exc_info=True, + ) + return False + + def resync_db_odk(): ( settlement_submissions, diff --git a/moderation/utils/utils.py b/moderation/utils/utils.py index aad397d6..92c60469 100644 --- a/moderation/utils/utils.py +++ b/moderation/utils/utils.py @@ -345,6 +345,8 @@ def sync_edited_updated_cropping_pattern(cp_submission): return system = cp_submission.get("__system", {}) + gps = cp_submission.get("GPS_point") or {} + lat, lon = extract_lat_lon_from_gps(gps) def get_crop_pattern(field_name, other_field_name): crops = cp_submission.get(field_name, "") @@ -390,7 +392,10 @@ def get_crop_pattern(field_name, other_field_name): "plan_id": cp_submission.get("plan_id") or "NA", "plan_name": to_utf8(cp_submission.get("plan_name") or "NA"), "status_re": system.get("reviewState") or "in progress", + "latitude": lat, + "longitude": lon, "system": system, + "gps_point": gps, "data_crop": cp_submission, } @@ -651,6 +656,9 @@ def _crop_pattern(field, other_field): return f"{crops}: {other}" return crops or "NA" + gps = data.get("GPS_point") or {} + lat, lon = extract_lat_lon_from_gps(gps) + return { "beneficiary_settlement": to_utf8(data.get("beneficiary_settlement") or "NA"), "irrigation_source": to_utf8(data.get("select_multiple_widgets") or "NA"), @@ -672,6 +680,9 @@ def _crop_pattern(field, other_field): ) ), "agri_productivity": to_utf8(data.get("select_one_productivity") or "NA"), + "latitude": lat, + "longitude": lon, + "gps_point": gps, } diff --git a/plans/api.py b/plans/api.py index 07ab1b72..533a26f3 100644 --- a/plans/api.py +++ b/plans/api.py @@ -1,510 +1,758 @@ -import os -from typing import Any, Dict, Optional - -import requests -from django.views.decorators.csrf import csrf_exempt -from rest_framework import status -from rest_framework.decorators import api_view, schema -from rest_framework.response import Response - -from nrm_app.settings import ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC, TMP_LOCATION -from utilities.auth_check_decorator import api_security_check -from utilities.auth_utils import auth_free -from utilities.constants import ( - ODK_SYNC_URL_AGRI_FEEDBACK, - ODK_SYNC_URL_AGRI_MAINTENANCE, - ODK_SYNC_URL_CROP, - ODK_SYNC_URL_GW_FEEDBACK, - ODK_SYNC_URL_GW_MAINTENANCE, - ODK_SYNC_URL_IRRIGATION_STRUCTURE, - ODK_SYNC_URL_LIVELIHOOD, - ODK_SYNC_URL_AGROHORTICULTURE, - ODK_SYNC_URL_RECHARGE_STRUCTURE, - ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, - ODK_SYNC_URL_SETTLEMENT, - ODK_SYNC_URL_SWB_FEEDBACK, - ODK_SYNC_URL_WATER_STRUCTURES, - ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, - ODK_SYNC_URL_WELL, -) - -from .build_layer import build_layer -from .models import ODKSyncLog, Plan, PlanApp -from .serializers import PlanAppSerializer -from .utils import fetch_bearer_token, fetch_odk_data -from geoadmin.models import GramPanchayat - - -# MARK: Get Plans API -@api_security_check(auth_type="Auth_free") -@schema(None) -def get_plans(request): - """ - Get Plans API - - Args: - block_id (str, optional): Block ID. Defaults to None. - - Returns: - Response: JSON response containing a list of plans of a block or all the plans - """ - try: - block_id = request.query_params.get("block_id", None) - if block_id is not None: - plans = Plan.objects.filter(block=block_id) - else: - plans = Plan.objects.all() - serializer = PlanAppSerializer(plans, many=True) - response = {"plans": serializer.data} - - return Response(response, status=status.HTTP_200_OK) - except Exception as e: - print("Exception in get_plans api :: ", e) - return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@api_view(["POST"]) -@auth_free -@schema(None) -def add_plan(request): - if request.method == "POST": - serializer = PlanAppSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() # Save the new Plan instance if validation passes - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {"error": "Method not allowed"}, status=status.HTTP_405_METHOD_NOT_ALLOWED - ) - - -# api's for add settlement, add well, add waterbody | add work [new, maintenance] -@api_view(["POST"]) -@auth_free -@schema(None) -def add_resources(request): - layer_name = request.data.get("layer_name").lower() - resource_type = request.data.get("resource_type").lower() - plan_id = request.data.get("plan_id") - plan_name = request.data.get("plan_name").lower() - district = request.data.get("district_name").lower() - block = request.data.get("block_name").lower() - - CSV_PATH = os.path.join( - TMP_LOCATION, - f"{resource_type}_{plan_id}_{block}.csv", - ) - - odk_data_found = fetch_odk_data(CSV_PATH, resource_type, block, plan_id) - - if not odk_data_found: - return Response( - {"error": f"No ODK data found for the given Plan ID: {plan_id}"}, - status=status.HTTP_404_NOT_FOUND, - ) - - try: - success = build_layer( - layer_type="resources", - item_type=resource_type, - plan_id=plan_id, - district=district, - block=block, - csv_path=CSV_PATH, - ) - if not success: - return Response( - {"error": "Failed to build resource layer."}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - except Exception as e: - return Response( - {"error": f"An unexpected error occurred: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - finally: - if os.path.exists(CSV_PATH): - os.remove(CSV_PATH) - - return Response({"message": "Success"}, status=status.HTTP_201_CREATED) - - -@api_view(["POST"]) -@auth_free -@schema(None) -def add_works(request): - """ - work type: plan_gw: recharge st., main_swb: maintenance surface water bodies, plan_agri: irrigation works, livelihood - works: work_type_plan_id_district_block - """ - layer_name = request.data.get("layer_name").lower() - work_type = request.data.get("work_type").lower() - plan_id = request.data.get("plan_id") - plan_name = request.data.get("plan_name").lower() - district = request.data.get("district_name").lower() - block = request.data.get("block_name").lower() - - CSV_PATH = os.path.join( - TMP_LOCATION, - f"{work_type}_{plan_id}_{block}.csv", - ) - - odk_data_found = fetch_odk_data(CSV_PATH, work_type, block, plan_id) - - if not odk_data_found: - return Response( - {"error": f"No ODK data found for the given Plan ID: {plan_id}"}, - status=status.HTTP_404_NOT_FOUND, - ) - - try: - success = build_layer( - layer_type="works", - item_type=work_type, - plan_id=plan_id, - district=district, - block=block, - csv_path=CSV_PATH, - ) - if not success: - return Response( - {"error": "Failed to build work layer."}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - except Exception as e: - return Response( - {"error": f"An unexpected error occurred: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - finally: - if os.path.exists(CSV_PATH): - os.remove(CSV_PATH) - - return Response({"message": "Success"}, status=status.HTTP_201_CREATED) - - -# MARK: SYNC OFFLINE DATA HELPER FUNCTIONS -def _get_resource_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping for different resource types.""" - return { - "settlement": { - "url": ODK_SYNC_URL_SETTLEMENT, - "success_message": "Settlement data synced successfully", - }, - "well": { - "url": ODK_SYNC_URL_WELL, - "success_message": "Well data synced successfully", - }, - "water_structures": { - "url": ODK_SYNC_URL_WATER_STRUCTURES, - "success_message": "Water structures data synced successfully", - }, - "cropping_pattern": { - "url": ODK_SYNC_URL_CROP, - "success_message": "Cropping pattern data synced successfully", - }, - } - - -def _get_work_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping for different work types.""" - return { - "recharge_st": { - "url": ODK_SYNC_URL_RECHARGE_STRUCTURE, - "success_message": "Recharge structure data synced successfully", - }, - "irrigation_st": { - "url": ODK_SYNC_URL_IRRIGATION_STRUCTURE, - "success_message": "Irrigation structure data synced successfully", - }, - "propose_maintenance_recharge_st": { - "url": ODK_SYNC_URL_GW_MAINTENANCE, - "success_message": "Recharge structure maintenance data synced successfully", - }, - "propose_maintenance_rs_swb": { - "url": ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, - "success_message": "Surface water body maintenance data synced successfully", - }, - "propose_maintenance_ws_swb": { - "url": ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, - "success_message": "Water structures maintenance data synced successfully", - }, - "propose_maintenance_irrigation_st": { - "url": ODK_SYNC_URL_AGRI_MAINTENANCE, - "success_message": "Irrigation structure maintenance data synced successfully", - }, - "livelihood": { - "url": ODK_SYNC_URL_LIVELIHOOD, - "success_message": "Livelihood data synced successfully", - }, - "agrohorticulture": { - "url": ODK_SYNC_URL_AGROHORTICULTURE, - "success_message": "Agrohorticulture data synced successfully", - }, - } - - -def _get_feedback_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping of different feedback types""" - return { - "gw_feedback": { - "url": ODK_SYNC_URL_GW_FEEDBACK, - "success_message": "Groundwater feedback data synced successfully", - }, - "swb_feedback": { - "url": ODK_SYNC_URL_SWB_FEEDBACK, - "success_message": "Surface water body feedback data synced successfully", - }, - "agri_feedback": { - "url": ODK_SYNC_URL_AGRI_FEEDBACK, - "success_message": "Agriculture feedback data synced successfully", - }, - } - - -def _validate_sync_request( - request, resource_type: str = None, work_type: str = None, feedback_type: str = None -) -> Optional[Response]: - """Validate the sync request parameters and content type.""" - - if not resource_type and not work_type and not feedback_type: - return Response( - { - "error": "Must specify either resource_type or work_type or feedback_type" - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - if resource_type: - valid_resources = ["settlement", "well", "water_structures", "cropping_pattern"] - if resource_type not in valid_resources: - return Response( - {"error": f"Invalid resource type. Must be one of {valid_resources}"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if work_type: - valid_work_types = [ - "recharge_st", - "irrigation_st", - "propose_maintenance_recharge_st", - "propose_maintenance_rs_swb", - "propose_maintenance_ws_swb", - "propose_maintenance_irrigation_st", - "livelihood", - "agrohorticulture", - ] - if work_type not in valid_work_types: - return Response( - {"error": f"Invalid work type. Must be one of {valid_work_types}"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if feedback_type: - valid_feedback_types = ["gw_feedback", "swb_feedback", "agri_feedback"] - if feedback_type not in valid_feedback_types: - return Response( - { - "error": f"Invalid feedback type. Must be one of {valid_feedback_types}" - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - if request.content_type != "application/xml": - return Response( - {"error": "Content-Type must be application/xml"}, - status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, - ) - - return None - - -def _sync_to_odk( - xml_string: str, - config: Dict[str, Any], - bearer_token: str, - category: str, - sync_type: str, -) -> Response: - """Handle the actual sync to ODK for a specific resource or work type.""" - sync_log = ODKSyncLog.objects.create( - category=category, - sync_type=sync_type, - xml_content=xml_string, - odk_url=config["url"], - status=ODKSyncLog.SyncStatus.PENDING, - ) - - try: - response = requests.post( - config["url"], - headers={ - "Content-Type": "application/xml", - "Authorization": f"Bearer {bearer_token}", - }, - data=xml_string, - ) - response.raise_for_status() - - odk_response = response.json() if response.content else None - sync_log.status = ODKSyncLog.SyncStatus.SUCCESS - sync_log.odk_response = odk_response - sync_log.save(update_fields=["status", "odk_response"]) - - return Response( - { - "sync_status": True, - "message": config["success_message"], - "odk_response": odk_response, - }, - status=status.HTTP_201_CREATED, - ) - - except requests.exceptions.RequestException as e: - item_name = config["success_message"].split()[0].lower() - print(f"Error syncing {item_name} data to ODK: {str(e)}") - - sync_log.status = ODKSyncLog.SyncStatus.FAILED - sync_log.error_details = str(e) - sync_log.save(update_fields=["status", "error_details"]) - - return Response( - { - "sync_status": False, - "error": f"Failed to sync {item_name} data to ODK", - "details": str(e), - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - -# MARK: SYNC OFFLINE DATA -# API to sync offline data coming from CC app -@api_view(["POST"]) -@csrf_exempt -@auth_free -@schema(None) -def sync_offline_data(request, resource_type=None, work_type=None, feedback_type=None): - """ - Sync data to ODK based on resource type or work type - Resource types: settlement, well, water_structures, cropping_pattern - Work types: "recharge_st", "irrigation_st", "propose_maintenance_recharge_st", "propose_maintenance_rs_swb", - "propose_maintenance_ws_swb", "propose_maintenance_irrigation_st", "livelihood", - Feedback types: "gw_feedback", "swb_feedback", "agri_feedback" - - fetch Bearer Token from ODK - - send xmlString to ODK - """ - print( - f"Inside sync_offline_data API for resource type: {resource_type}, work type: {work_type}, feedback type: {feedback_type}" - ) - - # Validate request - validation_error = _validate_sync_request( - request, resource_type, work_type, feedback_type - ) - if validation_error: - return validation_error - - if resource_type: - configs = _get_resource_config() - config = configs[resource_type] - category = ODKSyncLog.SyncCategory.RESOURCE - item_type = resource_type - elif work_type: - configs = _get_work_config() - config = configs[work_type] - category = ODKSyncLog.SyncCategory.WORK - item_type = work_type - elif feedback_type: - configs = _get_feedback_config() - config = configs[feedback_type] - category = ODKSyncLog.SyncCategory.FEEDBACK - item_type = feedback_type - else: - return Response( - {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST - ) - - xml_string = request.body.decode("utf-8") - print(f"Sync Category: {category}, Type: {item_type}") - - try: - bearer_token = fetch_bearer_token(ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC) - print("Bearer Token: ", bearer_token) - - return _sync_to_odk(xml_string, config, bearer_token, category, item_type) - - except Exception as e: - print("Exception in sync_offline_data api :: ", e) - return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -# map plan to gp api -@api_view(["PATCh"]) -@schema(None) -def map_plan_to_gp(request): - - plan_id = request.data.get("plan_id") - gp_id = request.data.get("gp_id") - - if not plan_id or not gp_id: - return Response( - { - "success": False, - "message": "plan_id and gp_id are required", - }, - status=400, - ) - - try: - plan = PlanApp.objects.get(id=plan_id) - - except PlanApp.DoesNotExist: - return Response( - { - "success": False, - "message": "Plan not found", - }, - status=404, - ) - - try: - gp = GramPanchayat.objects.get(gram_panchayat_code=gp_id) - - except GramPanchayat.DoesNotExist: - return Response( - { - "success": False, - "message": "Gram Panchayat not found", - }, - status=404, - ) - - # GP should belong to same tehsil - - if plan.tehsil_soi_id != gp.tehsil_id: - return Response( - { - "success": False, - "message": "Selected GP does not belong to plan tehsil", - }, - status=400, - ) - - plan.gp = gp - plan.updated_by = request.user - - plan.save(update_fields=["gp", "updated_by", "updated_at"]) - - return Response( - { - "success": True, - "message": "Plan mapped with GP successfully", - "data": { - "plan_id": plan.id, - "gp_id": gp.gram_panchayat_code, - "gp_name": gp.gram_panchayat_name, - }, - } - ) +import logging +import os +import time +import uuid +from typing import Any, Dict, List, Optional, Tuple + +import requests +from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.decorators import api_view, schema +from rest_framework.response import Response + +from dpr.utils import transform_name +from moderation.utils.update_csdb import sync_form_type +from nrm_app.settings import ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC, TMP_LOCATION +from utilities.auth_check_decorator import api_security_check +from utilities.auth_utils import auth_free +from utilities.constants import ( + ODK_SYNC_URL_AGRI_FEEDBACK, + ODK_SYNC_URL_AGRI_MAINTENANCE, + ODK_SYNC_URL_AGROHORTICULTURE, + ODK_SYNC_URL_CROP, + ODK_SYNC_URL_GW_FEEDBACK, + ODK_SYNC_URL_GW_MAINTENANCE, + ODK_SYNC_URL_IRRIGATION_STRUCTURE, + ODK_SYNC_URL_LIVELIHOOD, + ODK_SYNC_URL_RECHARGE_STRUCTURE, + ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, + ODK_SYNC_URL_SETTLEMENT, + ODK_SYNC_URL_SWB_FEEDBACK, + ODK_SYNC_URL_WATER_STRUCTURES, + ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, + ODK_SYNC_URL_WELL, +) + +logger = logging.getLogger(__name__) + +from .build_layer import build_layer +from .models import ODKSyncLog, Plan +from .serializers import PlanAppSerializer +from .utils import fetch_bearer_token, fetch_db_data + +_COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( + "layer_name", + "plan_id", + "plan_name", + "district_name", + "block_name", +) + +_LAYER_KIND_CONFIG: Dict[str, Dict[str, str]] = { + "resources": {"type_field": "resource_type", "singular": "resource"}, + "works": {"type_field": "work_type", "singular": "work"}, +} + + +# MARK: Get Plans API +@api_security_check(auth_type="Auth_free") +@schema(None) +def get_plans(request): + """ + Get Plans API + + Args: + block_id (str, optional): Block ID. Defaults to None. + + Returns: + Response: JSON response containing a list of plans of a block or all the plans + """ + try: + block_id = request.query_params.get("block_id", None) + if block_id is not None: + plans = Plan.objects.filter(block=block_id) + else: + plans = Plan.objects.all() + serializer = PlanAppSerializer(plans, many=True) + response = {"plans": serializer.data} + + return Response(response, status=status.HTTP_200_OK) + except Exception as e: + print("Exception in get_plans api :: ", e) + return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +@api_view(["POST"]) +@auth_free +@schema(None) +def add_plan(request): + if request.method == "POST": + serializer = PlanAppSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() # Save the new Plan instance if validation passes + return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Method not allowed"}, status=status.HTTP_405_METHOD_NOT_ALLOWED + ) + + +# MARK: Build Layer Helpers (shared by /add_resources and /add_works) +def _extract_payload(request, kind: str) -> Tuple[Optional[Dict[str, Any]], List[str]]: + """Pull and normalize the request payload. Returns (payload, missing_fields).""" + type_field = _LAYER_KIND_CONFIG[kind]["type_field"] + required = (*_COMMON_REQUIRED_FIELDS, type_field) + + missing = [f for f in required if not request.data.get(f)] + if missing: + return None, missing + + def _lower(value: Any) -> Any: + return value.lower() if isinstance(value, str) else value + + return { + "layer_name": _lower(request.data.get("layer_name")), + "item_type": _lower(request.data.get(type_field)), + "plan_id": request.data.get("plan_id"), + "plan_name": _lower(request.data.get("plan_name")), + "district": _lower(request.data.get("district_name")), + "block": _lower(request.data.get("block_name")), + }, [] + + +def _expected_layer_store_name( + item_type: str, plan_id: Any, district: str, block: str +) -> str: + """Mirror the naming convention used by build_layer.build_layer for transparency.""" + return f"{item_type}_{plan_id}_{district}_{transform_name(name=block)}" + + +def _safe_unlink(csv_path: str, request_id: str, kind: str) -> None: + try: + if os.path.exists(csv_path): + os.remove(csv_path) + logger.info( + f"[{request_id}] {kind}.build: cleaned up temp CSV at {csv_path}" + ) + except OSError as exc: + logger.warning( + f"[{request_id}] {kind}.build: failed to remove temp CSV at " + f"{csv_path}: {exc}" + ) + + +def _error_response( + request_id: str, + code: str, + message: str, + http_status: int, + extra: Optional[Dict[str, Any]] = None, +) -> Response: + data: Dict[str, Any] = {"request_id": request_id} + if extra: + data.update(extra) + return Response( + { + "status": "error", + "code": code, + "error": message, + "data": data, + }, + status=http_status, + ) + + +def _build_layer_for_kind(request, kind: str) -> Response: + """ + Shared workflow for /add_resources and /add_works: + 1. validate payload + 2. trigger incremental ODK -> DB sync (best-effort) + 3. fetch source records from DB and stage a CSV + 4. publish the layer to GeoServer + 5. clean up the temp CSV and return a structured response + """ + request_id = uuid.uuid4().hex[:12] + type_field = _LAYER_KIND_CONFIG[kind]["type_field"] + singular = _LAYER_KIND_CONFIG[kind]["singular"] + started_at = time.perf_counter() + + logger.info( + f"[{request_id}] {kind}.build: request received " + f"(content_type={request.content_type}, keys={list(request.data.keys())})" + ) + + payload, missing = _extract_payload(request, kind) + if missing: + logger.warning( + f"[{request_id}] {kind}.build: rejecting request — " + f"missing/empty fields: {missing}" + ) + return _error_response( + request_id, + code="missing_fields", + message=f"Missing required field(s): {', '.join(missing)}.", + http_status=status.HTTP_400_BAD_REQUEST, + extra={"missing_fields": missing}, + ) + + item_type = payload["item_type"] + plan_id = payload["plan_id"] + plan_name = payload["plan_name"] + district = payload["district"] + block = payload["block"] + layer_name = payload["layer_name"] + + context = { + type_field: item_type, + "plan_id": plan_id, + "plan_name": plan_name, + "district": district, + "block": block, + "layer_name": layer_name, + } + logger.info(f"[{request_id}] {kind}.build: payload normalized — {context}") + + csv_path = os.path.join(TMP_LOCATION, f"{item_type}_{plan_id}_{block}.csv") + logger.info(f"[{request_id}] {kind}.build: temp CSV path resolved to {csv_path}") + + sync_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: triggering incremental ODK sync for " + f"{type_field}={item_type}" + ) + sync_ok = sync_form_type(item_type) + sync_ms = int((time.perf_counter() - sync_started) * 1000) + if sync_ok: + logger.info( + f"[{request_id}] {kind}.build: ODK sync completed for " + f"{type_field}={item_type} in {sync_ms}ms" + ) + else: + logger.warning( + f"[{request_id}] {kind}.build: ODK sync FAILED for " + f"{type_field}={item_type} in {sync_ms}ms; proceeding with existing DB data" + ) + + fetch_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: fetching DB data for {type_field}={item_type}, " + f"plan_id={plan_id}, block={block}" + ) + try: + record_count = fetch_db_data(csv_path, item_type, block, plan_id) + except Exception as exc: + fetch_ms = int((time.perf_counter() - fetch_started) * 1000) + logger.exception( + f"[{request_id}] {kind}.build: unexpected error during fetch_db_data " + f"for {type_field}={item_type}, plan_id={plan_id} " + f"(fetch_ms={fetch_ms}): {exc}" + ) + _safe_unlink(csv_path, request_id, kind) + return _error_response( + request_id, + code="db_fetch_failed", + message="Failed to fetch source data from the database.", + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "details": str(exc), + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + }, + ) + fetch_ms = int((time.perf_counter() - fetch_started) * 1000) + + if not record_count: + total_ms = int((time.perf_counter() - started_at) * 1000) + logger.warning( + f"[{request_id}] {kind}.build: no DB data found for " + f"{type_field}={item_type}, plan_id={plan_id}, block={block} " + f"(sync_ok={sync_ok}, fetch_ms={fetch_ms}, total_ms={total_ms})" + ) + return _error_response( + request_id, + code="no_data_found", + message=( + f"No records found for {type_field}='{item_type}', " + f"plan_id='{plan_id}', block='{block}'." + ), + http_status=status.HTTP_404_NOT_FOUND, + extra={ + **context, + "record_count": 0, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "total_duration_ms": total_ms, + }, + ) + logger.info( + f"[{request_id}] {kind}.build: DB fetch staged {record_count} row(s) " + f"in {fetch_ms}ms" + ) + + layer_store_name = _expected_layer_store_name(item_type, plan_id, district, block) + build_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: publishing GeoServer layer " + f"workspace='{kind}', store='{layer_store_name}'" + ) + try: + success = build_layer( + layer_type=kind, + item_type=item_type, + plan_id=plan_id, + district=district, + block=block, + csv_path=csv_path, + ) + except Exception as exc: + build_ms = int((time.perf_counter() - build_started) * 1000) + total_ms = int((time.perf_counter() - started_at) * 1000) + logger.exception( + f"[{request_id}] {kind}.build: unexpected error during build_layer for " + f"{type_field}={item_type}, plan_id={plan_id} " + f"(build_ms={build_ms}, total_ms={total_ms}): {exc}" + ) + _safe_unlink(csv_path, request_id, kind) + return _error_response( + request_id, + code="internal_error", + message="An unexpected error occurred while building the layer.", + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "layer_store_name": layer_store_name, + "details": str(exc), + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + ) + finally: + _safe_unlink(csv_path, request_id, kind) + + build_ms = int((time.perf_counter() - build_started) * 1000) + total_ms = int((time.perf_counter() - started_at) * 1000) + + if not success: + logger.error( + f"[{request_id}] {kind}.build: build_layer returned False for " + f"{type_field}={item_type}, plan_id={plan_id} " + f"(build_ms={build_ms}, total_ms={total_ms})" + ) + return _error_response( + request_id, + code="layer_build_failed", + message=( + f"Failed to publish GeoServer layer '{layer_store_name}'. " + "See server logs for details." + ), + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "layer_store_name": layer_store_name, + "record_count": record_count, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + ) + + logger.info( + f"[{request_id}] {kind}.build: SUCCESS — published layer " + f"'{layer_store_name}' ({record_count} row(s)) in workspace='{kind}' " + f"(sync={sync_ms}ms, fetch={fetch_ms}ms, build={build_ms}ms, total={total_ms}ms)" + ) + return Response( + { + "status": "success", + "code": "layer_published", + "message": ( + f"Successfully published {singular} layer " + f"'{layer_store_name}' to GeoServer with {record_count} record(s)." + ), + "data": { + "request_id": request_id, + "layer_type": kind, + "workspace": kind, + "layer_store_name": layer_store_name, + "record_count": record_count, + **context, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + }, + status=status.HTTP_201_CREATED, + ) + + +# api's for add settlement, add well, add waterbody | add work [new, maintenance] +@api_view(["POST"]) +@auth_free +@schema(None) +def add_resources(request): + """ + Build and publish a GeoServer 'resources' layer for the given plan/block. + + Supported resource_type values: settlement, well, waterbody, cropping. + Layer naming convention: ___. + """ + return _build_layer_for_kind(request, kind="resources") + + +@api_view(["POST"]) +@auth_free +@schema(None) +def add_works(request): + """ + Build and publish a GeoServer 'works' layer for the given plan/block. + + Supported work_type values: + plan_gw — new recharge structures (groundwater) + main_gw — maintenance of recharge structures + plan_agri — new irrigation structures + main_agri — maintenance of irrigation structures + main_swb — surface water body maintenance + main_swb_rs — remote-sensed surface water body maintenance + livelihood — livelihood + agrohorticulture — agrohorticulture + + Layer naming convention: ___. + """ + return _build_layer_for_kind(request, kind="works") + + +# MARK: SYNC OFFLINE DATA HELPER FUNCTIONS +def _get_resource_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping for different resource types.""" + return { + "settlement": { + "url": ODK_SYNC_URL_SETTLEMENT, + "success_message": "Settlement data synced successfully", + }, + "well": { + "url": ODK_SYNC_URL_WELL, + "success_message": "Well data synced successfully", + }, + "water_structures": { + "url": ODK_SYNC_URL_WATER_STRUCTURES, + "success_message": "Water structures data synced successfully", + }, + "cropping_pattern": { + "url": ODK_SYNC_URL_CROP, + "success_message": "Cropping pattern data synced successfully", + }, + } + + +def _get_work_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping for different work types.""" + return { + "recharge_st": { + "url": ODK_SYNC_URL_RECHARGE_STRUCTURE, + "success_message": "Recharge structure data synced successfully", + }, + "irrigation_st": { + "url": ODK_SYNC_URL_IRRIGATION_STRUCTURE, + "success_message": "Irrigation structure data synced successfully", + }, + "propose_maintenance_recharge_st": { + "url": ODK_SYNC_URL_GW_MAINTENANCE, + "success_message": "Recharge structure maintenance data synced successfully", + }, + "propose_maintenance_rs_swb": { + "url": ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, + "success_message": "Surface water body maintenance data synced successfully", + }, + "propose_maintenance_ws_swb": { + "url": ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, + "success_message": "Water structures maintenance data synced successfully", + }, + "propose_maintenance_irrigation_st": { + "url": ODK_SYNC_URL_AGRI_MAINTENANCE, + "success_message": "Irrigation structure maintenance data synced successfully", + }, + "livelihood": { + "url": ODK_SYNC_URL_LIVELIHOOD, + "success_message": "Livelihood data synced successfully", + }, + "agrohorticulture": { + "url": ODK_SYNC_URL_AGROHORTICULTURE, + "success_message": "Agrohorticulture data synced successfully", + }, + } + + +def _get_feedback_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping of different feedback types""" + return { + "gw_feedback": { + "url": ODK_SYNC_URL_GW_FEEDBACK, + "success_message": "Groundwater feedback data synced successfully", + }, + "swb_feedback": { + "url": ODK_SYNC_URL_SWB_FEEDBACK, + "success_message": "Surface water body feedback data synced successfully", + }, + "agri_feedback": { + "url": ODK_SYNC_URL_AGRI_FEEDBACK, + "success_message": "Agriculture feedback data synced successfully", + }, + } + + +def _validate_sync_request( + request, resource_type: str = None, work_type: str = None, feedback_type: str = None +) -> Optional[Response]: + """Validate the sync request parameters and content type.""" + + if not resource_type and not work_type and not feedback_type: + return Response( + { + "error": "Must specify either resource_type or work_type or feedback_type" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + if resource_type: + valid_resources = ["settlement", "well", "water_structures", "cropping_pattern"] + if resource_type not in valid_resources: + return Response( + {"error": f"Invalid resource type. Must be one of {valid_resources}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if work_type: + valid_work_types = [ + "recharge_st", + "irrigation_st", + "propose_maintenance_recharge_st", + "propose_maintenance_rs_swb", + "propose_maintenance_ws_swb", + "propose_maintenance_irrigation_st", + "livelihood", + "agrohorticulture", + ] + if work_type not in valid_work_types: + return Response( + {"error": f"Invalid work type. Must be one of {valid_work_types}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if feedback_type: + valid_feedback_types = ["gw_feedback", "swb_feedback", "agri_feedback"] + if feedback_type not in valid_feedback_types: + return Response( + { + "error": f"Invalid feedback type. Must be one of {valid_feedback_types}" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + if request.content_type != "application/xml": + return Response( + {"error": "Content-Type must be application/xml"}, + status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, + ) + + return None + + +def _sync_to_odk( + xml_string: str, + config: Dict[str, Any], + bearer_token: str, + category: str, + sync_type: str, +) -> Response: + """Handle the actual sync to ODK for a specific resource or work type.""" + sync_log = ODKSyncLog.objects.create( + category=category, + sync_type=sync_type, + xml_content=xml_string, + odk_url=config["url"], + status=ODKSyncLog.SyncStatus.PENDING, + ) + + try: + response = requests.post( + config["url"], + headers={ + "Content-Type": "application/xml", + "Authorization": f"Bearer {bearer_token}", + }, + data=xml_string, + ) + response.raise_for_status() + + odk_response = response.json() if response.content else None + sync_log.status = ODKSyncLog.SyncStatus.SUCCESS + sync_log.odk_response = odk_response + sync_log.save(update_fields=["status", "odk_response"]) + + return Response( + { + "sync_status": True, + "message": config["success_message"], + "odk_response": odk_response, + }, + status=status.HTTP_201_CREATED, + ) + + except requests.exceptions.RequestException as e: + item_name = config["success_message"].split()[0].lower() + print(f"Error syncing {item_name} data to ODK: {str(e)}") + + sync_log.status = ODKSyncLog.SyncStatus.FAILED + sync_log.error_details = str(e) + sync_log.save(update_fields=["status", "error_details"]) + + return Response( + { + "sync_status": False, + "error": f"Failed to sync {item_name} data to ODK", + "details": str(e), + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + +# MARK: SYNC OFFLINE DATA +# API to sync offline data coming from CC app +@api_view(["POST"]) +@csrf_exempt +@auth_free +@schema(None) +def sync_offline_data(request, resource_type=None, work_type=None, feedback_type=None): + """ + Sync data to ODK based on resource type or work type + Resource types: settlement, well, water_structures, cropping_pattern + Work types: "recharge_st", "irrigation_st", "propose_maintenance_recharge_st", "propose_maintenance_rs_swb", + "propose_maintenance_ws_swb", "propose_maintenance_irrigation_st", "livelihood", + Feedback types: "gw_feedback", "swb_feedback", "agri_feedback" + - fetch Bearer Token from ODK + - send xmlString to ODK + """ + print( + f"Inside sync_offline_data API for resource type: {resource_type}, work type: {work_type}, feedback type: {feedback_type}" + ) + + # Validate request + validation_error = _validate_sync_request( + request, resource_type, work_type, feedback_type + ) + if validation_error: + return validation_error + + if resource_type: + configs = _get_resource_config() + config = configs[resource_type] + category = ODKSyncLog.SyncCategory.RESOURCE + item_type = resource_type + elif work_type: + configs = _get_work_config() + config = configs[work_type] + category = ODKSyncLog.SyncCategory.WORK + item_type = work_type + elif feedback_type: + configs = _get_feedback_config() + config = configs[feedback_type] + category = ODKSyncLog.SyncCategory.FEEDBACK + item_type = feedback_type + else: + return Response( + {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + ) + + xml_string = request.body.decode("utf-8") + print(f"Sync Category: {category}, Type: {item_type}") + + try: + bearer_token = fetch_bearer_token(ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC) + print("Bearer Token: ", bearer_token) + + return _sync_to_odk(xml_string, config, bearer_token, category, item_type) + + except Exception as e: + print("Exception in sync_offline_data api :: ", e) + return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +# map plan to gp api +@api_view(["PATCh"]) +@schema(None) +def map_plan_to_gp(request): + + plan_id = request.data.get("plan_id") + gp_id = request.data.get("gp_id") + + if not plan_id or not gp_id: + return Response( + { + "success": False, + "message": "plan_id and gp_id are required", + }, + status=400, + ) + + try: + plan = PlanApp.objects.get(id=plan_id) + + except PlanApp.DoesNotExist: + return Response( + { + "success": False, + "message": "Plan not found", + }, + status=404, + ) + + try: + gp = GramPanchayat.objects.get(gram_panchayat_code=gp_id) + + except GramPanchayat.DoesNotExist: + return Response( + { + "success": False, + "message": "Gram Panchayat not found", + }, + status=404, + ) + + # GP should belong to same tehsil + + if plan.tehsil_soi_id != gp.tehsil_id: + return Response( + { + "success": False, + "message": "Selected GP does not belong to plan tehsil", + }, + status=400, + ) + + plan.gp = gp + plan.updated_by = request.user + + plan.save(update_fields=["gp", "updated_by", "updated_at"]) + + return Response( + { + "success": True, + "message": "Plan mapped with GP successfully", + "data": { + "plan_id": plan.id, + "gp_id": gp.gram_panchayat_code, + "gp_name": gp.gram_panchayat_name, + }, + } + ) diff --git a/plans/build_layer.py b/plans/build_layer.py index 7937c72c..12e7d54c 100644 --- a/plans/build_layer.py +++ b/plans/build_layer.py @@ -1,18 +1,32 @@ +import os +import tempfile +import traceback +from io import BytesIO + import geopandas as gpd import pandas as pd -from shapely.geometry import Point -from geopandas import GeoDataFrame -from io import BytesIO -import zipfile import requests -import os -import sys -from typing import Optional -import tempfile +from shapely.geometry import Point + from dpr.utils import transform_name +from nrm_app.settings import GEOSERVER_PASSWORD, GEOSERVER_URL, GEOSERVER_USERNAME from utilities.logger import logger -import traceback -from nrm_app.settings import GEOSERVER_URL, GEOSERVER_PASSWORD, GEOSERVER_USERNAME + + +# GeoPackage is intentionally preferred over ESRI Shapefile: +# - Shapefile DBF truncates field names to 10 chars, capping fields at ~255 +# and causing collisions across the merged ODK-blob + moderation columns. +# - GeoPackage has effectively no limit on field names or count, native +# UTF-8, richer types, and is a single file (no zip bundle needed). +_DEFAULT_FORMAT = "gpkg" +_DRIVER_BY_EXTENSION = { + "gpkg": "GPKG", + "shp": "ESRI Shapefile", +} +_CONTENT_TYPE_BY_EXTENSION = { + "gpkg": "application/x-sqlite3", + "shp": "application/zip", +} class Geoserver_BB: @@ -27,135 +41,242 @@ def __init__( self.password = password logger.debug(f"Initialized Geoserver_BB with URL: {service_url}") - def test_connection(self): + def test_connection(self) -> bool: try: test_url = f"{self.service_url}/rest/about/status" response = requests.get( - test_url, - auth=(self.username, self.password), - verify=True, # Change to False if using self-signed cert + test_url, auth=(self.username, self.password), verify=True ) logger.debug(f"Connection test status code: {response.status_code}") - return response.status_code in [200, 201] + return response.status_code in (200, 201) except Exception as e: logger.error(f"Connection test failed: {str(e)}") return False + def delete_datastore_if_exists(self, workspace: str, store_name: str) -> bool: + """ + Idempotently remove an existing datastore (with its layers) so a + subsequent upload registers a fresh datastore of the new type. + GeoServer's `file.?update=overwrite` only replaces the file + inside an existing datastore — it does NOT change the datastore's + type, so a stale shapefile store would keep pointing at a missing + `.shp` after we switch to GeoPackage. + """ + url = ( + f"{self.service_url}/rest/workspaces/{workspace}/datastores/" + f"{store_name}?recurse=true" + ) + try: + r = requests.delete( + url, auth=(self.username, self.password), verify=True + ) + if r.status_code in (200, 202): + logger.info( + f"Deleted existing datastore '{workspace}:{store_name}' " + f"(status={r.status_code})" + ) + return True + if r.status_code == 404: + logger.debug( + f"No existing datastore '{workspace}:{store_name}' to delete" + ) + return False + logger.warning( + f"Unexpected status {r.status_code} deleting datastore " + f"'{workspace}:{store_name}': {r.content}" + ) + return False + except Exception as e: + logger.warning( + f"Failed to delete datastore '{workspace}:{store_name}': {e}" + ) + return False + def create_datastore( self, - path: BytesIO, + data: bytes, store_name: str, workspace: str, - file_extension: str = "shp", - ): + file_extension: str = _DEFAULT_FORMAT, + ) -> str: try: - headers = { - "Content-type": "application/zip", - "Accept": "application/xml", - } - - url = f"{self.service_url}/rest/workspaces/{workspace}/datastores/{store_name}/file.{file_extension}?filename={store_name}&update=overwrite" - logger.debug(f"Attempting to create datastore at URL: {url}") + content_type = _CONTENT_TYPE_BY_EXTENSION.get( + file_extension, "application/octet-stream" + ) + headers = {"Content-type": content_type, "Accept": "application/xml"} + url = ( + f"{self.service_url}/rest/workspaces/{workspace}/datastores/" + f"{store_name}/file.{file_extension}" + f"?filename={store_name}&update=overwrite" + ) + logger.debug( + f"Uploading datastore: store='{store_name}', workspace='{workspace}', " + f"extension='{file_extension}', size={len(data)} bytes, " + f"content_type='{content_type}'" + ) if not self.test_connection(): raise Exception("Failed to connect to GeoServer") r = requests.put( url, - data=path.getvalue(), + data=data, auth=(self.username, self.password), headers=headers, - verify=True, # Change to False if using self-signed cert + verify=True, ) - logger.debug(f"Create datastore response: {r.status_code}") logger.debug(f"Response content: {r.content}") - if r.status_code in [200, 201, 202]: - return "The shapefile datastore created successfully!" - else: - raise Exception(f"GeoServer Error: {r.status_code}, {r.content}") + if r.status_code in (200, 201, 202): + return ( + f"GeoServer datastore '{store_name}' created/updated " + f"(extension={file_extension})" + ) + raise Exception(f"GeoServer Error: {r.status_code}, {r.content}") except Exception as e: logger.error(f"Error in create_datastore: {str(e)}") raise -def build_layer(layer_type, item_type, plan_id, district, block, csv_path): +def build_layer( + layer_type: str, + item_type: str, + plan_id, + district: str, + block: str, + csv_path: str, + file_extension: str = _DEFAULT_FORMAT, +) -> bool: try: + driver = _DRIVER_BY_EXTENSION.get(file_extension) + if driver is None: + raise ValueError( + f"Unsupported file_extension '{file_extension}'. " + f"Expected one of {sorted(_DRIVER_BY_EXTENSION)}." + ) + logger.info( - f"Starting build_layer with params: type={layer_type}, item={item_type}, plan={plan_id}, district={district}, block={block}" + f"build_layer: starting — layer_type={layer_type}, item_type={item_type}, " + f"plan_id={plan_id}, district={district}, block={block}, " + f"format={file_extension}/{driver}" ) - logger.debug(f"CSV path: {csv_path}") - logger.debug(f"Current working directory: {os.getcwd()}") + logger.debug(f"build_layer: csv_path={csv_path}, cwd={os.getcwd()}") - # Verify CSV exists and is readable if not os.path.exists(csv_path): raise FileNotFoundError(f"CSV file not found: {csv_path}") - # Load CSV and create geometry df_geom = pd.read_csv(csv_path) - logger.debug(f"CSV loaded successfully with {len(df_geom)} rows") + logger.info( + f"build_layer: loaded CSV with {len(df_geom)} row(s) " + f"and {len(df_geom.columns)} column(s)" + ) - # Verify required columns exist - required_columns = ["longitude", "latitude"] - missing_columns = [ - col for col in required_columns if col not in df_geom.columns - ] + missing_columns = [c for c in ("longitude", "latitude") if c not in df_geom.columns] if missing_columns: - raise ValueError(f"Missing required columns: {missing_columns}") + raise ValueError( + f"No record carried GPS coordinates (missing columns: {missing_columns}); " + f"check the source form's GPS_point field" + ) + + before = len(df_geom) + df_geom = df_geom.dropna(subset=["longitude", "latitude"]).reset_index(drop=True) + dropped = before - len(df_geom) + if dropped: + logger.warning( + f"build_layer: dropped {dropped}/{before} row(s) without GPS coordinates" + ) + if df_geom.empty: + raise ValueError("No rows with valid GPS coordinates after filtering") geometry = [Point(xy) for xy in zip(df_geom["longitude"], df_geom["latitude"])] - gdf = gpd.GeoDataFrame(df_geom, geometry=geometry) - gdf.crs = "EPSG:4326" + gdf = gpd.GeoDataFrame(df_geom, geometry=geometry, crs="EPSG:4326") formatted_block = transform_name(name=block) store_layer_name = f"{item_type}_{plan_id}_{district}_{formatted_block}" - logger.debug(f"Store layer name: {store_layer_name}") + logger.info(f"build_layer: store/layer name='{store_layer_name}'") - # Use a temporary directory with explicit permissions with tempfile.TemporaryDirectory(prefix="geoserver_") as tmpdirname: - os.chmod(tmpdirname, 0o777) # Ensure write permissions - shapefile_path = os.path.join(tmpdirname, f"{store_layer_name}.shp") - logger.debug(f"Temporary shapefile path: {shapefile_path}") - - gdf.to_file(shapefile_path, driver="ESRI Shapefile") - logger.debug("Shapefile created successfully") - - with BytesIO() as shapefile_buffer: - with zipfile.ZipFile( - shapefile_buffer, "w", zipfile.ZIP_DEFLATED - ) as zip_buffer: - for filename in os.listdir(tmpdirname): - file_path = os.path.join(tmpdirname, filename) - with open(file_path, "rb") as file: - zip_buffer.writestr(filename, file.read()) - - shapefile_buffer.seek(0) - logger.debug("ZIP file created in memory") - - push_result = push_layer_to_geoserver( - shapefile_buffer, store_layer_name, workspace=layer_type - ) - logger.info(f"Geoserver Push Result: {push_result}") + os.chmod(tmpdirname, 0o777) + payload_bytes = _write_layer_payload( + gdf, tmpdirname, store_layer_name, file_extension, driver + ) + logger.info( + f"build_layer: payload prepared ({len(payload_bytes)} bytes, " + f"{len(gdf.columns)} attribute(s) including geometry)" + ) + + push_result = push_layer_to_geoserver( + payload_bytes, + store_layer_name, + workspace=layer_type, + file_extension=file_extension, + ) + logger.info(f"build_layer: geoserver push result — {push_result}") return True except Exception as e: - logger.error(f"Exception in build_layer: {str(e)}") + logger.error(f"build_layer: exception — {str(e)}") logger.error(traceback.format_exc()) return False +def _write_layer_payload( + gdf: gpd.GeoDataFrame, + tmpdirname: str, + store_layer_name: str, + file_extension: str, + driver: str, +) -> bytes: + """ + Serialise `gdf` to disk in the requested format and return the wire payload + GeoServer expects. GeoPackage uploads as a single file; Shapefile uploads + as a zipped bundle of the .shp + sidecars. + """ + if file_extension == "gpkg": + gpkg_path = os.path.join(tmpdirname, f"{store_layer_name}.gpkg") + gdf.to_file(gpkg_path, driver=driver, layer=store_layer_name) + logger.debug(f"build_layer: wrote GeoPackage at {gpkg_path}") + with open(gpkg_path, "rb") as fp: + return fp.read() + + import zipfile + + shapefile_path = os.path.join(tmpdirname, f"{store_layer_name}.shp") + gdf.to_file(shapefile_path, driver=driver) + logger.debug(f"build_layer: wrote Shapefile bundle at {shapefile_path}") + buffer = BytesIO() + with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf: + for filename in os.listdir(tmpdirname): + with open(os.path.join(tmpdirname, filename), "rb") as src: + zf.writestr(filename, src.read()) + return buffer.getvalue() + + def push_layer_to_geoserver( - in_memory_zip, store_layer_name, workspace="test_workspace" -): + data: bytes, + store_layer_name: str, + workspace: str = "test_workspace", + file_extension: str = _DEFAULT_FORMAT, +) -> str: try: logger.debug( - f"Pushing layer to geoserver: {store_layer_name} in workspace {workspace}" + f"push_layer_to_geoserver: store='{store_layer_name}', " + f"workspace='{workspace}', extension='{file_extension}', size={len(data)} bytes" ) geo = Geoserver_BB() + deleted = geo.delete_datastore_if_exists(workspace, store_layer_name) + if deleted: + logger.info( + f"push_layer_to_geoserver: cleared stale datastore " + f"'{workspace}:{store_layer_name}' before re-publishing" + ) return geo.create_datastore( - path=in_memory_zip, store_name=store_layer_name, workspace=workspace + data=data, + store_name=store_layer_name, + workspace=workspace, + file_extension=file_extension, ) except Exception as e: - logger.error(f"Error pushing to geoserver: {str(e)}") + logger.error(f"push_layer_to_geoserver: error — {str(e)}") raise diff --git a/plans/tests.py b/plans/tests.py index 9b4ddf26..bb68993b 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -1,9 +1,18 @@ # plans/tests.py +import csv +import os +import tempfile +from datetime import datetime, timezone +from unittest.mock import patch + from django.test import TestCase from django.urls import reverse from rest_framework.test import APITestCase, APIClient from rest_framework import status + +from dpr.models import ODK_settlement from .models import Plan +from .utils import fetch_db_data from projects.models import Project, AppType from organization.models import Organization from users.models import User, UserProjectGroup @@ -312,3 +321,236 @@ def test_delete_plan_as_viewer(self): # Verify plan was not deleted self.assertEqual(Plan.objects.count(), 1) + + +# Minimal valid ODK settlement JSON (same structure as ODK submissions stored in data_settlement) +def _make_settlement_json(plan_id, block_name, settlement_id="SETT001", review_state="hasIssues"): + return { + "__id": f"uuid:{settlement_id}", + "__system": {"reviewState": review_state, "submissionDate": "2024-01-01T00:00:00Z"}, + "block_name": block_name, + "plan_id": str(plan_id), + "GPS_point": { + "point_mapsappearance": { + "coordinates": [78.5, 20.5] + } + }, + "Settlements_id": settlement_id, + "Settlements_name": "Test Settlement", + "MNREGA_INFORMATION": { + "NREGA_aware": 10, + "NREGA_applied": 5, + "NREGA_job_card": 3, + "total_household": 2, + "NREGA_work_days": 100, + "q1": "yes", + "select_one_Y_N": "yes", + "select_one_demands": "wages", + "select_multiple_issues": "delayed_payment", + "select_one_contributions": "labour", + }, + } + + +def _create_settlement(plan_id, block_name, settlement_id="SETT001", + is_deleted=False, is_moderated=False, review_state="hasIssues", + data_override=None): + data = data_override or _make_settlement_json(plan_id, block_name, settlement_id, review_state) + return ODK_settlement.objects.create( + settlement_id=settlement_id, + settlement_name="Test Settlement", + submission_time=datetime(2024, 1, 1, tzinfo=timezone.utc), + submitted_by="test_user", + status_re=review_state, + latitude=20.5, + longitude=78.5, + block_name=block_name, + number_of_households=10, + largest_caste="General", + smallest_caste="SC", + settlement_status="active", + plan_id=str(plan_id), + plan_name="Test Plan", + uuid=f"uuid:{settlement_id}", + farmer_family={}, + livestock_census={}, + nrega_job_aware=10, + nrega_job_applied=5, + nrega_past_work="yes", + nrega_raise_demand="yes", + nrega_demand="wages", + nrega_issues="delayed_payment", + nrega_community="labour", + data_settlement=data, + is_deleted=is_deleted, + is_moderated=is_moderated, + ) + + +class FetchDbDataTest(TestCase): + + def setUp(self): + self.tmp_dir = tempfile.mkdtemp() + + def _csv_path(self, name="test.csv"): + return os.path.join(self.tmp_dir, name) + + def test_returns_true_and_writes_csv_for_valid_settlement(self): + _create_settlement(plan_id="42", block_name="test block") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + self.assertTrue(os.path.exists(csv_path)) + with open(csv_path) as f: + reader = csv.DictReader(f) + rows = list(reader) + self.assertEqual(len(rows), 1) + self.assertIn("latitude", rows[0]) + self.assertIn("longitude", rows[0]) + self.assertEqual(rows[0]["sett_id"], "SETT001") + self.assertEqual(rows[0]["sett_name"], "Test Settlement") + + def test_moderated_record_uses_moderated_json(self): + moderated_data = _make_settlement_json("42", "test block") + moderated_data["Settlements_name"] = "Moderated Settlement Name" + _create_settlement( + plan_id="42", + block_name="test block", + settlement_id="SETT002", + is_moderated=True, + data_override=moderated_data, + ) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_name"], "Moderated Settlement Name") + + def test_deleted_records_excluded(self): + _create_settlement(plan_id="42", block_name="test block", is_deleted=True) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertFalse(result) + self.assertFalse(os.path.exists(csv_path)) + + def test_rejected_submissions_excluded_by_transform(self): + _create_settlement( + plan_id="42", block_name="test block", + settlement_id="SETT003", review_state="rejected" + ) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertFalse(result) + + def test_returns_false_for_no_matching_records(self): + csv_path = self._csv_path() + result = fetch_db_data(csv_path, "settlement", "nonexistent_block", "99") + self.assertFalse(result) + + def test_returns_false_for_unknown_resource_type(self): + csv_path = self._csv_path() + result = fetch_db_data(csv_path, "unknown_type", "test_block", "42") + self.assertFalse(result) + + def test_block_name_with_spaces_matches_underscore_block_param(self): + _create_settlement(plan_id="42", block_name="test block") + csv_path = self._csv_path() + + # block param uses underscore; DB has spaces — should still match + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + + def test_only_matching_plan_id_returned(self): + _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") + _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_id"], "S1") + + +class AddResourcesAPITest(APITestCase): + + def setUp(self): + self.client = APIClient() + self.url = reverse("add_resources") + + @patch("plans.api.build_layer", return_value=True) + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_201_when_db_data_exists(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + mock_sync.assert_called_once_with("settlement") + mock_build.assert_called_once() + + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_404_when_no_db_data(self, mock_sync): + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "99", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "no block", + }) + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + @patch("plans.api.build_layer", return_value=True) + @patch("plans.api.sync_form_type", return_value=False) + def test_proceeds_with_db_data_even_when_sync_fails(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + mock_build.assert_called_once() + + @patch("plans.api.build_layer", return_value=False) + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_500_when_build_layer_fails(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/plans/utils.py b/plans/utils.py index e2fc661d..7e96cfcf 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -1,24 +1,22 @@ import csv -import json import logging import re from datetime import datetime, timezone -import os + import dateutil.parser import requests -from nrm_app.settings import ODK_PASSWORD, ODK_USERNAME -from utilities.constants import ( - ODK_URL_SESSION, - ODK_URL_agri, - ODK_URL_gw, - ODK_URL_livelihood, - ODK_URL_settlement, - ODK_URL_swb, - ODK_URL_waterbody, - ODK_URL_well, - ODK_URL_crop +from dpr.models import ( + ODK_settlement, ODK_well, ODK_waterbody, + ODK_groundwater, ODK_agri, ODK_livelihood, ODK_crop, + SWB_maintenance, SWB_RS_maintenance, GW_maintenance, Agri_maintenance, + ODK_agrohorticulture, +) +from moderation.utils.utils import ( + MODEL_FIELD_EXTRACTORS as _MODERATION_EXTRACTORS, + extract_lat_lon_from_gps, ) +from utilities.constants import ODK_URL_SESSION logger = logging.getLogger(__name__) @@ -39,121 +37,40 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() -# MARK: Fetch ODK Data -def fetch_odk_data(csv_path, resource_type, block, plan_id): - print("CSV path: ", csv_path) - - odk_url_map = { - "settlement": ODK_URL_settlement, - "well": ODK_URL_well, - "waterbody": ODK_URL_waterbody, - "cropping" : ODK_URL_crop, - "plan_gw": ODK_URL_gw, - "main_swb": ODK_URL_swb, - "plan_agri": ODK_URL_agri, - "livelihood": ODK_URL_livelihood, - } - - if resource_type not in odk_url_map: - logger.warning(f"Unknown resource type: {resource_type}") - return False - - return odk_data( - odk_url_map[resource_type], - csv_path, - block, - plan_id, - resource_type=resource_type, - ) - - -def odk_data(ODK_url, csv_path, block, plan_id, resource_type): - request_obj_odk = requests.get(ODK_url, auth=(ODK_USERNAME, ODK_PASSWORD)) - response_dict = json.loads(request_obj_odk.content) - response_list = response_dict["value"] - logger.info(f"Fetched data from the ODK: {ODK_url}") - all_keys = set() - - if resource_type == "settlement": - modified_response_list = modify_response_list_settlement( - response_list, block, plan_id - ) - elif resource_type == "well": - modified_response_list = modify_response_list_well( - response_list, block, plan_id - ) - elif resource_type == "waterbody": - modified_response_list = modify_response_list_waterbody( - response_list, block, plan_id - ) - - elif resource_type == "cropping": - modified_response_list = modify_reponse_list_cropping( - response_list, block, plan_id - ) - - elif resource_type == "plan_gw": - modified_response_list = modify_response_list_plan( - response_list, block, plan_id - ) - for item in modified_response_list: - all_keys.update(extract_keys(item)) - fieldnames = list(all_keys) - - elif resource_type == "main_swb": - modified_response_list = modify_response_list_plan( - response_list, block, plan_id - ) - for item in modified_response_list: - all_keys.update(extract_keys(item)) - fieldnames = list(all_keys) - - elif resource_type == "plan_agri": - modified_response_list = modify_response_list_plan( - response_list, block, plan_id - ) - for item in modified_response_list: - all_keys.update(extract_keys(item)) - fieldnames = list(all_keys) - - elif resource_type == "livelihood": - modified_response_list = modify_response_list_livelihood( - response_list, block, plan_id - ) - for item in modified_response_list: - all_keys.update(extract_keys(item)) - fieldnames = list(all_keys) +_RESOURCE_TYPES_FLAT_HEADER = frozenset({ + "settlement", "well", "waterbody", "cropping", +}) +_RESOURCE_TYPES_UNION_HEADER = frozenset({ + "plan_gw", "plan_agri", "main_swb", "main_gw", "main_swb_rs", "main_agri", + "livelihood", "agrohorticulture", +}) - if not modified_response_list: - logger.warning(f"No ODK data found for the given Plan ID: {plan_id}") - return False - if resource_type in ["settlement", "well", "waterbody", "cropping"]: +def _write_csv(resource_type, modified_response_list, all_keys, csv_path): + if resource_type in _RESOURCE_TYPES_FLAT_HEADER: header_keys = modified_response_list[0].keys() - print("FIELD NAMES", header_keys) with open(csv_path, "w", encoding="utf-8") as output_file: dict_writer = csv.DictWriter( output_file, fieldnames=header_keys, extrasaction="ignore" ) dict_writer.writeheader() dict_writer.writerows(modified_response_list) - logger.info(f"CSV generated for resource : {resource_type}") - elif resource_type in ["plan_gw", "main_swb", "plan_agri", "livelihood"]: + elif resource_type in _RESOURCE_TYPES_UNION_HEADER: with open(csv_path, "w", newline="", encoding="utf-8") as csvfile: - dict_writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + dict_writer = csv.DictWriter(csvfile, fieldnames=list(all_keys)) dict_writer.writeheader() for item in modified_response_list: - flattened_item = flatten_dict(item) - dict_writer.writerow(flattened_item) - logger.info(f"CSV generated for the work : {resource_type}") - - return True + dict_writer.writerow(flatten_dict(item)) + logger.info(f"CSV generated for '{resource_type}' at {csv_path}") # MARK: Modify ODK Settlement Data def modify_response_list_settlement(res, block, plan_id): res_list = [] - print(f"block name: {block} and plan id: {plan_id}") + logger.info( + "modify_response_list_settlement: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) for result in res: if result is None: continue @@ -170,23 +87,7 @@ def modify_response_list_settlement(res, block, plan_id): if str(result.get("plan_id")) != str(plan_id): continue - latitude = None - longitude = None - - if ( - isinstance(result, dict) - and result.get("GPS_point") is not None - and result["GPS_point"].get("point_mapsappearance") is not None - and "coordinates" in result["GPS_point"]["point_mapsappearance"] - ): - try: - latitude = result["GPS_point"]["point_mapsappearance"]["coordinates"][1] - longitude = result["GPS_point"]["point_mapsappearance"]["coordinates"][ - 0 - ] - except Exception as e: - print(f"Could not get the coordinates for settlement: {e}") - + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) if latitude is not None and longitude is not None: result["latitude"] = latitude result["longitude"] = longitude @@ -196,8 +97,8 @@ def modify_response_list_settlement(res, block, plan_id): result["sett_name"] = result["Settlements_name"] try: mgnrega_info = result.get("MNREGA_INFORMATION", {}) - except Exception as e: - print(e) + except Exception: + logger.exception("modify_response_list_settlement: failed reading MNREGA_INFORMATION") continue if mgnrega_info: result["job_aware"] = mgnrega_info.get("NREGA_aware", "") or 0 @@ -235,21 +136,7 @@ def modify_response_list_well(res, block, plan_id): if str(result.get("plan_id")) != str(plan_id): continue - latitude = None - longitude = None - - if ( - isinstance(result, dict) - and result.get("GPS_point") is not None - and result["GPS_point"].get("point_mapappearance") is not None - and "coordinates" in result["GPS_point"]["point_mapappearance"] - ): - try: - latitude = result["GPS_point"]["point_mapappearance"]["coordinates"][1] - longitude = result["GPS_point"]["point_mapappearance"]["coordinates"][0] - except Exception as e: - print(f"Could not get the coordinates for settlement: {e}") - + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) if latitude is not None and longitude is not None: result["latitude"] = latitude result["longitude"] = longitude @@ -282,8 +169,8 @@ def modify_response_list_well(res, block, plan_id): result["repair"] = repair_value else: result["repair"] = "NA" - except Exception as e: - print("Exception occured in adding data from ODK to well layer: ", e) + except Exception: + logger.exception("modify_response_list_well: failed enriching record") continue res_list.append(result) @@ -292,8 +179,11 @@ def modify_response_list_well(res, block, plan_id): # MARK: Modify ODK Waterbody Data def modify_response_list_waterbody(res, block, plan_id): - print("Result:", res) res_list = [] + logger.info( + "modify_response_list_waterbody: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) for result in res: if result is None: continue @@ -307,19 +197,7 @@ def modify_response_list_waterbody(res, block, plan_id): if str(result.get("plan_id")) != str(plan_id): continue - latitude = None - longitude = None - if ( - isinstance(result, dict) - and result.get("GPS_point") is not None - and result["GPS_point"].get("point_mapappearance") is not None - and "coordinates" in result["GPS_point"]["point_mapappearance"] - ): - try: - latitude = result["GPS_point"]["point_mapappearance"]["coordinates"][1] - longitude = result["GPS_point"]["point_mapappearance"]["coordinates"][0] - except Exception as e: - print(f"Could not get the coordinates for settlement: {e}") + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) if latitude is not None and longitude is not None: result["latitude"] = latitude result["longitude"] = longitude @@ -382,8 +260,8 @@ def modify_response_list_waterbody(res, block, plan_id): # Add the dimensions to the result dictionary # result.update(water_structure_dimension) - except Exception as e: - print("Exception in adding a water structure record: ", e) + except Exception: + logger.exception("modify_response_list_waterbody: failed enriching record") continue res_list.append(result) return res_list @@ -392,7 +270,10 @@ def modify_response_list_waterbody(res, block, plan_id): # MARK: Modify ODK Cropping Data def modify_reponse_list_cropping(res, block, plan_id): res_list = [] - print(f"block name: {block} and plan id: {plan_id}") + logger.info( + "modify_reponse_list_cropping: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) for result in res: if result is None: @@ -412,23 +293,8 @@ def modify_reponse_list_cropping(res, block, plan_id): continue - latitude = None - longitude = None - - if ( - isinstance(result, dict) - and result.get("GPS_point") is not None - and result["GPS_point"].get("point_mapappearance") is not None - and "coordinates" in result["GPS_point"]["point_mapappearance"] - ): - try: - latitude = result["GPS_point"]["point_mapappearance"]["coordinates"][1] - longitude = result["GPS_point"]["point_mapappearance"]["coordinates"][0] - except Exception as e: - print(f"Could not get the coordinates for crop patch: {e}") - + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) if latitude is not None and longitude is not None: - print(result) result["latitude"] = latitude result["longitude"] = longitude @@ -487,23 +353,7 @@ def modify_response_list_plan(res, block, plan_id): if str(result.get("plan_id")) != str(plan_id): continue - latitude = None - longitude = None - - if ( - isinstance(result, dict) - and result.get("GPS_point") is not None - and result["GPS_point"].get("point_mapsappearance") is not None - and "coordinates" in result["GPS_point"]["point_mapsappearance"] - ): - try: - latitude = result["GPS_point"]["point_mapsappearance"]["coordinates"][1] - longitude = result["GPS_point"]["point_mapsappearance"]["coordinates"][ - 0 - ] - except Exception as e: - print(f"Could not get the coordinates for settlement: {e}") - + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) if latitude is not None and longitude is not None: result["latitude"] = latitude result["longitude"] = longitude @@ -567,21 +417,7 @@ def modify_response_list_livelihood(res, block, plan_id): if str(result.get("plan_id")) != str(plan_id): continue - latitude = None - longitude = None - - if ( - isinstance(result, dict) - and result.get("GPS_point") is not None - and result["GPS_point"].get("point_mapappearance") is not None - and "coordinates" in result["GPS_point"]["point_mapappearance"] - ): - try: - latitude = result["GPS_point"]["point_mapappearance"]["coordinates"][1] - longitude = result["GPS_point"]["point_mapappearance"]["coordinates"][0] - except Exception as e: - print(f"Could not get the coordinates for settlement: {e}") - + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) if latitude is not None and longitude is not None: result["latitude"] = latitude result["longitude"] = longitude @@ -591,6 +427,269 @@ def modify_response_list_livelihood(res, block, plan_id): return res_list +# MARK: Modify ODK Maintenance / Agrohorticulture (Generic) +def modify_response_list_work(res, block, plan_id): + """ + Robust transform for maintenance and agrohorticulture submissions. + Unlike `modify_response_list_plan`, this avoids bracket-access on keys + that maintenance/agrohorticulture blobs may not carry (e.g. work_id, + Beneficiary_Name). Block filter is best-effort: applied only when the + blob actually has block_name (these models have no block_name DB column). + """ + res_list = [] + for result in res: + if result is None: + continue + if not isinstance(result, dict): + continue + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + blob_block = result.get("block_name") + if blob_block: + try: + if normalize_name(str(blob_block).lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + lat, lon = extract_lat_lon_from_gps(result.get("GPS_point")) + if lat is not None and lon is not None: + result["latitude"] = lat + result["longitude"] = lon + + sys_info = result.get("__system") or {} + if isinstance(sys_info, dict): + review_state = sys_info.get("reviewState") + if review_state: + result["status_re"] = review_state + + res_list.append(result) + return res_list + + +# Layer-build source-of-truth registry. Key = resource_type / work_type the +# `/add_resources` and `/add_works` endpoints accept. Tuple = (Model, JSON +# blob field, model has block_name column for DB-level pre-filtering). +# +# Resources (workspace=resources): settlement, well, waterbody, cropping +# Works (workspace=works): +# plan_gw — new recharge structures (groundwater) +# main_gw — maintenance of recharge structures +# plan_agri — new irrigation structures +# main_agri — maintenance of irrigation structures +# main_swb — surface water body maintenance (water-structure form) +# main_swb_rs — remote-sensed surface water body maintenance +# livelihood — livelihood +# agrohorticulture — agrohorticulture +_DB_CONFIG = { + "settlement": (ODK_settlement, "data_settlement", True), + "well": (ODK_well, "data_well", True), + "waterbody": (ODK_waterbody, "data_waterbody", True), + "cropping": (ODK_crop, "data_crop", False), + "plan_gw": (ODK_groundwater, "data_groundwater", True), + "plan_agri": (ODK_agri, "data_agri", True), + "livelihood": (ODK_livelihood, "data_livelihood", True), + "main_swb": (SWB_maintenance, "data_swb_maintenance", False), + "main_gw": (GW_maintenance, "data_gw_maintenance", False), + "main_swb_rs": (SWB_RS_maintenance, "data_swb_rs_maintenance", False), + "main_agri": (Agri_maintenance, "data_agri_maintenance", False), + # `data_agohorticulture` is the actual model field name — there is a + # spelling typo in the schema. Respecting it here to avoid a migration. + "agrohorticulture": (ODK_agrohorticulture, "data_agohorticulture", False), +} + +# Fields never useful to project alongside the JSON blob: the blob itself, +# soft-delete metadata, and relational fields (FKs serialise poorly via values()). +_PROJECTION_EXCLUDED_FIELDS = frozenset({ + "data_before_moderation", + "is_deleted", + "deleted_at", + "deleted_by", + "moderated_by", +}) + + +def _scalar_projection_fields(model, json_blob_field: str) -> list: + """ + Concrete, non-relational fields on `model` safe to project via `.values()` + alongside the raw ODK JSON blob. Captures everything moderation can edit + (settlement_name, block_name, nrega_*, lat/lon, status_re, ...) so the + generated layer reflects the latest moderated values, not just the + original ODK submission stored in `data_`. + """ + skip = {json_blob_field, *_PROJECTION_EXCLUDED_FIELDS} + fields = [] + for f in model._meta.get_fields(): + if not getattr(f, "concrete", False): + continue + if f.many_to_one or f.one_to_one or f.many_to_many or f.one_to_many: + continue + if f.name in skip: + continue + fields.append(f.name) + return fields + + +def _merge_moderated(blob: dict, friendly: dict, friendly_canonical: bool) -> dict: + """ + Merge friendly DB column values into the raw ODK blob so the layer carries + both the original ODK keys (Settlements_name, GPS_point, ...) and the + moderated friendly columns (settlement_name, block_name, ...). + + `friendly_canonical=True` (model has a moderation extractor): friendly + columns are kept in sync with every moderation edit, so they win on + collision. `False` (no extractor, e.g. SWB_maintenance): moderation only + touches the blob, so the blob wins on collision. + + GeoPackage/SQLite (the downstream layer format) is case-insensitive on + column names, so a blob key like "GPS_point" and a friendly column + "gps_point" would collide on write. We dedupe case-insensitively in + favour of the canonical side; non-colliding keys (e.g. "Settlements_name" + vs "settlement_name") are both preserved. + """ + if friendly_canonical: + winner, loser = friendly, blob + else: + winner, loser = blob, friendly + + winner_lower = {k.lower() for k in winner} + loser_filtered = { + k: v for k, v in loser.items() + if k.lower() not in winner_lower + } + return {**loser_filtered, **winner} + + +# MARK: Fetch DB Data +def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: + """ + Build the CSV of records for the given (resource_type, plan_id, block) + by reading from our DB (post-moderation source of truth). + + Returns the number of rows actually written to the CSV; 0 means no + usable data was found and the caller should treat it as a soft 404. + """ + logger.info( + f"fetch_db_data: starting — resource_type={resource_type}, " + f"plan_id={plan_id}, block={block}, csv_path={csv_path}" + ) + + entry = _DB_CONFIG.get(resource_type) + if not entry: + logger.warning(f"fetch_db_data: unknown resource_type '{resource_type}'") + return 0 + + model, data_field, has_block_col = entry + projection_fields = _scalar_projection_fields(model, data_field) + friendly_canonical = model in _MODERATION_EXTRACTORS + logger.info( + f"fetch_db_data: querying {model.__name__}.{data_field} " + f"with plan_id={plan_id}, is_deleted=False" + + ( + f", block_name icontains '{block}'" + if has_block_col + else " (no block_name column, skipping DB block filter)" + ) + ) + logger.info( + f"fetch_db_data: projecting blob '{data_field}' + " + f"{len(projection_fields)} moderated column(s) " + f"(friendly_canonical={friendly_canonical}): {projection_fields}" + ) + + qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) + if has_block_col: + qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) + + raw_rows = list(qs.values(data_field, *projection_fields)) + logger.info( + f"fetch_db_data: DB returned {len(raw_rows)} record(s) for " + f"resource_type={resource_type}, plan_id={plan_id}" + ) + + response_list = [] + empty_blob_count = 0 + for row in raw_rows: + blob = row.get(data_field) or {} + if not blob: + empty_blob_count += 1 + continue + friendly = {k: v for k, v in row.items() if k != data_field} + response_list.append(_merge_moderated(blob, friendly, friendly_canonical)) + + if empty_blob_count: + logger.warning( + f"fetch_db_data: skipped {empty_blob_count} record(s) with empty " + f"{data_field}" + ) + + if not response_list: + logger.warning( + f"fetch_db_data: no usable records for resource_type={resource_type}, " + f"plan_id={plan_id}, block={block}" + ) + return 0 + + logger.info( + f"fetch_db_data: running transform for resource_type={resource_type} " + f"on {len(response_list)} record(s) (each enriched with " + f"{len(projection_fields)} moderated column(s))" + ) + + all_keys = set() + if resource_type == "settlement": + rows = modify_response_list_settlement(response_list, block, plan_id) + elif resource_type == "well": + rows = modify_response_list_well(response_list, block, plan_id) + elif resource_type == "waterbody": + rows = modify_response_list_waterbody(response_list, block, plan_id) + elif resource_type == "cropping": + rows = modify_reponse_list_cropping(response_list, block, plan_id) + elif resource_type in ["plan_gw", "main_swb", "plan_agri"]: + rows = modify_response_list_plan(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + elif resource_type == "livelihood": + rows = modify_response_list_livelihood(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + elif resource_type in [ + "main_gw", "main_swb_rs", "main_agri", "agrohorticulture", + ]: + rows = modify_response_list_work(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + else: + logger.warning( + f"fetch_db_data: no transform defined for resource_type='{resource_type}'" + ) + return 0 + + logger.info( + f"fetch_db_data: transform produced {len(rows)} row(s) " + f"(filtered from {len(response_list)}) for resource_type={resource_type}" + ) + + if not rows: + logger.warning( + f"fetch_db_data: transform returned empty list for " + f"resource_type={resource_type}, plan_id={plan_id}, block={block}" + ) + return 0 + + logger.info(f"fetch_db_data: writing CSV to {csv_path}") + _write_csv(resource_type, rows, all_keys, csv_path) + logger.info( + f"fetch_db_data: done — {len(rows)} row(s) written to {csv_path} " + f"(columns include {len(projection_fields)} moderated friendly field(s))" + ) + return len(rows) + + def flatten_dict(d, parent_key="", sep="_"): items = [] for k, v in d.items(): diff --git a/plans/views.py b/plans/views.py index d30a332c..931a8c1b 100755 --- a/plans/views.py +++ b/plans/views.py @@ -6,6 +6,7 @@ from rest_framework import permissions, status, viewsets from rest_framework.authentication import BaseAuthentication from rest_framework.decorators import action +from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response from rest_framework_simplejwt.authentication import JWTAuthentication @@ -30,7 +31,6 @@ PlanCreateSerializer, PlanUpdateSerializer, ) -from rest_framework.pagination import PageNumberPagination STATE_CENTROIDS = { "Jammu & Kashmir": {"lat": 34.0837, "lon": 74.7973}, diff --git a/templates/admin/dpr/dpr_report/change_list.html b/templates/admin/dpr/dpr_report/change_list.html new file mode 100644 index 00000000..36d8c9ae --- /dev/null +++ b/templates/admin/dpr/dpr_report/change_list.html @@ -0,0 +1,11 @@ +{% extends "admin/change_list.html" %} + +{% block object-tools-items %} +
  • +
    + {% csrf_token %} + +
    +
  • + {{ block.super }} +{% endblock %} From 69e800eac43a2377d34b7fd52e27dad76b80b363 Mon Sep 17 00:00:00 2001 From: Aman Verma Date: Thu, 11 Jun 2026 12:43:53 +0530 Subject: [PATCH 06/38] Feature/tree health raw scripts (#1004) * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * tree health raw script * mws report revert --------- Co-authored-by: Ksheetiz-24 --- utilities/scripts/tree_health/README.md | 116 ++ .../IS_DW_sentinel_data_export_new_year.ipynb | 1 + .../predict_ccd_ch_results.ipynb | 1 + .../colab_notebooks/trees_corrections.ipynb | 1 + .../colab_notebooks/uploadAssets.ipynb | 1 + .../uploadAssets_correction.ipynb | 1 + .../tree_health/gee_scripts/ccd_change.js | 76 ++ .../tree_health/gee_scripts/ch_change.js | 74 ++ .../tree_health/gee_scripts/fc_to_image.js | 157 +++ .../gee_scripts/fc_to_image_corrections.js | 184 +++ .../gee_scripts/modal_change_analysis_ccd.js | 668 +++++++++++ .../gee_scripts/modal_change_analysis_ch.js | 1020 +++++++++++++++++ .../gee_scripts/modal_overall_change.js | 208 ++++ 13 files changed, 2508 insertions(+) create mode 100644 utilities/scripts/tree_health/README.md create mode 100644 utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb create mode 100644 utilities/scripts/tree_health/gee_scripts/ccd_change.js create mode 100644 utilities/scripts/tree_health/gee_scripts/ch_change.js create mode 100644 utilities/scripts/tree_health/gee_scripts/fc_to_image.js create mode 100644 utilities/scripts/tree_health/gee_scripts/fc_to_image_corrections.js create mode 100644 utilities/scripts/tree_health/gee_scripts/modal_change_analysis_ccd.js create mode 100644 utilities/scripts/tree_health/gee_scripts/modal_change_analysis_ch.js create mode 100644 utilities/scripts/tree_health/gee_scripts/modal_overall_change.js diff --git a/utilities/scripts/tree_health/README.md b/utilities/scripts/tree_health/README.md new file mode 100644 index 00000000..804f110f --- /dev/null +++ b/utilities/scripts/tree_health/README.md @@ -0,0 +1,116 @@ +# Annual Tree Health Layer Generation (Pan-India) + +This guide walks you through the step-by-step process of generating the annual tree health layers for Pan-India. Follow the pipeline carefully to ensure consistency and accuracy across all datasets. + +## Pipeline Overview + +### 1. Download the Data (Colab Script) + +Start by running the Colab download script `IS_DW_sentinel_data_export_new_year.ipynb` to fetch the latest data. This script gathers all the necessary information required to initiate the analysis. + +> **Estimated Runtime:** 3–8 hours to download and export all data to Google Drive, depending on the size of the ACZ. + +> **Note:** Complete this step for all ACZs before moving to the next step. + +--- + +### 2. Predict Results (Colab Script) + +Once the data is ready, use the Colab prediction script `predict_ccd_ch_results.ipynb` to generate prediction outputs. + +The script produces a CSV file containing the predicted values. +m +--- + +### 3. Upload Predictions to GEE (Colab Script) + +Upload the prediction CSV to Google Earth Engine (GEE) using the Colab upload script `uploadAssets.ipynb`. + +--- + +### 4. Convert Feature Collections to Images (GEE Script) + +Run the `fc_to_image.js` script in GEE to convert the uploaded feature collection into an image layer. + +> **Important:** If the target image collection does not already exist (for example, `ccd_2020` or `ch_2020`), create it before proceeding. + +--- + +### 5. Apply Corrections (Colab Script) + +Run the Colab correction script `trees_corrections.ipynb` to make the necessary adjustments to the predictions. + +The script generates a new CSV file containing corrected values. + +> **Important:** This script corrects the data for the current year -1 and current year -2. Therefore, make sure to re-run **Steps 6, 7, and 8** for both years. + +--- + +### 6. Upload Corrected Data to GEE (Colab Script) + +Use the Colab upload corrections script `uploadAssets_correction.ipynb` to upload the correction CSV to GEE. + +--- + +### 7. Convert Corrected Data to Images (GEE Script) + +Run the `fc_to_image_corrections.js` script to convert the corrected data points into image layers. + +> **Important:** Create new image collections if required (for example, `corrections_ccd_2020` or `corrections_ch_2020`). + +> **Note:** Complete this step for all ACZs before moving to the next step. + +--- + +### 8. Run the Modal Change Analysis (GEE Scripts) + +After generating the modal outputs, run the modal change analysis scripts to calculate changes between two years based on the modal values of three consecutive years. + +#### `modal_change_analysis_ccd.js` + +Performs Canopy Cover Density (CCD) change analysis between two years using modal CCD outputs. + +For example, to compute the change between `year1` and `year2`, the script compares: + +- `mode(year1 - 1, year1, year1 + 1)` +- `mode(year2 - 1, year2, year2 + 1)` + +This approach helps reduce year-to-year noise and provides more stable change detection results. + +#### `modal_change_analysis_ch.js` + +Performs Canopy Height (CH) change analysis between two years using modal CH outputs derived from three consecutive years. + +--- + +### 9. Run the Overall Change Analysis (GEE Scripts) + +To generate the overall tree health change between two years, execute the following scripts: + +#### `ccd_change.js` + +Calculates the change in modal Canopy Cover Density (CCD) between the selected years. + +#### `ch_change.js` + +Calculates the change in modal Canopy Height (CH) between the selected years. + +#### `modal_overall_change.js` + +Combines the CCD and CH changes generated by the above scripts to perform the overall tree health change analysis between the selected years. + +--- + +## Workflow Summary + +1. Download data for all ACZs. +2. Generate prediction outputs. +3. Upload predictions to GEE. +4. Convert uploaded feature collections to image layers. +5. Apply corrections to the prediction outputs. +6. Upload corrected data to GEE. +7. Convert corrected feature collections to image layers. +8. Generate modal CCD and CH outputs. +9. Run modal change analysis to calculate CCD and CH changes between years using three-year modal windows. +10. Generate CCD and CH change layers. +11. Run the overall change analysis to combine CCD and CH changes into the final tree health change layer. \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb b/utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb new file mode 100644 index 00000000..adf409ba --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":11689,"status":"ok","timestamp":1775322096195,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"jteYmnZOnFje"},"outputs":[],"source":["import pandas as pd\n","from glob import glob\n","import re\n","import matplotlib.pyplot as plt\n","import json\n","import numpy as np\n","from scipy import stats as st\n","import ee\n","import shapely.geometry\n","from shapely.geometry import Point, Polygon\n","import geopandas as gpd\n","from math import sqrt\n","from shapely import wkt\n","import os\n","import time\n","import geemap"]},{"cell_type":"code","execution_count":20,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":1536,"status":"ok","timestamp":1775323431299,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"5s4VjzimnVwY","outputId":"c4cedd6c-0576-48d5-bfc3-3afc0134bbce"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["ee.Authenticate()\n","ee.Initialize(project='ext-datasets')"]},{"cell_type":"code","execution_count":4,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":44067,"status":"ok","timestamp":1775322195865,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ESTyH3bjXEMp","outputId":"8d2baa0d-9f83-41d5-e5a5-dee64cdc44bb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":18,"status":"ok","timestamp":1775322209346,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"Ktrj5HIGncZt","outputId":"a9205a41-8e79-4fb5-9080-dd14de322179"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["year = 2017"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775322216118,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"VqMB2CcvnfO4","outputId":"a969f5c8-80c1-4854-e1b6-da7d594c8c65"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Choose appropriate ACZ\n","\n","# agroclimatic_zone = \"Eastern Plateau & Hills Region\"\n","agroclimatic_zone = \"Southern Plateau and Hills Region\"\n","# agroclimatic_zone = \"East Coast Plains & Hills Region\"\n","# agroclimatic_zone = \"Western Plateau and Hills Region\"\n","# agroclimatic_zone = \"Central Plateau & Hills Region\"\n","# agroclimatic_zone = \"Lower Gangetic Plain Region\"\n","# agroclimatic_zone = \"Middle Gangetic Plain Region\"\n","# agroclimatic_zone = \"Eastern Himalayan Region\"\n","#agroclimatic_zone = \"Western Himalayan Region\"\n","# agroclimatic_zone = \"Upper Gangetic Plain Region\"\n","# agroclimatic_zone = \"Trans Gangetic Plain Region\"\n","# agroclimatic_zone = \"West Coast Plains & Ghat Region\" ## # Model not available\n","# agroclimatic_zone = \"Gujarat Plains & Hills Region\" ## # Model not available\n","# agroclimatic_zone = \"Western Dry Region\" ## # Model not available"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":4,"status":"ok","timestamp":1775322219096,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"c7iMtt-CniYQ","outputId":"13d24f8f-2ab2-4149-a3a6-a587cb238acb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775322221411,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"3npVzImWnlH0","outputId":"d443d000-eda0-4c52-9946-944002d4562e"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["india_boundary = ee.FeatureCollection(\"projects/ee-mtpictd/assets/harsh/Agroclimatic_regions\")\n","agrozone = india_boundary.filter(ee.Filter.eq('regionname', agroclimatic_zone)).geometry()\n","india_district_boundary = ee.FeatureCollection(\"projects/ee-indiasat/assets/india_district_boundaries\")"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775322226455,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"-kXRm3DJnnlU","outputId":"fcd76c20-eb44-43d9-e278-824978c6fd92"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["s1_bands = ['VV', 'VH', 'angle']\n","s2_bands = ['B1','B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B10', 'B11','B12']"]},{"cell_type":"code","execution_count":21,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775323441748,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"HyTMlhh7nqOD","outputId":"9399a761-e6af-4cb1-83ed-b61ee81ae395"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["START_DATE = {year-2: {'kharif': str(year-2)+'-07-01', 'rabi': str(year-2)+'-11-01', 'zaid': str(year-1)+'-03-01'},\n"," year-1: {'kharif': str(year-1)+'-07-01', 'rabi': str(year-1)+'-11-01', 'zaid': str(year)+'-03-01'},\n"," year: {'kharif': str(year)+'-07-01', 'rabi': str(year)+'-11-01', 'zaid': str(year+1)+'-03-01'}}\n","\n","END_DATE = {year-2: {'kharif': str(year-2)+'-10-31', 'rabi': str(year-1)+'-02-28', 'zaid': str(year-1)+'-06-30'},\n"," year-1: {'kharif': str(year-1)+'-10-31', 'rabi': str(year)+'-02-28', 'zaid': str(year)+'-06-30'},\n"," year: {'kharif': str(year)+'-10-31', 'rabi': str(year+1)+'-02-28', 'zaid': str(year+1)+'-06-30'}}"]},{"cell_type":"code","execution_count":22,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":73},"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775323443285,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"c8Me4AlontBi","outputId":"918d5170-1440-41f1-8236-578730308d7f"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["{2015: {'kharif': '2015-07-01', 'rabi': '2015-11-01', 'zaid': '2016-03-01'}, 2016: {'kharif': '2016-07-01', 'rabi': '2016-11-01', 'zaid': '2017-03-01'}, 2017: {'kharif': '2017-07-01', 'rabi': '2017-11-01', 'zaid': '2018-03-01'}}\n","{2015: {'kharif': '2015-10-31', 'rabi': '2016-02-28', 'zaid': '2016-06-30'}, 2016: {'kharif': '2016-10-31', 'rabi': '2017-02-28', 'zaid': '2017-06-30'}, 2017: {'kharif': '2017-10-31', 'rabi': '2018-02-28', 'zaid': '2018-06-30'}}\n"]}],"source":["print(START_DATE)\n","print(END_DATE)"]},{"cell_type":"code","execution_count":12,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":10,"status":"ok","timestamp":1775322231788,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9zuvGfH4nwHB","outputId":"6eba4155-87dc-4a8b-ecdc-416edc167bd8"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Will take an AOI geometry as input and return the 10km x 10km grids in it as a list\n","def createGrids(aoi):\n"," proj = ee.Projection('EPSG:4326')\n"," gridSize = 10000\n"," grid = aoi.coveringGrid(proj, gridSize)\n"," features = grid.getInfo()['features']\n"," return features"]},{"cell_type":"code","execution_count":13,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":46,"status":"ok","timestamp":1775322233759,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"cHhG3vDgnzmb","outputId":"ed3905c8-8e3b-4179-c065-1b597e8d7750"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def s2_mask(image):\n"," \"\"\"\n"," Getting a cloud-free Sentinel-2 imagery.\n"," \"\"\"\n"," quality_band = image.select('QA60')\n"," # Using the bit mask for clouds (bit 10) and cirrus clouds (bit 11) respectively.\n"," cloudmask = 1 << 10\n"," cirrusmask = 1 << 11\n"," # Both flags should be set to zero, indicating clear conditions of sky.\n"," mask = quality_band.bitwiseAnd(cloudmask).eq(0) and (quality_band.bitwiseAnd(cirrusmask).eq(0))\n"," return image.updateMask(mask)\n","\n","def get_s2_image(aoi, start_date, end_date):\n"," # s2_bands_season = [band + '_med_' + season for band in s2_bands]\n"," return ee.ImageCollection('COPERNICUS/S2_HARMONIZED').filterDate(\n"," start_date , end_date).filterBounds(aoi).filter(\n"," ee.Filter.lt(\"CLOUDY_PIXEL_PERCENTAGE\", 20)).sort('CLOUD_COVER').map(\n"," s2_mask).select(s2_bands).median().divide(10000).clip(aoi)\n","\n","def get_s1_image(aoi, start_date, end_date):\n"," # s1_bands_season = [band + '_' + season for band in s1_bands]\n"," return ee.ImageCollection('COPERNICUS/S1_GRD').filterDate(start_date , end_date).filterBounds(aoi).filter(\n"," ee.Filter.listContains('transmitterReceiverPolarisation', 'VV')).filter(\n"," ee.Filter.listContains('transmitterReceiverPolarisation', 'VH')).filter(\n"," ee.Filter.eq('instrumentMode', 'IW')).select(s1_bands).median().clip(aoi)"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775322235773,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"KD4RzXrTn2CK","outputId":"e402cf2f-b589-4e45-dc83-92db5bf7d468"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def save_data_csv(data_points, img_name, district, year):\n"," print(\"Saving data for\", district, year)\n"," new_img_name = img_name.replace('&', 'and')\n"," new_img_name = new_img_name.replace('(', '')\n"," new_img_name = new_img_name.replace(')', '')\n"," task = ee.batch.Export.table.toDrive(\n"," collection = data_points,\n"," description = new_img_name,\n"," folder = f\"{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{year}\", # f\"{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{district}_{year}\",\n"," fileNamePrefix = new_img_name,\n"," fileFormat = 'CSV'\n"," )\n"," task.start()\n"," print(\"Task Started\",task.status())\n"," return task"]},{"cell_type":"code","execution_count":23,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":53},"executionInfo":{"elapsed":19,"status":"ok","timestamp":1775323448485,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"X0eH_LS-n4j0","outputId":"c1686218-9aa1-4297-9d43-84fe8b1bc7f6"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["1\n","['Y.S.R.']\n"]}],"source":["df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = ['Y.S.R.']#list(df['Name'])\n","print(len(dist_list))\n","print(dist_list)"]},{"cell_type":"code","execution_count":24,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775323451067,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"pPZhyHc1n6zG","outputId":"fdc3a9aa-5b8c-4ebc-f944-9be9efa0e8bc"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["['2015', '2016', '2017']\n"]}],"source":["years = [str(int(year)-2), str(int(year)-1), str(year)]\n","year_0 = years[0]\n","year_1 = years[1]\n","year_2 = years[2]\n","year_suffix = {year_0: year_0[-2:], year_1: year_1[-2:], year_2: year_2[-2:]}\n","print(years)"]},{"cell_type":"code","execution_count":17,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775322268138,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"QWVDbWyDn9NZ","outputId":"38eafe48-4777-45d0-9fdf-726318b7c2e8"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def get_dw_tree_cover(aoi, start_date, end_date, scale = 25):\n"," tree_cover_dw = ee.ImageCollection(\"GOOGLE/DYNAMICWORLD/V1\").filterDate(start_date, end_date) \\\n"," .filterBounds(aoi).select('label').mode().clip(aoi)\n"," return tree_cover_dw.updateMask(tree_cover_dw.eq(1)).reproject(crs='EPSG:4326', scale=scale)\n","\n","def get_is_tree_cover(aoi, curr_year, scale = 25):\n"," curr_year = int(curr_year)\n"," indiasat_asset = f\"projects/corestack-datasets/assets/datasets/LULC_v3_river_basin/pan_india_lulc_v3_{curr_year}_{curr_year+1}\"\n"," lulc_image = ee.Image(indiasat_asset).select(\"predicted_label\").clip(aoi)\n"," return lulc_image.updateMask(lulc_image.eq(6)).reproject(crs='EPSG:4326', scale=scale)\n","\n","def get_tree_cover(aoi, curr_year, scale = 25):\n"," curr_year = int(curr_year)\n"," start_date = ee.Date(f'{curr_year}-07-01')\n"," end_date = ee.Date(f'{curr_year+1}-06-30')\n"," print(\"curr_year\", curr_year)\n"," tree_cover_is = get_is_tree_cover(aoi, curr_year, scale) if curr_year > 2016 else None\n"," tree_cover_dw = get_dw_tree_cover(aoi, start_date, end_date, scale)\n"," if tree_cover_is:\n"," tree_cover = tree_cover_is.mask().Or(tree_cover_dw.mask())\n"," else:\n"," print(\"=================Only DW\")\n"," tree_cover = tree_cover_dw.mask()\n"," # tree_cover = tree_cover_is.mask().Or(tree_cover_dw.mask()) if tree_cover_is else tree_cover_dw.mask()\n"," tree_cover = tree_cover.updateMask(tree_cover)\n"," return tree_cover.reproject(crs='EPSG:4326', scale=scale)"]},{"cell_type":"markdown","metadata":{"id":"LJEejVAPnvGR"},"source":["# Combined Grid"]},{"cell_type":"code","execution_count":25,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"EhNLoTWcN3Ws","executionInfo":{"status":"ok","timestamp":1775323714437,"user_tz":-330,"elapsed":249336,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"cff2690a-c434-4c34-9ec2-af6aea4c75f7"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Year 2017, District 0: Y.S.R., grids: 201\n","curr_year 2017\n","Grid 0\n","Grid 1\n","Grid 2\n","Grid 3\n","Grid 4\n","Grid 5\n","Grid 6\n","Grid 7\n","Grid 8\n","Grid 9\n","Grid 10\n","Grid 11\n","Grid 12\n","Grid 13\n","Grid 14\n","Grid 15\n","Grid 16\n","Grid 17\n","Grid 18\n","Grid 19\n","Grid 20\n","Grid 21\n","Grid 22\n","Grid 23\n","Grid 24\n","Grid 25\n","Grid 26\n","Grid 27\n","Grid 28\n","Grid 29\n","Grid 30\n","Grid 31\n","Grid 32\n","Grid 33\n","Grid 34\n","Grid 35\n","Grid 36\n","Grid 37\n","Grid 38\n","Grid 39\n","Grid 40\n","Grid 41\n","Grid 42\n","Grid 43\n","Grid 44\n","Grid 45\n","Grid 46\n","Grid 47\n","Grid 48\n","Grid 49\n","Grid 50\n","Grid 51\n","Grid 52\n","Grid 53\n","Grid 54\n","Grid 55\n","Grid 56\n","Grid 57\n","Grid 58\n","Grid 59\n","Grid 60\n","Grid 61\n","Grid 62\n","Grid 63\n","Grid 64\n","Grid 65\n","Grid 66\n","Grid 67\n","Grid 68\n","Grid 69\n","Grid 70\n","Grid 71\n","Grid 72\n","Grid 73\n","Grid 74\n","Grid 75\n","Grid 76\n","Grid 77\n","Grid 78\n","Grid 79\n","Grid 80\n","Grid 81\n","Grid 82\n","Grid 83\n","Grid 84\n","Grid 85\n","Grid 86\n","Grid 87\n","Grid 88\n","Grid 89\n","Grid 90\n","Grid 91\n","Grid 92\n","Grid 93\n","Grid 94\n","Grid 95\n","Grid 96\n","Grid 97\n","Grid 98\n","Grid 99\n","Grid 100\n","Grid 101\n","Grid 102\n","Grid 103\n","Grid 104\n","Grid 105\n","Grid 106\n","Grid 107\n","Grid 108\n","Grid 109\n","Grid 110\n","Grid 111\n","Grid 112\n","Grid 113\n","Grid 114\n","Grid 115\n","Grid 116\n","Grid 117\n","Grid 118\n","Grid 119\n","Grid 120\n","Grid 121\n","Grid 122\n","Grid 123\n","Grid 124\n","Grid 125\n","Grid 126\n","Grid 127\n","Grid 128\n","Grid 129\n","Grid 130\n","Grid 131\n","Grid 132\n","Grid 133\n","Grid 134\n","Grid 135\n","Grid 136\n","Grid 137\n","Grid 138\n","Grid 139\n","Grid 140\n","Grid 141\n","Grid 142\n","Grid 143\n","Grid 144\n","Grid 145\n","Grid 146\n","Grid 147\n","Grid 148\n","Grid 149\n","Grid 150\n","Grid 151\n","Grid 152\n","Grid 153\n","Grid 154\n","Grid 155\n","Grid 156\n","Grid 157\n","Grid 158\n","Grid 159\n","Grid 160\n","Grid 161\n","Grid 162\n","Grid 163\n","Grid 164\n","Grid 165\n","Grid 166\n","Grid 167\n","Grid 168\n","Grid 169\n","Grid 170\n","Grid 171\n","Grid 172\n","Grid 173\n","Grid 174\n","Grid 175\n","Grid 176\n","Grid 177\n","Grid 178\n","Grid 179\n","Grid 180\n","Grid 181\n","Grid 182\n","Grid 183\n","Grid 184\n","Grid 185\n","Grid 186\n","Grid 187\n","Grid 188\n","Grid 189\n","Grid 190\n","Grid 191\n","Grid 192\n","Grid 193\n","Grid 194\n","Grid 195\n","Grid 196\n","Grid 197\n","Grid 198\n","Grid 199\n","Grid 200\n","[, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ]\n","Saving data for Y.S.R. 2017\n","Task Started {'state': 'READY', 'description': 'Y.S.R._2017_all_grids', 'priority': 100, 'creation_timestamp_ms': 1775323714046, 'update_timestamp_ms': 1775323714046, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K7HMZ3QXN7RJ4OVN5RGYFIAY', 'name': 'projects/ext-datasets/operations/K7HMZ3QXN7RJ4OVN5RGYFIAY'}\n","249.2729194164276\n"]}],"source":["# Combined tiles and optimized\n","import sys\n","sys.setrecursionlimit(6000)\n","for curr_year in ['2017']:#years:\n","\n"," total_time = 0\n","\n"," dist_cnt = 0\n"," for district in dist_list:\n","\n"," start_time = time.time()\n","\n"," district_aoi = india_district_boundary.filter(ee.Filter.eq('Name', district)).geometry()\n"," district_aoi = district_aoi.intersection(agrozone)\n"," features = createGrids(district_aoi)\n","\n"," print(f'Year {curr_year}, District {dist_cnt}: {district}, grids: {len(features)}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{curr_year}/'\n"," os.makedirs(path, exist_ok=True)\n","\n"," # df = pd.DataFrame()\n"," # df['grid_num'] = list(range(len(features)))\n"," valid_grid_indices = []\n"," tree_points_list = []\n","\n"," # Precompute tree cover for the whole district\n"," tree_cover_district = get_tree_cover(district_aoi, curr_year, scale=25)\n","\n"," # Precompute S1 and S2 images for the whole district for each season\n"," start_date = START_DATE[int(curr_year)]\n"," end_date = END_DATE[int(curr_year)]\n"," s1_images = {}\n"," s2_images = {}\n"," for season in ['kharif', 'rabi', 'zaid']:\n"," try:\n"," s1_images[season] = get_s1_image(district_aoi, start_date[season], end_date[season]).updateMask(tree_cover_district)\n"," except Exception as exp:\n"," print(f\"S1 Error occured: {season}\", exp)\n"," s1_images[season] = None\n"," try:\n"," s2_images[season] = get_s2_image(district_aoi, start_date[season], end_date[season]).updateMask(tree_cover_district)\n"," except Exception as exp:\n"," print(f\"S2 Error occured: {season}\", exp)\n"," s2_images[season] = None\n","\n"," # List to hold all sample points\n"," all_sample_points = []\n","\n"," i = 0\n"," for feature in features:\n"," print(f'Grid {i}')\n","\n"," coord = feature['geometry']['coordinates'][0]\n"," aoi = ee.Geometry.Polygon(coord)\n"," aoi = aoi.intersection(district_aoi)\n"," # Get the coordinates of the geometry\n"," coordinates = aoi.coordinates()\n"," # Check if coordinates list is empty (i.e. geometry is empty)\n"," is_empty = coordinates.length().eq(0)\n","\n"," # print('Is geometry empty?', is_empty.getInfo())\n"," if not is_empty.getInfo():\n"," valid_grid_indices.append(i) # Only add if valid\n"," img_name = district + \"_\" + str(i) + \"_\" + str(curr_year)\n","\n"," # Clip tree cover to grid\n"," tree_cover = tree_cover_district.clip(aoi)\n","\n"," # Compose image for all seasons using precomputed images\n"," image = None\n"," if s1_images['kharif'] is not None:\n"," image = s1_images['kharif'].clip(aoi)\n"," if s2_images['kharif'] is not None and image is not None:\n"," s2_data = s2_images['kharif'].clip(aoi)\n"," image = image.addBands(s2_data).select(s1_bands + s2_bands)\n"," image = image.rename([band + '_kharif' for band in s1_bands + s2_bands])\n","\n"," for season in ['rabi', 'zaid']:\n"," s1_data = s1_images[season]\n"," s2_data = s2_images[season]\n"," if s1_data is not None:\n"," s1_data = s1_data.clip(aoi)\n"," if s2_data is not None and s1_data is not None:\n"," s2_data = s2_data.clip(aoi)\n"," image_merged = s1_data.addBands(s2_data).select(s1_bands + s2_bands)\n"," image_merged = image_merged.rename([band + '_' + season for band in s1_bands + s2_bands])\n"," image = image.addBands(image_merged) if image is not None else image_merged\n","\n"," # Sample points only if tree cover exists\n"," sample_tree_cover = tree_cover.sample(\n"," region=aoi,\n"," scale=25,\n"," factor=1,\n"," tileScale=10,\n"," geometries=True\n"," )\n"," try:\n"," tree_points = sample_tree_cover.size().getInfo()\n"," except:\n"," tree_points = 0\n"," tree_points_list.append(tree_points)\n","\n"," # if tree_points > 0 and image is not None:\n"," sample_points = image.sample(\n"," region=aoi,\n"," scale=25,\n"," factor=1,\n"," tileScale=10,\n"," geometries=True\n"," )\n"," all_sample_points.append(sample_points)\n","\n"," i += 1\n"," print(all_sample_points)\n"," # Merge all sample points into a single FeatureCollection\n"," if all_sample_points:\n"," merged_sample_points = all_sample_points[0]\n"," for sp in all_sample_points[1:]:\n"," merged_sample_points = merged_sample_points.merge(sp)\n","\n"," # Export the merged FeatureCollection\n"," try:\n"," img_name = district + \"_\" + str(curr_year) + \"_all_grids\"\n"," task = save_data_csv(merged_sample_points, img_name, district, curr_year)\n"," prev_task = task\n"," except Exception as e:\n"," print(e)\n"," while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," continue\n"," task = save_data_csv(merged_sample_points, img_name, path, curr_year)\n"," prev_task = task\n","\n"," df = pd.DataFrame()\n"," df['grid_num'] = valid_grid_indices\n"," df['tree_points'] = tree_points_list\n"," df.to_csv(path + 'tree_cover_points.csv', index=False)\n","\n"," dist_cnt += 1\n"," end_time = time.time()\n","\n"," total_time += (end_time - start_time)\n"," print(total_time)\n","\n"," # print(\"Waiting for last task to be completed...\")\n"," # while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," # continue\n"," # print(\"Last task completed!\")\n","\n"," # total_time += (time.time() - end_time)\n"," # print(\"Total Time Taken:\", total_time)"]},{"cell_type":"markdown","metadata":{"id":"snxgyeoRno-K"},"source":["# Separate Grid (Older version - Do not run this)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"P1WY_Cb7oAgz","outputId":"a7c02335-532e-4b97-820f-6361ad46ffb9"},"outputs":[{"data":{"text/html":["\n"," \n"," "],"text/plain":[""]},"metadata":{},"output_type":"display_data"},{"name":"stdout","output_type":"stream","text":["\u001b[1;30;43mStreaming output truncated to the last 5000 lines.\u001b[0m\n","Task Started {'state': 'READY', 'description': 'Haora_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762652761544, 'update_timestamp_ms': 1762652761544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JPA4UPIX3U5VCIKQ2SWCQECH', 'name': 'projects/ext-datasets/operations/JPA4UPIX3U5VCIKQ2SWCQECH'}\n","Grid 16\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762652766812, 'update_timestamp_ms': 1762652766812, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EAMYVBQXNMMEY4FOMWM43BNC', 'name': 'projects/ext-datasets/operations/EAMYVBQXNMMEY4FOMWM43BNC'}\n","Grid 17\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762652773310, 'update_timestamp_ms': 1762652773310, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XJ6UW3VLPSK622S7N3GTPBDE', 'name': 'projects/ext-datasets/operations/XJ6UW3VLPSK622S7N3GTPBDE'}\n","Grid 18\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762652779471, 'update_timestamp_ms': 1762652779471, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UQF6ZXFYNJIJDDAVOJAADO4Q', 'name': 'projects/ext-datasets/operations/UQF6ZXFYNJIJDDAVOJAADO4Q'}\n","Grid 19\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762652788203, 'update_timestamp_ms': 1762652788203, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V6CMFXQIIPPHMDY7G7RSBHXO', 'name': 'projects/ext-datasets/operations/V6CMFXQIIPPHMDY7G7RSBHXO'}\n","Grid 20\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762652795968, 'update_timestamp_ms': 1762652795968, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IEIRR7HN3564PNRHXV3B7LBO', 'name': 'projects/ext-datasets/operations/IEIRR7HN3564PNRHXV3B7LBO'}\n","Grid 21\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762652804392, 'update_timestamp_ms': 1762652804392, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OJ627QLSFNOELKQGFC4XWVXJ', 'name': 'projects/ext-datasets/operations/OJ627QLSFNOELKQGFC4XWVXJ'}\n","Grid 22\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762652812461, 'update_timestamp_ms': 1762652812461, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HVAIQPOGNWFJXL4P2GKTW6CP', 'name': 'projects/ext-datasets/operations/HVAIQPOGNWFJXL4P2GKTW6CP'}\n","Grid 23\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762652821455, 'update_timestamp_ms': 1762652821455, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7Z6YDDABUKVAF6WCB57HEL67', 'name': 'projects/ext-datasets/operations/7Z6YDDABUKVAF6WCB57HEL67'}\n","Grid 24\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762652829407, 'update_timestamp_ms': 1762652829407, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OMGDWCBDMII3R7MTIP5A46NM', 'name': 'projects/ext-datasets/operations/OMGDWCBDMII3R7MTIP5A46NM'}\n","Grid 25\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762652837188, 'update_timestamp_ms': 1762652837188, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZGIKQF5DUYPD5IC6PHGBDRZ4', 'name': 'projects/ext-datasets/operations/ZGIKQF5DUYPD5IC6PHGBDRZ4'}\n","Grid 26\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762652841386, 'update_timestamp_ms': 1762652841386, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P6D6LB352XNF2GB6FND34XKL', 'name': 'projects/ext-datasets/operations/P6D6LB352XNF2GB6FND34XKL'}\n","Grid 27\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762652847414, 'update_timestamp_ms': 1762652847414, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XU2OBEAFI2SP46DPZFU2Q724', 'name': 'projects/ext-datasets/operations/XU2OBEAFI2SP46DPZFU2Q724'}\n","Grid 28\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762652853805, 'update_timestamp_ms': 1762652853805, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R26FGY4WBDEUAU5ISWRZ6XQH', 'name': 'projects/ext-datasets/operations/R26FGY4WBDEUAU5ISWRZ6XQH'}\n","Grid 29\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762652861699, 'update_timestamp_ms': 1762652861699, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N33BZBXVFCNZQAF6T2NMZDY7', 'name': 'projects/ext-datasets/operations/N33BZBXVFCNZQAF6T2NMZDY7'}\n","3315.372728586197\n","Year 2017, District 17: Hugli, grids: 55\n","Grid 0\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762652873437, 'update_timestamp_ms': 1762652873437, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '67WLQUZFYL335CGJ7LLBYJLI', 'name': 'projects/ext-datasets/operations/67WLQUZFYL335CGJ7LLBYJLI'}\n","Grid 1\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762652879133, 'update_timestamp_ms': 1762652879133, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HW36MCMET6I4Y5UNARSAIH3V', 'name': 'projects/ext-datasets/operations/HW36MCMET6I4Y5UNARSAIH3V'}\n","Grid 2\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762652886436, 'update_timestamp_ms': 1762652886436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IWZBIZESVKRK7M56GH3YEWY6', 'name': 'projects/ext-datasets/operations/IWZBIZESVKRK7M56GH3YEWY6'}\n","Grid 3\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762652894869, 'update_timestamp_ms': 1762652894869, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLIX56WRXKWEHW44N55XWRKV', 'name': 'projects/ext-datasets/operations/JLIX56WRXKWEHW44N55XWRKV'}\n","Grid 4\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762652902789, 'update_timestamp_ms': 1762652902789, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MM5SISFPENEPZOVVAFMJUVU5', 'name': 'projects/ext-datasets/operations/MM5SISFPENEPZOVVAFMJUVU5'}\n","Grid 5\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762652910673, 'update_timestamp_ms': 1762652910673, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KQIK775U7IYSBN5LZJYJD5IS', 'name': 'projects/ext-datasets/operations/KQIK775U7IYSBN5LZJYJD5IS'}\n","Grid 6\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762652917226, 'update_timestamp_ms': 1762652917226, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCVEK3WW2CGFFIV7FKQHXJNN', 'name': 'projects/ext-datasets/operations/DCVEK3WW2CGFFIV7FKQHXJNN'}\n","Grid 7\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762652924007, 'update_timestamp_ms': 1762652924007, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YEYMUF2WW3TGZHOQRWRAFT54', 'name': 'projects/ext-datasets/operations/YEYMUF2WW3TGZHOQRWRAFT54'}\n","Grid 8\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762652931314, 'update_timestamp_ms': 1762652931314, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BDZGPIYOXGCCFOWADOVDSNHC', 'name': 'projects/ext-datasets/operations/BDZGPIYOXGCCFOWADOVDSNHC'}\n","Grid 9\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762652937745, 'update_timestamp_ms': 1762652937745, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WUG6PNYVFDMWPJSYSVORFPFD', 'name': 'projects/ext-datasets/operations/WUG6PNYVFDMWPJSYSVORFPFD'}\n","Grid 10\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762652945240, 'update_timestamp_ms': 1762652945240, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7MHCLX37JCBBJX7QP2MRKO2X', 'name': 'projects/ext-datasets/operations/7MHCLX37JCBBJX7QP2MRKO2X'}\n","Grid 11\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762652952393, 'update_timestamp_ms': 1762652952393, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RCXTUBPSVBBZTZDUILO7SCOE', 'name': 'projects/ext-datasets/operations/RCXTUBPSVBBZTZDUILO7SCOE'}\n","Grid 12\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762652959856, 'update_timestamp_ms': 1762652959856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UU4US5LIPZLFY3HCI3XRKRSG', 'name': 'projects/ext-datasets/operations/UU4US5LIPZLFY3HCI3XRKRSG'}\n","Grid 13\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762652967985, 'update_timestamp_ms': 1762652967985, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CEHVDOTQNQOWS7Z36OTLHHIB', 'name': 'projects/ext-datasets/operations/CEHVDOTQNQOWS7Z36OTLHHIB'}\n","Grid 14\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762652975340, 'update_timestamp_ms': 1762652975340, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ND7ZK45V2GPRHW572HNNOUNP', 'name': 'projects/ext-datasets/operations/ND7ZK45V2GPRHW572HNNOUNP'}\n","Grid 15\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762652983473, 'update_timestamp_ms': 1762652983473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N4DYL3R2WRHR6VK7LGYGAHAE', 'name': 'projects/ext-datasets/operations/N4DYL3R2WRHR6VK7LGYGAHAE'}\n","Grid 16\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762652990247, 'update_timestamp_ms': 1762652990247, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ILZGJKW7WCP6GQPD46H65GKL', 'name': 'projects/ext-datasets/operations/ILZGJKW7WCP6GQPD46H65GKL'}\n","Grid 17\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762652996402, 'update_timestamp_ms': 1762652996402, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S3O4QNBG6YIUR7IVRGMUA2UI', 'name': 'projects/ext-datasets/operations/S3O4QNBG6YIUR7IVRGMUA2UI'}\n","Grid 18\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762653004523, 'update_timestamp_ms': 1762653004523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7WEN5MKBMTCYRO73BRTBNMLD', 'name': 'projects/ext-datasets/operations/7WEN5MKBMTCYRO73BRTBNMLD'}\n","Grid 19\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762653010473, 'update_timestamp_ms': 1762653010473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OFXUFGKTKWLLC2U3ACD4V4PE', 'name': 'projects/ext-datasets/operations/OFXUFGKTKWLLC2U3ACD4V4PE'}\n","Grid 20\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762653019182, 'update_timestamp_ms': 1762653019182, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VUAUUFFV2SAVVOEXQLALAWXR', 'name': 'projects/ext-datasets/operations/VUAUUFFV2SAVVOEXQLALAWXR'}\n","Grid 21\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762653024959, 'update_timestamp_ms': 1762653024959, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IH2FDQWZWDCAGW6JRZTZP7QI', 'name': 'projects/ext-datasets/operations/IH2FDQWZWDCAGW6JRZTZP7QI'}\n","Grid 22\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762653032654, 'update_timestamp_ms': 1762653032654, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HE5ZQHMEEPRCZZ2QCXUN752R', 'name': 'projects/ext-datasets/operations/HE5ZQHMEEPRCZZ2QCXUN752R'}\n","Grid 23\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762653039615, 'update_timestamp_ms': 1762653039615, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHSIU2LEF6WYMG6VW3ZCDMIX', 'name': 'projects/ext-datasets/operations/YHSIU2LEF6WYMG6VW3ZCDMIX'}\n","Grid 24\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762653046808, 'update_timestamp_ms': 1762653046808, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KVZWQHJVKSAWJ3AHZMNXIJOV', 'name': 'projects/ext-datasets/operations/KVZWQHJVKSAWJ3AHZMNXIJOV'}\n","Grid 25\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762653052031, 'update_timestamp_ms': 1762653052031, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SVCS6CUVAMGD3BIXWJ2O4DFP', 'name': 'projects/ext-datasets/operations/SVCS6CUVAMGD3BIXWJ2O4DFP'}\n","Grid 26\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762653060075, 'update_timestamp_ms': 1762653060075, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZHOWVC7Q2MNYEQWLZBH7RWTQ', 'name': 'projects/ext-datasets/operations/ZHOWVC7Q2MNYEQWLZBH7RWTQ'}\n","Grid 27\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762653068196, 'update_timestamp_ms': 1762653068196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LVE2GEXSXRTJGXVWGNFWRXBZ', 'name': 'projects/ext-datasets/operations/LVE2GEXSXRTJGXVWGNFWRXBZ'}\n","Grid 28\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762653075759, 'update_timestamp_ms': 1762653075759, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PQUFVZ6HJLZ3KKTMJVX7MYEI', 'name': 'projects/ext-datasets/operations/PQUFVZ6HJLZ3KKTMJVX7MYEI'}\n","Grid 29\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762653083680, 'update_timestamp_ms': 1762653083680, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MWXGACU43LGFK4XXLAPTLJXX', 'name': 'projects/ext-datasets/operations/MWXGACU43LGFK4XXLAPTLJXX'}\n","Grid 30\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762653087724, 'update_timestamp_ms': 1762653087724, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FYBTXGKGIUVM2BP3FFGZZCNO', 'name': 'projects/ext-datasets/operations/FYBTXGKGIUVM2BP3FFGZZCNO'}\n","Grid 31\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762653094613, 'update_timestamp_ms': 1762653094613, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IWA6Q2SLUATTOPA5Z3QCXMNP', 'name': 'projects/ext-datasets/operations/IWA6Q2SLUATTOPA5Z3QCXMNP'}\n","Grid 32\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762653098875, 'update_timestamp_ms': 1762653098875, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZAH4CPIQF53TIOOR25RQY3UE', 'name': 'projects/ext-datasets/operations/ZAH4CPIQF53TIOOR25RQY3UE'}\n","Grid 33\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762653106035, 'update_timestamp_ms': 1762653106035, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EU4I3HM7SH64GU5IVPGJQBFM', 'name': 'projects/ext-datasets/operations/EU4I3HM7SH64GU5IVPGJQBFM'}\n","Grid 34\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762653113073, 'update_timestamp_ms': 1762653113073, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PWUBHH3Q7PPTLFBFLKRYSYUC', 'name': 'projects/ext-datasets/operations/PWUBHH3Q7PPTLFBFLKRYSYUC'}\n","Grid 35\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762653120645, 'update_timestamp_ms': 1762653120645, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H7T2JZOKUCFFV2NW2RIJ56II', 'name': 'projects/ext-datasets/operations/H7T2JZOKUCFFV2NW2RIJ56II'}\n","Grid 36\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762653126866, 'update_timestamp_ms': 1762653126866, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VNCXOPJMS62XSDOAGVHP4O73', 'name': 'projects/ext-datasets/operations/VNCXOPJMS62XSDOAGVHP4O73'}\n","Grid 37\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762653133620, 'update_timestamp_ms': 1762653133620, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UDONOS7G2HIGSIVAC2GP4ME7', 'name': 'projects/ext-datasets/operations/UDONOS7G2HIGSIVAC2GP4ME7'}\n","Grid 38\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762653138488, 'update_timestamp_ms': 1762653138488, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G5ELJ4O53QEHVQY2Y7WNKXNM', 'name': 'projects/ext-datasets/operations/G5ELJ4O53QEHVQY2Y7WNKXNM'}\n","Grid 39\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762653144956, 'update_timestamp_ms': 1762653144956, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V665E4OPIGTCWB5FO4K43HNC', 'name': 'projects/ext-datasets/operations/V665E4OPIGTCWB5FO4K43HNC'}\n","Grid 40\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762653151869, 'update_timestamp_ms': 1762653151869, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ATIMLT6LUYHI6PNG4EVNNSNG', 'name': 'projects/ext-datasets/operations/ATIMLT6LUYHI6PNG4EVNNSNG'}\n","Grid 41\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762653159135, 'update_timestamp_ms': 1762653159135, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PFM3F32LPGML42REWDXLJ5YQ', 'name': 'projects/ext-datasets/operations/PFM3F32LPGML42REWDXLJ5YQ'}\n","Grid 42\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762653163523, 'update_timestamp_ms': 1762653163523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OOX27I5RUBHMDS3ZKCZKDHJM', 'name': 'projects/ext-datasets/operations/OOX27I5RUBHMDS3ZKCZKDHJM'}\n","Grid 43\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762653167557, 'update_timestamp_ms': 1762653167557, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LD4RYM7I2ZWAREDALCSB5SVH', 'name': 'projects/ext-datasets/operations/LD4RYM7I2ZWAREDALCSB5SVH'}\n","Grid 44\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762653171076, 'update_timestamp_ms': 1762653171076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UBAJS5UUTC7LA7XQWDPYGEDV', 'name': 'projects/ext-datasets/operations/UBAJS5UUTC7LA7XQWDPYGEDV'}\n","Grid 45\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762653177390, 'update_timestamp_ms': 1762653177390, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BAXAKZQKWVWRIMXKNU5NGIGI', 'name': 'projects/ext-datasets/operations/BAXAKZQKWVWRIMXKNU5NGIGI'}\n","Grid 46\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762653187101, 'update_timestamp_ms': 1762653187101, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62JU7POKQPOCQ6ECPZBRZOJK', 'name': 'projects/ext-datasets/operations/62JU7POKQPOCQ6ECPZBRZOJK'}\n","Grid 47\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762653195208, 'update_timestamp_ms': 1762653195208, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BBM5LULHPY57D26QENGBFZEA', 'name': 'projects/ext-datasets/operations/BBM5LULHPY57D26QENGBFZEA'}\n","Grid 48\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762653202094, 'update_timestamp_ms': 1762653202094, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OTJUT7NPE4XH3B7X227CQC2E', 'name': 'projects/ext-datasets/operations/OTJUT7NPE4XH3B7X227CQC2E'}\n","Grid 49\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762653207724, 'update_timestamp_ms': 1762653207724, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EJCTVJWVA35JNT7WY7MS2PEZ', 'name': 'projects/ext-datasets/operations/EJCTVJWVA35JNT7WY7MS2PEZ'}\n","Grid 50\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762653215082, 'update_timestamp_ms': 1762653215082, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5Q476RLEGWQK22ZGZJVBQNIJ', 'name': 'projects/ext-datasets/operations/5Q476RLEGWQK22ZGZJVBQNIJ'}\n","Grid 51\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762653222507, 'update_timestamp_ms': 1762653222507, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FEX2WGIRLJCE7IBH74CDGLSO', 'name': 'projects/ext-datasets/operations/FEX2WGIRLJCE7IBH74CDGLSO'}\n","Grid 52\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762653231333, 'update_timestamp_ms': 1762653231333, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AVYNP6SEDV26DVLT6JEPVBQF', 'name': 'projects/ext-datasets/operations/AVYNP6SEDV26DVLT6JEPVBQF'}\n","Grid 53\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762653239636, 'update_timestamp_ms': 1762653239636, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZEQM4XXGVBBLIRHWMUBSV6Q5', 'name': 'projects/ext-datasets/operations/ZEQM4XXGVBBLIRHWMUBSV6Q5'}\n","Grid 54\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762653243926, 'update_timestamp_ms': 1762653243926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHQUJTEO4DJPKTFAOIR4VWXA', 'name': 'projects/ext-datasets/operations/YHQUJTEO4DJPKTFAOIR4VWXA'}\n","3697.579036951065\n","Year 2017, District 18: Kolkata, grids: 6\n","Grid 0\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762653255739, 'update_timestamp_ms': 1762653255739, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UXUXC4D6QFYHE4VSN3ZUDSNP', 'name': 'projects/ext-datasets/operations/UXUXC4D6QFYHE4VSN3ZUDSNP'}\n","Grid 1\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762653263107, 'update_timestamp_ms': 1762653263107, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X4ISR3KUCH25N42LTY2QEIMB', 'name': 'projects/ext-datasets/operations/X4ISR3KUCH25N42LTY2QEIMB'}\n","Grid 2\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762653270940, 'update_timestamp_ms': 1762653270940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JIXZH7CTRXOXPECWCUEA25CB', 'name': 'projects/ext-datasets/operations/JIXZH7CTRXOXPECWCUEA25CB'}\n","Grid 3\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762653278573, 'update_timestamp_ms': 1762653278573, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RIHCWKL5W7PAS2F5ZERX2FLQ', 'name': 'projects/ext-datasets/operations/RIHCWKL5W7PAS2F5ZERX2FLQ'}\n","Grid 4\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762653285117, 'update_timestamp_ms': 1762653285117, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2MO4V3JQ5U5ZKUVWZWKVOOLC', 'name': 'projects/ext-datasets/operations/2MO4V3JQ5U5ZKUVWZWKVOOLC'}\n","Grid 5\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762653292659, 'update_timestamp_ms': 1762653292659, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MEWVKX5M5UWFLXBD4N3KSMUS', 'name': 'projects/ext-datasets/operations/MEWVKX5M5UWFLXBD4N3KSMUS'}\n","3746.3626384735107\n","Year 2017, District 19: Maldah, grids: 61\n","Grid 0\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762653307026, 'update_timestamp_ms': 1762653307026, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QAPIPPIW66JXE4SHXRYGGATA', 'name': 'projects/ext-datasets/operations/QAPIPPIW66JXE4SHXRYGGATA'}\n","Grid 1\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762653314549, 'update_timestamp_ms': 1762653314549, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'II3RFO4GMMJQ7VVIITOZTRQ6', 'name': 'projects/ext-datasets/operations/II3RFO4GMMJQ7VVIITOZTRQ6'}\n","Grid 2\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762653322843, 'update_timestamp_ms': 1762653322843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GQPNIDVKNVFTOL76HN3PWZA5', 'name': 'projects/ext-datasets/operations/GQPNIDVKNVFTOL76HN3PWZA5'}\n","Grid 3\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762653330638, 'update_timestamp_ms': 1762653330638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MQG4FRBMMJL5FE6AU3Y3M35W', 'name': 'projects/ext-datasets/operations/MQG4FRBMMJL5FE6AU3Y3M35W'}\n","Grid 4\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762653337061, 'update_timestamp_ms': 1762653337061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RBIFNYSNTWMQIVHH7LF4RU5H', 'name': 'projects/ext-datasets/operations/RBIFNYSNTWMQIVHH7LF4RU5H'}\n","Grid 5\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762653344098, 'update_timestamp_ms': 1762653344098, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLYW2F2V5EDDC4WTNIZ7JC6Q', 'name': 'projects/ext-datasets/operations/XLYW2F2V5EDDC4WTNIZ7JC6Q'}\n","Grid 6\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762653352420, 'update_timestamp_ms': 1762653352420, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EUQV5UNADPFXYD65CQ64UN56', 'name': 'projects/ext-datasets/operations/EUQV5UNADPFXYD65CQ64UN56'}\n","Grid 7\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762653360453, 'update_timestamp_ms': 1762653360453, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CYJQMBWOLJUOWMBIX3IGGO4V', 'name': 'projects/ext-datasets/operations/CYJQMBWOLJUOWMBIX3IGGO4V'}\n","Grid 8\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762653369302, 'update_timestamp_ms': 1762653369302, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJXY66HWTEPRUWXHBVHCKHSU', 'name': 'projects/ext-datasets/operations/HJXY66HWTEPRUWXHBVHCKHSU'}\n","Grid 9\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762653376196, 'update_timestamp_ms': 1762653376196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KRCQLFABFHAXYCA27UAOMGQ3', 'name': 'projects/ext-datasets/operations/KRCQLFABFHAXYCA27UAOMGQ3'}\n","Grid 10\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762653383697, 'update_timestamp_ms': 1762653383697, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3FDGAB5FBCMXHBHYWDDZI62T', 'name': 'projects/ext-datasets/operations/3FDGAB5FBCMXHBHYWDDZI62T'}\n","Grid 11\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762653391609, 'update_timestamp_ms': 1762653391609, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PTT5B6F4TFKMLHHGA5KGVSHQ', 'name': 'projects/ext-datasets/operations/PTT5B6F4TFKMLHHGA5KGVSHQ'}\n","Grid 12\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762653401352, 'update_timestamp_ms': 1762653401352, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FWLOBKFWETXJ27PFYAYMBUI5', 'name': 'projects/ext-datasets/operations/FWLOBKFWETXJ27PFYAYMBUI5'}\n","Grid 13\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762653419468, 'update_timestamp_ms': 1762653419468, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '44LAF56HFWQPWWXW5CWUJ7IP', 'name': 'projects/ext-datasets/operations/44LAF56HFWQPWWXW5CWUJ7IP'}\n","Grid 14\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762653427261, 'update_timestamp_ms': 1762653427261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SZR5YDJO2YUZK3W55ACIBWTW', 'name': 'projects/ext-datasets/operations/SZR5YDJO2YUZK3W55ACIBWTW'}\n","Grid 15\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762653435984, 'update_timestamp_ms': 1762653435984, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DL5MAMXB4L3LLQXQ3HIHD6AI', 'name': 'projects/ext-datasets/operations/DL5MAMXB4L3LLQXQ3HIHD6AI'}\n","Grid 16\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762653443583, 'update_timestamp_ms': 1762653443583, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W4UMT7X5ZJ5L7OQXPK6FB4SD', 'name': 'projects/ext-datasets/operations/W4UMT7X5ZJ5L7OQXPK6FB4SD'}\n","Grid 17\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762653451043, 'update_timestamp_ms': 1762653451043, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DTPOZN2ZC5H65D7RYPIXZWH5', 'name': 'projects/ext-datasets/operations/DTPOZN2ZC5H65D7RYPIXZWH5'}\n","Grid 18\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762653458890, 'update_timestamp_ms': 1762653458890, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FVB5AVADD3JEATFLCFBBRKBT', 'name': 'projects/ext-datasets/operations/FVB5AVADD3JEATFLCFBBRKBT'}\n","Grid 19\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762653466409, 'update_timestamp_ms': 1762653466409, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WHDQ3GKZGMJS5MNJ6Y3H2JO3', 'name': 'projects/ext-datasets/operations/WHDQ3GKZGMJS5MNJ6Y3H2JO3'}\n","Grid 20\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762653474177, 'update_timestamp_ms': 1762653474177, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NXGV26SJ2FZHQVGAL3QG5PDJ', 'name': 'projects/ext-datasets/operations/NXGV26SJ2FZHQVGAL3QG5PDJ'}\n","Grid 21\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762653481511, 'update_timestamp_ms': 1762653481511, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QC4ETEY5YN4JWEJEZQH4NFLS', 'name': 'projects/ext-datasets/operations/QC4ETEY5YN4JWEJEZQH4NFLS'}\n","Grid 22\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762653488560, 'update_timestamp_ms': 1762653488560, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XKGFAZTVNOV7EY4OJRVIDZZV', 'name': 'projects/ext-datasets/operations/XKGFAZTVNOV7EY4OJRVIDZZV'}\n","Grid 23\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762653494949, 'update_timestamp_ms': 1762653494949, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JBWZZDVCJHEQDKTYUGPVH24N', 'name': 'projects/ext-datasets/operations/JBWZZDVCJHEQDKTYUGPVH24N'}\n","Grid 24\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762653501686, 'update_timestamp_ms': 1762653501686, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FZ27X6HFBSZWXMWLZ7W6PZSH', 'name': 'projects/ext-datasets/operations/FZ27X6HFBSZWXMWLZ7W6PZSH'}\n","Grid 25\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762653509420, 'update_timestamp_ms': 1762653509420, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QAMZUMFTIL6QO2CGJVMRXEDX', 'name': 'projects/ext-datasets/operations/QAMZUMFTIL6QO2CGJVMRXEDX'}\n","Grid 26\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762653515776, 'update_timestamp_ms': 1762653515776, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MAUX2TR736ZX4DRGTLLNIP7Y', 'name': 'projects/ext-datasets/operations/MAUX2TR736ZX4DRGTLLNIP7Y'}\n","Grid 27\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762653522065, 'update_timestamp_ms': 1762653522065, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QDPDGUGHELDOTRPE5CQP4XZN', 'name': 'projects/ext-datasets/operations/QDPDGUGHELDOTRPE5CQP4XZN'}\n","Grid 28\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762653530057, 'update_timestamp_ms': 1762653530057, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WZSXJF2ZQZUU4RODFFUFMM75', 'name': 'projects/ext-datasets/operations/WZSXJF2ZQZUU4RODFFUFMM75'}\n","Grid 29\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762653537639, 'update_timestamp_ms': 1762653537639, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZPNMLXGNND5IJKGKCJD6TOP', 'name': 'projects/ext-datasets/operations/KZPNMLXGNND5IJKGKCJD6TOP'}\n","Grid 30\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762653544926, 'update_timestamp_ms': 1762653544926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7DC5KUBNX2FF3SMR72W34ZHG', 'name': 'projects/ext-datasets/operations/7DC5KUBNX2FF3SMR72W34ZHG'}\n","Grid 31\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762653552527, 'update_timestamp_ms': 1762653552527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BLD33OJTOWGHCXTGVZXZIO2P', 'name': 'projects/ext-datasets/operations/BLD33OJTOWGHCXTGVZXZIO2P'}\n","Grid 32\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762653560390, 'update_timestamp_ms': 1762653560390, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B252XMEYH5O2ZA7L4SJWHFPX', 'name': 'projects/ext-datasets/operations/B252XMEYH5O2ZA7L4SJWHFPX'}\n","Grid 33\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762653563974, 'update_timestamp_ms': 1762653563974, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L377EDJ5RJO5JIGX32CUBHWC', 'name': 'projects/ext-datasets/operations/L377EDJ5RJO5JIGX32CUBHWC'}\n","Grid 34\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762653571157, 'update_timestamp_ms': 1762653571157, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VXAL2ERHA3YZGYJAJI6FJM4L', 'name': 'projects/ext-datasets/operations/VXAL2ERHA3YZGYJAJI6FJM4L'}\n","Grid 35\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762653579961, 'update_timestamp_ms': 1762653579961, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMP3FFN72TNUTT4J6Q7SRAPN', 'name': 'projects/ext-datasets/operations/BMP3FFN72TNUTT4J6Q7SRAPN'}\n","Grid 36\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762653586557, 'update_timestamp_ms': 1762653586557, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MR2ZM6CM5POJM2LCSE7NGTSY', 'name': 'projects/ext-datasets/operations/MR2ZM6CM5POJM2LCSE7NGTSY'}\n","Grid 37\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762653595043, 'update_timestamp_ms': 1762653595043, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZEAJ3NM2CDE5MNBAV5RXP7X', 'name': 'projects/ext-datasets/operations/XZEAJ3NM2CDE5MNBAV5RXP7X'}\n","Grid 38\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762653603189, 'update_timestamp_ms': 1762653603189, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SVQGTVZYFMJX56OO4LUGU6FZ', 'name': 'projects/ext-datasets/operations/SVQGTVZYFMJX56OO4LUGU6FZ'}\n","Grid 39\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762653610089, 'update_timestamp_ms': 1762653610089, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6R77UCAJUFCC3FTNEIWOV5KU', 'name': 'projects/ext-datasets/operations/6R77UCAJUFCC3FTNEIWOV5KU'}\n","Grid 40\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762653616912, 'update_timestamp_ms': 1762653616912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AHGO4Z6KJD2MADBE4HJRZXLO', 'name': 'projects/ext-datasets/operations/AHGO4Z6KJD2MADBE4HJRZXLO'}\n","Grid 41\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762653624910, 'update_timestamp_ms': 1762653624910, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4L6DLRUNVBFADPTVT4RWRAY3', 'name': 'projects/ext-datasets/operations/4L6DLRUNVBFADPTVT4RWRAY3'}\n","Grid 42\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762653631524, 'update_timestamp_ms': 1762653631524, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGWJG63I2NHNLEHSDS6BNXA6', 'name': 'projects/ext-datasets/operations/QGWJG63I2NHNLEHSDS6BNXA6'}\n","Grid 43\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762653639518, 'update_timestamp_ms': 1762653639518, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XE3NBLV25Z2I4Q3SQCP4MGAH', 'name': 'projects/ext-datasets/operations/XE3NBLV25Z2I4Q3SQCP4MGAH'}\n","Grid 44\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762653646429, 'update_timestamp_ms': 1762653646429, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HRAXBS7AIGEPBX4HP3ROTLVN', 'name': 'projects/ext-datasets/operations/HRAXBS7AIGEPBX4HP3ROTLVN'}\n","Grid 45\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762653654205, 'update_timestamp_ms': 1762653654205, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5NA5YFUVXGDNTNRCEZG7DIK2', 'name': 'projects/ext-datasets/operations/5NA5YFUVXGDNTNRCEZG7DIK2'}\n","Grid 46\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762653660439, 'update_timestamp_ms': 1762653660439, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CWFOGQOWCFIAKBBNAIVK5WM5', 'name': 'projects/ext-datasets/operations/CWFOGQOWCFIAKBBNAIVK5WM5'}\n","Grid 47\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762653669479, 'update_timestamp_ms': 1762653669479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2H22QVZXDN3LNHSUAC4UJCQ4', 'name': 'projects/ext-datasets/operations/2H22QVZXDN3LNHSUAC4UJCQ4'}\n","Grid 48\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762653673328, 'update_timestamp_ms': 1762653673328, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EMSW7CJWFFWQ6APGYTPECA2P', 'name': 'projects/ext-datasets/operations/EMSW7CJWFFWQ6APGYTPECA2P'}\n","Grid 49\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762653680761, 'update_timestamp_ms': 1762653680761, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KB6GXPQTM66LGRZUEVNBYHQY', 'name': 'projects/ext-datasets/operations/KB6GXPQTM66LGRZUEVNBYHQY'}\n","Grid 50\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762653687933, 'update_timestamp_ms': 1762653687933, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BIYFLVYMNTLUEBSHJVGBNJ7M', 'name': 'projects/ext-datasets/operations/BIYFLVYMNTLUEBSHJVGBNJ7M'}\n","Grid 51\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762653696412, 'update_timestamp_ms': 1762653696412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OHND56M62MWDUBE7JZMQFMHZ', 'name': 'projects/ext-datasets/operations/OHND56M62MWDUBE7JZMQFMHZ'}\n","Grid 52\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762653705129, 'update_timestamp_ms': 1762653705129, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AEPLV4U3722KMODMFNVOHHK6', 'name': 'projects/ext-datasets/operations/AEPLV4U3722KMODMFNVOHHK6'}\n","Grid 53\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762653713348, 'update_timestamp_ms': 1762653713348, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPCO5ZYVPYU3U4NPRNSBPLJL', 'name': 'projects/ext-datasets/operations/PPCO5ZYVPYU3U4NPRNSBPLJL'}\n","Grid 54\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762653721065, 'update_timestamp_ms': 1762653721065, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DGHL73DRSIR2MVY4CXWZK4YO', 'name': 'projects/ext-datasets/operations/DGHL73DRSIR2MVY4CXWZK4YO'}\n","Grid 55\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762653728737, 'update_timestamp_ms': 1762653728737, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DUECP6FOWWNQEQIQIFVN33DN', 'name': 'projects/ext-datasets/operations/DUECP6FOWWNQEQIQIFVN33DN'}\n","Grid 56\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762653736532, 'update_timestamp_ms': 1762653736532, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4I6S5YXZZT7EFL2DFDQ5AGF4', 'name': 'projects/ext-datasets/operations/4I6S5YXZZT7EFL2DFDQ5AGF4'}\n","Grid 57\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762653744879, 'update_timestamp_ms': 1762653744879, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TIQWF4ZWIORMW7BTWRGXPOBA', 'name': 'projects/ext-datasets/operations/TIQWF4ZWIORMW7BTWRGXPOBA'}\n","Grid 58\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762653750799, 'update_timestamp_ms': 1762653750799, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GNY6NKPSF53UNE6JEJEFLKQE', 'name': 'projects/ext-datasets/operations/GNY6NKPSF53UNE6JEJEFLKQE'}\n","Grid 59\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762653757995, 'update_timestamp_ms': 1762653757995, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BRH4LJTFLB5NIFYX23Z2D24K', 'name': 'projects/ext-datasets/operations/BRH4LJTFLB5NIFYX23Z2D24K'}\n","Grid 60\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762653763814, 'update_timestamp_ms': 1762653763814, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCUJBEOPU66DWIDE63GS7TFK', 'name': 'projects/ext-datasets/operations/DCUJBEOPU66DWIDE63GS7TFK'}\n","4217.510969877243\n","Year 2017, District 20: Murshidabad, grids: 87\n","Grid 0\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762653777841, 'update_timestamp_ms': 1762653777841, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NX2VXZOCFQGCIMGOUVTSJTF4', 'name': 'projects/ext-datasets/operations/NX2VXZOCFQGCIMGOUVTSJTF4'}\n","Grid 1\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762653784734, 'update_timestamp_ms': 1762653784734, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZUAMH7MMA6G4MHBCMJKVFJ32', 'name': 'projects/ext-datasets/operations/ZUAMH7MMA6G4MHBCMJKVFJ32'}\n","Grid 2\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762653789468, 'update_timestamp_ms': 1762653789468, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EWKFB3TZCYETDAPV6JJOTKSO', 'name': 'projects/ext-datasets/operations/EWKFB3TZCYETDAPV6JJOTKSO'}\n","Grid 3\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762653797795, 'update_timestamp_ms': 1762653797795, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RNSUJECPKWVXFW5ZIMHJC3B7', 'name': 'projects/ext-datasets/operations/RNSUJECPKWVXFW5ZIMHJC3B7'}\n","Grid 4\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762653805593, 'update_timestamp_ms': 1762653805593, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YRKBCIIRDJNL2AA735MXNV3J', 'name': 'projects/ext-datasets/operations/YRKBCIIRDJNL2AA735MXNV3J'}\n","Grid 5\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762653811334, 'update_timestamp_ms': 1762653811334, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H4KZJ6RHG3I73I2VG3PGQP5Y', 'name': 'projects/ext-datasets/operations/H4KZJ6RHG3I73I2VG3PGQP5Y'}\n","Grid 6\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762653818350, 'update_timestamp_ms': 1762653818350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2AWVITKPMQ4CKI7PX7ISXONF', 'name': 'projects/ext-datasets/operations/2AWVITKPMQ4CKI7PX7ISXONF'}\n","Grid 7\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762653822840, 'update_timestamp_ms': 1762653822840, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BZ5VSADHRL67CKQ4NLWJTT2Q', 'name': 'projects/ext-datasets/operations/BZ5VSADHRL67CKQ4NLWJTT2Q'}\n","Grid 8\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762653830584, 'update_timestamp_ms': 1762653830584, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HFHFUCAEXLHFHUIP6YE754JP', 'name': 'projects/ext-datasets/operations/HFHFUCAEXLHFHUIP6YE754JP'}\n","Grid 9\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762653838966, 'update_timestamp_ms': 1762653838966, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6Y2G4VKF3KTUDO3YYLYMLEID', 'name': 'projects/ext-datasets/operations/6Y2G4VKF3KTUDO3YYLYMLEID'}\n","Grid 10\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762653846040, 'update_timestamp_ms': 1762653846040, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6NSOWXNHMOWNSV6NCTWXP5CB', 'name': 'projects/ext-datasets/operations/6NSOWXNHMOWNSV6NCTWXP5CB'}\n","Grid 11\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762653852887, 'update_timestamp_ms': 1762653852887, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5FDF4VYLPS5WXUJVDZFCKBYZ', 'name': 'projects/ext-datasets/operations/5FDF4VYLPS5WXUJVDZFCKBYZ'}\n","Grid 12\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762653860246, 'update_timestamp_ms': 1762653860246, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BF5RASHVLHR6PFDXNYEGFMCW', 'name': 'projects/ext-datasets/operations/BF5RASHVLHR6PFDXNYEGFMCW'}\n","Grid 13\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762653866589, 'update_timestamp_ms': 1762653866589, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E2CKWLS4IVVQ6MBWTFH3C3K4', 'name': 'projects/ext-datasets/operations/E2CKWLS4IVVQ6MBWTFH3C3K4'}\n","Grid 14\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762653873324, 'update_timestamp_ms': 1762653873324, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '46IQBJ7Q5WF4JA76HEXULARX', 'name': 'projects/ext-datasets/operations/46IQBJ7Q5WF4JA76HEXULARX'}\n","Grid 15\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762653881176, 'update_timestamp_ms': 1762653881176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NQJKDXO4OVFAK7MXPFEDZH6', 'name': 'projects/ext-datasets/operations/4NQJKDXO4OVFAK7MXPFEDZH6'}\n","Grid 16\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762653888440, 'update_timestamp_ms': 1762653888440, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QU43U6BDPA4N35BDT5NJJIXS', 'name': 'projects/ext-datasets/operations/QU43U6BDPA4N35BDT5NJJIXS'}\n","Grid 17\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762653895140, 'update_timestamp_ms': 1762653895140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XKLNC5QV3WSY4CFMWVAEMAIN', 'name': 'projects/ext-datasets/operations/XKLNC5QV3WSY4CFMWVAEMAIN'}\n","Grid 18\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762653904788, 'update_timestamp_ms': 1762653904788, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GBJHBSZ2S4ELOUHGGSIUDMG7', 'name': 'projects/ext-datasets/operations/GBJHBSZ2S4ELOUHGGSIUDMG7'}\n","Grid 19\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762653912798, 'update_timestamp_ms': 1762653912798, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KKB6K7JJR5NW33DZRDFX3YK4', 'name': 'projects/ext-datasets/operations/KKB6K7JJR5NW33DZRDFX3YK4'}\n","Grid 20\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762653919534, 'update_timestamp_ms': 1762653919534, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UJ4UD5WWKANSIE2AWNTGMU4Q', 'name': 'projects/ext-datasets/operations/UJ4UD5WWKANSIE2AWNTGMU4Q'}\n","Grid 21\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762653925878, 'update_timestamp_ms': 1762653925878, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ET77UW3Y3THD7NJM52CSYJNO', 'name': 'projects/ext-datasets/operations/ET77UW3Y3THD7NJM52CSYJNO'}\n","Grid 22\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762653932951, 'update_timestamp_ms': 1762653932951, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2MRIBK3DGIVLB7LEI3IDII5W', 'name': 'projects/ext-datasets/operations/2MRIBK3DGIVLB7LEI3IDII5W'}\n","Grid 23\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762653943326, 'update_timestamp_ms': 1762653943326, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RH42F7CGTX5HHIXR22M3DXW4', 'name': 'projects/ext-datasets/operations/RH42F7CGTX5HHIXR22M3DXW4'}\n","Grid 24\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762653949452, 'update_timestamp_ms': 1762653949452, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NBXQWQYYKWZO7REI3E2KFCY2', 'name': 'projects/ext-datasets/operations/NBXQWQYYKWZO7REI3E2KFCY2'}\n","Grid 25\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762653957880, 'update_timestamp_ms': 1762653957880, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3HLL5UWJBBN4SNVIXAVSB45J', 'name': 'projects/ext-datasets/operations/3HLL5UWJBBN4SNVIXAVSB45J'}\n","Grid 26\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762653964804, 'update_timestamp_ms': 1762653964804, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPPJZCYRFS74YB2KC72AAF4O', 'name': 'projects/ext-datasets/operations/PPPJZCYRFS74YB2KC72AAF4O'}\n","Grid 27\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762653971742, 'update_timestamp_ms': 1762653971742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KRIKZXHKRXA6A4RF3C5ZZMME', 'name': 'projects/ext-datasets/operations/KRIKZXHKRXA6A4RF3C5ZZMME'}\n","Grid 28\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762653979906, 'update_timestamp_ms': 1762653979906, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LBDOREIVQQ735JGAHEMFGRAU', 'name': 'projects/ext-datasets/operations/LBDOREIVQQ735JGAHEMFGRAU'}\n","Grid 29\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762653988463, 'update_timestamp_ms': 1762653988463, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SN6G6M2C55NZ352BVJAQ35A4', 'name': 'projects/ext-datasets/operations/SN6G6M2C55NZ352BVJAQ35A4'}\n","Grid 30\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762653996378, 'update_timestamp_ms': 1762653996378, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QA5MAZT6HTJH5RQPNADA4BUK', 'name': 'projects/ext-datasets/operations/QA5MAZT6HTJH5RQPNADA4BUK'}\n","Grid 31\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762654001857, 'update_timestamp_ms': 1762654001857, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZ6HHRDQMKOYQHIYLMQNBGKR', 'name': 'projects/ext-datasets/operations/AZ6HHRDQMKOYQHIYLMQNBGKR'}\n","Grid 32\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762654009445, 'update_timestamp_ms': 1762654009445, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BEQN5GMLUCYMRSUUHIPODHYB', 'name': 'projects/ext-datasets/operations/BEQN5GMLUCYMRSUUHIPODHYB'}\n","Grid 33\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762654016924, 'update_timestamp_ms': 1762654016924, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '44HVPDN62JQH7BUOFR2VQ6LN', 'name': 'projects/ext-datasets/operations/44HVPDN62JQH7BUOFR2VQ6LN'}\n","Grid 34\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762654023800, 'update_timestamp_ms': 1762654023800, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VWZG7RILSLQ4AJFQRK2S3WSS', 'name': 'projects/ext-datasets/operations/VWZG7RILSLQ4AJFQRK2S3WSS'}\n","Grid 35\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762654032471, 'update_timestamp_ms': 1762654032471, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KUBX4FOSYCWCTCI75R7OABNH', 'name': 'projects/ext-datasets/operations/KUBX4FOSYCWCTCI75R7OABNH'}\n","Grid 36\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762654040811, 'update_timestamp_ms': 1762654040811, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KB7BCNNPE6GIJW72DTNL4RZQ', 'name': 'projects/ext-datasets/operations/KB7BCNNPE6GIJW72DTNL4RZQ'}\n","Grid 37\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762654049211, 'update_timestamp_ms': 1762654049211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLPHSI6XVO56J7PKYQQWM2DS', 'name': 'projects/ext-datasets/operations/JLPHSI6XVO56J7PKYQQWM2DS'}\n","Grid 38\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762654057262, 'update_timestamp_ms': 1762654057262, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKLC4T64M27G46OKHYBTJPZE', 'name': 'projects/ext-datasets/operations/SKLC4T64M27G46OKHYBTJPZE'}\n","Grid 39\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762654066731, 'update_timestamp_ms': 1762654066731, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FFARSCE33IKE74VD5CXXY7KZ', 'name': 'projects/ext-datasets/operations/FFARSCE33IKE74VD5CXXY7KZ'}\n","Grid 40\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762654071677, 'update_timestamp_ms': 1762654071677, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BVJUW6FK3OAJLPY544MSEXKZ', 'name': 'projects/ext-datasets/operations/BVJUW6FK3OAJLPY544MSEXKZ'}\n","Grid 41\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762654076894, 'update_timestamp_ms': 1762654076894, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QLFPYEPBTWNWVMFHBKEB7VPJ', 'name': 'projects/ext-datasets/operations/QLFPYEPBTWNWVMFHBKEB7VPJ'}\n","Grid 42\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762654084155, 'update_timestamp_ms': 1762654084155, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z6HSU344JNLLONL4TAWFXB7E', 'name': 'projects/ext-datasets/operations/Z6HSU344JNLLONL4TAWFXB7E'}\n","Grid 43\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762654096054, 'update_timestamp_ms': 1762654096054, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4HJ6CZEXP2ZWMHBM57ER3RD7', 'name': 'projects/ext-datasets/operations/4HJ6CZEXP2ZWMHBM57ER3RD7'}\n","Grid 44\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762654103872, 'update_timestamp_ms': 1762654103872, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R6HZAE6XRQWNPOQUZTJXLBDY', 'name': 'projects/ext-datasets/operations/R6HZAE6XRQWNPOQUZTJXLBDY'}\n","Grid 45\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762654108933, 'update_timestamp_ms': 1762654108933, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SSPWCJS7V3HRF2HPA652YD4T', 'name': 'projects/ext-datasets/operations/SSPWCJS7V3HRF2HPA652YD4T'}\n","Grid 46\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762654116877, 'update_timestamp_ms': 1762654116877, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EG2PSYE6HTAWGVIJSPH6GVAH', 'name': 'projects/ext-datasets/operations/EG2PSYE6HTAWGVIJSPH6GVAH'}\n","Grid 47\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762654123631, 'update_timestamp_ms': 1762654123631, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VGRICQZE2AKI33AUXFTY7LJL', 'name': 'projects/ext-datasets/operations/VGRICQZE2AKI33AUXFTY7LJL'}\n","Grid 48\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762654133474, 'update_timestamp_ms': 1762654133474, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37MGLXZAP4AEDSGNHR4YTFPK', 'name': 'projects/ext-datasets/operations/37MGLXZAP4AEDSGNHR4YTFPK'}\n","Grid 49\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762654141171, 'update_timestamp_ms': 1762654141171, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XCWB546AUSPANV7JYRNXDRA6', 'name': 'projects/ext-datasets/operations/XCWB546AUSPANV7JYRNXDRA6'}\n","Grid 50\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762654145765, 'update_timestamp_ms': 1762654145765, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2GCUUDHIUD342CLSQHQCOOJE', 'name': 'projects/ext-datasets/operations/2GCUUDHIUD342CLSQHQCOOJE'}\n","Grid 51\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762654154438, 'update_timestamp_ms': 1762654154438, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZEKHWK5DQ62KX4SYDI76W3QV', 'name': 'projects/ext-datasets/operations/ZEKHWK5DQ62KX4SYDI76W3QV'}\n","Grid 52\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762654160861, 'update_timestamp_ms': 1762654160861, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4VAWOFVC5P5PRRD6VVTS3H73', 'name': 'projects/ext-datasets/operations/4VAWOFVC5P5PRRD6VVTS3H73'}\n","Grid 53\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762654168851, 'update_timestamp_ms': 1762654168851, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LOKEALXX57SWTCFETEBPNSIX', 'name': 'projects/ext-datasets/operations/LOKEALXX57SWTCFETEBPNSIX'}\n","Grid 54\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762654176454, 'update_timestamp_ms': 1762654176454, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OGYQ23YWNQI7R2KYVYJ7VL52', 'name': 'projects/ext-datasets/operations/OGYQ23YWNQI7R2KYVYJ7VL52'}\n","Grid 55\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762654183923, 'update_timestamp_ms': 1762654183923, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UYJ4Z7K25V4NX7VILLH2UD5Y', 'name': 'projects/ext-datasets/operations/UYJ4Z7K25V4NX7VILLH2UD5Y'}\n","Grid 56\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762654191408, 'update_timestamp_ms': 1762654191408, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JOCJYMG3UWIIVPI4A6OB37JJ', 'name': 'projects/ext-datasets/operations/JOCJYMG3UWIIVPI4A6OB37JJ'}\n","Grid 57\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762654194986, 'update_timestamp_ms': 1762654194986, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SWCFXQQCMDTVOKHGU2FJNTAQ', 'name': 'projects/ext-datasets/operations/SWCFXQQCMDTVOKHGU2FJNTAQ'}\n","Grid 58\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762654202860, 'update_timestamp_ms': 1762654202860, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UVZ5XDC26KR5UIYJPBZRJEHA', 'name': 'projects/ext-datasets/operations/UVZ5XDC26KR5UIYJPBZRJEHA'}\n","Grid 59\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762654206594, 'update_timestamp_ms': 1762654206594, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WVIMTAFGVPTPLMHC3CQBITNI', 'name': 'projects/ext-datasets/operations/WVIMTAFGVPTPLMHC3CQBITNI'}\n","Grid 60\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762654213512, 'update_timestamp_ms': 1762654213512, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YUVX5V4UVUIULAQRELZGWCCZ', 'name': 'projects/ext-datasets/operations/YUVX5V4UVUIULAQRELZGWCCZ'}\n","Grid 61\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762654221501, 'update_timestamp_ms': 1762654221501, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ININU7K5RYMHTU45UCPHJZIE', 'name': 'projects/ext-datasets/operations/ININU7K5RYMHTU45UCPHJZIE'}\n","Grid 62\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762654229227, 'update_timestamp_ms': 1762654229227, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4XB3RDH2RLZT4O3N6PFVNF6I', 'name': 'projects/ext-datasets/operations/4XB3RDH2RLZT4O3N6PFVNF6I'}\n","Grid 63\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762654234955, 'update_timestamp_ms': 1762654234955, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VXQYA4V7ULZKEIKMHPMXRGVE', 'name': 'projects/ext-datasets/operations/VXQYA4V7ULZKEIKMHPMXRGVE'}\n","Grid 64\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762654242660, 'update_timestamp_ms': 1762654242660, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TS33FE4ICXVIA25FBCHQ2QEB', 'name': 'projects/ext-datasets/operations/TS33FE4ICXVIA25FBCHQ2QEB'}\n","Grid 65\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762654251350, 'update_timestamp_ms': 1762654251350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QIESXKNY7CRZCN5QX34XK6N7', 'name': 'projects/ext-datasets/operations/QIESXKNY7CRZCN5QX34XK6N7'}\n","Grid 66\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762654259090, 'update_timestamp_ms': 1762654259090, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G2ZNNHIMPD2ZOL6UWBB2QSE4', 'name': 'projects/ext-datasets/operations/G2ZNNHIMPD2ZOL6UWBB2QSE4'}\n","Grid 67\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762654266099, 'update_timestamp_ms': 1762654266099, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZHT2OMTNF43VE7DM5WYCLB6K', 'name': 'projects/ext-datasets/operations/ZHT2OMTNF43VE7DM5WYCLB6K'}\n","Grid 68\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762654274490, 'update_timestamp_ms': 1762654274490, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LXH2NTPT4WO4334Q5K3G6REW', 'name': 'projects/ext-datasets/operations/LXH2NTPT4WO4334Q5K3G6REW'}\n","Grid 69\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762654281865, 'update_timestamp_ms': 1762654281865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IHJG6HO5JXBURXH6QIY52ZXB', 'name': 'projects/ext-datasets/operations/IHJG6HO5JXBURXH6QIY52ZXB'}\n","Grid 70\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762654288001, 'update_timestamp_ms': 1762654288001, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z64IXTFYHMT4YP2FTWTAF4BQ', 'name': 'projects/ext-datasets/operations/Z64IXTFYHMT4YP2FTWTAF4BQ'}\n","Grid 71\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762654295009, 'update_timestamp_ms': 1762654295009, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6U4HLKEPGFFPGTUECDNENYSI', 'name': 'projects/ext-datasets/operations/6U4HLKEPGFFPGTUECDNENYSI'}\n","Grid 72\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762654302138, 'update_timestamp_ms': 1762654302138, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IQDVX5WSXQ7E5WU6TWYRDJRI', 'name': 'projects/ext-datasets/operations/IQDVX5WSXQ7E5WU6TWYRDJRI'}\n","Grid 73\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762654309416, 'update_timestamp_ms': 1762654309416, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VLQQ6MSCOAGR7XOF7PJILBJY', 'name': 'projects/ext-datasets/operations/VLQQ6MSCOAGR7XOF7PJILBJY'}\n","Grid 74\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762654316819, 'update_timestamp_ms': 1762654316819, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYHSYAVWZCJPNH47ZHL4YJLJ', 'name': 'projects/ext-datasets/operations/ZYHSYAVWZCJPNH47ZHL4YJLJ'}\n","Grid 75\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762654324497, 'update_timestamp_ms': 1762654324497, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HHCUJR4DOHQTWZYVJC3VPQHB', 'name': 'projects/ext-datasets/operations/HHCUJR4DOHQTWZYVJC3VPQHB'}\n","Grid 76\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762654333435, 'update_timestamp_ms': 1762654333435, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2NXHHIZTAWMUIP2ONH2WKRNK', 'name': 'projects/ext-datasets/operations/2NXHHIZTAWMUIP2ONH2WKRNK'}\n","Grid 77\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762654340665, 'update_timestamp_ms': 1762654340665, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PA7N3M6BT5NFEOYVZTT2RF3E', 'name': 'projects/ext-datasets/operations/PA7N3M6BT5NFEOYVZTT2RF3E'}\n","Grid 78\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762654355061, 'update_timestamp_ms': 1762654355061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4LZYVKDAFXDBAZAZCMAD5HJS', 'name': 'projects/ext-datasets/operations/4LZYVKDAFXDBAZAZCMAD5HJS'}\n","Grid 79\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762654363095, 'update_timestamp_ms': 1762654363095, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BZ5UIKJ4MH62FMCDX4RDPB6N', 'name': 'projects/ext-datasets/operations/BZ5UIKJ4MH62FMCDX4RDPB6N'}\n","Grid 80\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762654367787, 'update_timestamp_ms': 1762654367787, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IZSLOUU4TXEVO65BL6F6A6TI', 'name': 'projects/ext-datasets/operations/IZSLOUU4TXEVO65BL6F6A6TI'}\n","Grid 81\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762654373562, 'update_timestamp_ms': 1762654373562, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7CLOGSI2U3AVSKGXPAAOUUYO', 'name': 'projects/ext-datasets/operations/7CLOGSI2U3AVSKGXPAAOUUYO'}\n","Grid 82\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762654380912, 'update_timestamp_ms': 1762654380912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LAIUHPT4O2BI27Z6XUYATFCI', 'name': 'projects/ext-datasets/operations/LAIUHPT4O2BI27Z6XUYATFCI'}\n","Grid 83\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762654386601, 'update_timestamp_ms': 1762654386601, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4A4QTYVS3JCDIL7WNKOCQOJC', 'name': 'projects/ext-datasets/operations/4A4QTYVS3JCDIL7WNKOCQOJC'}\n","Grid 84\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762654393177, 'update_timestamp_ms': 1762654393177, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AWH4WDKEPAPR2NUVUO55REA2', 'name': 'projects/ext-datasets/operations/AWH4WDKEPAPR2NUVUO55REA2'}\n","Grid 85\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762654399017, 'update_timestamp_ms': 1762654399017, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PTZJ333PZBVDKFMJ2CA2B27P', 'name': 'projects/ext-datasets/operations/PTZJ333PZBVDKFMJ2CA2B27P'}\n","Grid 86\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762654405087, 'update_timestamp_ms': 1762654405087, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NHJPNXCKQABNHNA7FTKI47K2', 'name': 'projects/ext-datasets/operations/NHJPNXCKQABNHNA7FTKI47K2'}\n","4858.730777263641\n","Year 2017, District 21: Nadia, grids: 72\n","Grid 0\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762654419792, 'update_timestamp_ms': 1762654419792, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P5PGIJ6O6ZOCYR5GPDTFUSGM', 'name': 'projects/ext-datasets/operations/P5PGIJ6O6ZOCYR5GPDTFUSGM'}\n","Grid 1\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762654428290, 'update_timestamp_ms': 1762654428290, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EV7E5A62P67TQE7K7H72GMHI', 'name': 'projects/ext-datasets/operations/EV7E5A62P67TQE7K7H72GMHI'}\n","Grid 2\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762654436050, 'update_timestamp_ms': 1762654436050, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4AVZ7E3WSF7MZQ5SRC22BDN5', 'name': 'projects/ext-datasets/operations/4AVZ7E3WSF7MZQ5SRC22BDN5'}\n","Grid 3\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762654442743, 'update_timestamp_ms': 1762654442743, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZQODKJ3S5ANYKJQYAXBL447K', 'name': 'projects/ext-datasets/operations/ZQODKJ3S5ANYKJQYAXBL447K'}\n","Grid 4\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762654448632, 'update_timestamp_ms': 1762654448632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XNYFGFYQFEOSIAKVFEE7W6XU', 'name': 'projects/ext-datasets/operations/XNYFGFYQFEOSIAKVFEE7W6XU'}\n","Grid 5\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762654455185, 'update_timestamp_ms': 1762654455185, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3BXQRHRNSWRULT4HJMA3CN6R', 'name': 'projects/ext-datasets/operations/3BXQRHRNSWRULT4HJMA3CN6R'}\n","Grid 6\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762654462176, 'update_timestamp_ms': 1762654462176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q77RUTGWKSFVUYJCEDQXB6XR', 'name': 'projects/ext-datasets/operations/Q77RUTGWKSFVUYJCEDQXB6XR'}\n","Grid 7\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762654468567, 'update_timestamp_ms': 1762654468567, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SOHWXOHUHGMGRQVYC3LRXY7I', 'name': 'projects/ext-datasets/operations/SOHWXOHUHGMGRQVYC3LRXY7I'}\n","Grid 8\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762654476363, 'update_timestamp_ms': 1762654476363, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2N3M5A4DU65YQXAZEEAGWDBO', 'name': 'projects/ext-datasets/operations/2N3M5A4DU65YQXAZEEAGWDBO'}\n","Grid 9\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762654482096, 'update_timestamp_ms': 1762654482096, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W7DJDN6AT7R73UQCRTXHUPLX', 'name': 'projects/ext-datasets/operations/W7DJDN6AT7R73UQCRTXHUPLX'}\n","Grid 10\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762654489169, 'update_timestamp_ms': 1762654489169, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JAZE4BTT7ULXR4AW22IC4IA5', 'name': 'projects/ext-datasets/operations/JAZE4BTT7ULXR4AW22IC4IA5'}\n","Grid 11\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762654494392, 'update_timestamp_ms': 1762654494392, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P5CR4MTND7KHWX6MBWDVSDX6', 'name': 'projects/ext-datasets/operations/P5CR4MTND7KHWX6MBWDVSDX6'}\n","Grid 12\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762654507057, 'update_timestamp_ms': 1762654507057, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LG6MCIURPMHJE72WS37BF67W', 'name': 'projects/ext-datasets/operations/LG6MCIURPMHJE72WS37BF67W'}\n","Grid 13\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762654510762, 'update_timestamp_ms': 1762654510762, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q62SVQJXSOLW33DSXWEA54WH', 'name': 'projects/ext-datasets/operations/Q62SVQJXSOLW33DSXWEA54WH'}\n","Grid 14\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762654518502, 'update_timestamp_ms': 1762654518502, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NOV7L45SQCUNMKRPUY6WRTG2', 'name': 'projects/ext-datasets/operations/NOV7L45SQCUNMKRPUY6WRTG2'}\n","Grid 15\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762654523429, 'update_timestamp_ms': 1762654523429, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZBKSXJEJEZ3CMYOJSNRBYOPN', 'name': 'projects/ext-datasets/operations/ZBKSXJEJEZ3CMYOJSNRBYOPN'}\n","Grid 16\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762654532988, 'update_timestamp_ms': 1762654532988, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N7IIJR5LMYZRNWOZ7IHY34XE', 'name': 'projects/ext-datasets/operations/N7IIJR5LMYZRNWOZ7IHY34XE'}\n","Grid 17\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762654545412, 'update_timestamp_ms': 1762654545412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFWAH4H5QTNH6PORAP5DRIKG', 'name': 'projects/ext-datasets/operations/IFWAH4H5QTNH6PORAP5DRIKG'}\n","Grid 18\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762654552007, 'update_timestamp_ms': 1762654552007, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UQOIY7PFUKS5XT7DSBV2RCNO', 'name': 'projects/ext-datasets/operations/UQOIY7PFUKS5XT7DSBV2RCNO'}\n","Grid 19\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762654560385, 'update_timestamp_ms': 1762654560385, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5ODI7Q3VCCSI2CDX6VSTEJSX', 'name': 'projects/ext-datasets/operations/5ODI7Q3VCCSI2CDX6VSTEJSX'}\n","Grid 20\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762654569196, 'update_timestamp_ms': 1762654569196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3HI2V3ZNVQVHDBITC7KIAH3', 'name': 'projects/ext-datasets/operations/K3HI2V3ZNVQVHDBITC7KIAH3'}\n","Grid 21\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762654578627, 'update_timestamp_ms': 1762654578627, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BA67H25NCB5XSZWY4VYBJRFR', 'name': 'projects/ext-datasets/operations/BA67H25NCB5XSZWY4VYBJRFR'}\n","Grid 22\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762654585544, 'update_timestamp_ms': 1762654585544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHSQSRDRJV65EYXLCUXD5G2K', 'name': 'projects/ext-datasets/operations/YHSQSRDRJV65EYXLCUXD5G2K'}\n","Grid 23\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762654592409, 'update_timestamp_ms': 1762654592409, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IURVCPQ472HQXBMIR2WKMMPW', 'name': 'projects/ext-datasets/operations/IURVCPQ472HQXBMIR2WKMMPW'}\n","Grid 24\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762654598493, 'update_timestamp_ms': 1762654598493, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LMKFYNE6L3XZKRID6G4MI26H', 'name': 'projects/ext-datasets/operations/LMKFYNE6L3XZKRID6G4MI26H'}\n","Grid 25\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762654608846, 'update_timestamp_ms': 1762654608846, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ESSMALVXLSZGFXYDHOG572TM', 'name': 'projects/ext-datasets/operations/ESSMALVXLSZGFXYDHOG572TM'}\n","Grid 26\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762654614074, 'update_timestamp_ms': 1762654614074, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R4B5FL2FNRE33XDQ5WTJD6PS', 'name': 'projects/ext-datasets/operations/R4B5FL2FNRE33XDQ5WTJD6PS'}\n","Grid 27\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762654620875, 'update_timestamp_ms': 1762654620875, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CJ64I3PTFXQXK2AH5PNH26NK', 'name': 'projects/ext-datasets/operations/CJ64I3PTFXQXK2AH5PNH26NK'}\n","Grid 28\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762654631431, 'update_timestamp_ms': 1762654631431, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3VBOFEDHHXMHQSTTTJWI3P2S', 'name': 'projects/ext-datasets/operations/3VBOFEDHHXMHQSTTTJWI3P2S'}\n","Grid 29\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762654640823, 'update_timestamp_ms': 1762654640823, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2TAVARUICMYDVATVCAYUY3OD', 'name': 'projects/ext-datasets/operations/2TAVARUICMYDVATVCAYUY3OD'}\n","Grid 30\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762654648097, 'update_timestamp_ms': 1762654648097, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BT2DVMSYFWV7HFUAFGMFUE3O', 'name': 'projects/ext-datasets/operations/BT2DVMSYFWV7HFUAFGMFUE3O'}\n","Grid 31\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762654656171, 'update_timestamp_ms': 1762654656171, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BR7YIKUSAEU46WPPX4MSJFFQ', 'name': 'projects/ext-datasets/operations/BR7YIKUSAEU46WPPX4MSJFFQ'}\n","Grid 32\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762654663746, 'update_timestamp_ms': 1762654663746, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AG5VIAECDXES6USZKPVVJ5BO', 'name': 'projects/ext-datasets/operations/AG5VIAECDXES6USZKPVVJ5BO'}\n","Grid 33\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762654673967, 'update_timestamp_ms': 1762654673967, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6NZMVZGPB5T5R4Y6Y2OVDK43', 'name': 'projects/ext-datasets/operations/6NZMVZGPB5T5R4Y6Y2OVDK43'}\n","Grid 34\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762654682863, 'update_timestamp_ms': 1762654682863, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J2F2C4UPFWOTRU4URM36FEUH', 'name': 'projects/ext-datasets/operations/J2F2C4UPFWOTRU4URM36FEUH'}\n","Grid 35\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762654688596, 'update_timestamp_ms': 1762654688596, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EUJEKUNXV2E6DCPHQDF5WYXR', 'name': 'projects/ext-datasets/operations/EUJEKUNXV2E6DCPHQDF5WYXR'}\n","Grid 36\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762654697025, 'update_timestamp_ms': 1762654697025, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RSLDAJITOU6XBMMLFH4WIREI', 'name': 'projects/ext-datasets/operations/RSLDAJITOU6XBMMLFH4WIREI'}\n","Grid 37\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762654703898, 'update_timestamp_ms': 1762654703898, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3MN5ODMGPRQQP3D5TRYPT23E', 'name': 'projects/ext-datasets/operations/3MN5ODMGPRQQP3D5TRYPT23E'}\n","Grid 38\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762654708866, 'update_timestamp_ms': 1762654708866, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CR45V4BGQRBTEHIX7ZCRNQRU', 'name': 'projects/ext-datasets/operations/CR45V4BGQRBTEHIX7ZCRNQRU'}\n","Grid 39\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762654716825, 'update_timestamp_ms': 1762654716825, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LTNFHQHZQHYMRFNFV5JO5K3J', 'name': 'projects/ext-datasets/operations/LTNFHQHZQHYMRFNFV5JO5K3J'}\n","Grid 40\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762654725729, 'update_timestamp_ms': 1762654725729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4TODRBKH5ZRCB5EPJCEJUYMH', 'name': 'projects/ext-datasets/operations/4TODRBKH5ZRCB5EPJCEJUYMH'}\n","Grid 41\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762654736107, 'update_timestamp_ms': 1762654736107, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4AOARY2SLRNTYXLJORE3NG4J', 'name': 'projects/ext-datasets/operations/4AOARY2SLRNTYXLJORE3NG4J'}\n","Grid 42\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762654744870, 'update_timestamp_ms': 1762654744870, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VZFPYELGR7JPXWZIX7ZSRPBT', 'name': 'projects/ext-datasets/operations/VZFPYELGR7JPXWZIX7ZSRPBT'}\n","Grid 43\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762654751181, 'update_timestamp_ms': 1762654751181, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S2HCJSHR54YV77OBCXQI3DOJ', 'name': 'projects/ext-datasets/operations/S2HCJSHR54YV77OBCXQI3DOJ'}\n","Grid 44\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762654762793, 'update_timestamp_ms': 1762654762793, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3XVLYS5J2BHIUDHLKQPBX3SU', 'name': 'projects/ext-datasets/operations/3XVLYS5J2BHIUDHLKQPBX3SU'}\n","Grid 45\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762654769048, 'update_timestamp_ms': 1762654769048, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BGT53AHBJXRE6EO47BFSMU7Q', 'name': 'projects/ext-datasets/operations/BGT53AHBJXRE6EO47BFSMU7Q'}\n","Grid 46\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762654775783, 'update_timestamp_ms': 1762654775783, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L2T64P3RVYPMN2R4F7QPLPAS', 'name': 'projects/ext-datasets/operations/L2T64P3RVYPMN2R4F7QPLPAS'}\n","Grid 47\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762654784493, 'update_timestamp_ms': 1762654784493, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KQR6772HX4KTWYGY64G2PTMS', 'name': 'projects/ext-datasets/operations/KQR6772HX4KTWYGY64G2PTMS'}\n","Grid 48\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762654793860, 'update_timestamp_ms': 1762654793860, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYRQMDUCXAPDCNOMN44ICPNZ', 'name': 'projects/ext-datasets/operations/ZYRQMDUCXAPDCNOMN44ICPNZ'}\n","Grid 49\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762654801999, 'update_timestamp_ms': 1762654801999, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UGG75TWLKBDKU7BBKFYT2K3L', 'name': 'projects/ext-datasets/operations/UGG75TWLKBDKU7BBKFYT2K3L'}\n","Grid 50\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762654809058, 'update_timestamp_ms': 1762654809058, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DBI3JVZTUKCZ6WFTHDYMYE72', 'name': 'projects/ext-datasets/operations/DBI3JVZTUKCZ6WFTHDYMYE72'}\n","Grid 51\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762654815411, 'update_timestamp_ms': 1762654815411, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YYQUFHIO7ZCVXTWCRCBV4RKY', 'name': 'projects/ext-datasets/operations/YYQUFHIO7ZCVXTWCRCBV4RKY'}\n","Grid 52\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762654820819, 'update_timestamp_ms': 1762654820819, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXHMRD53TXSEE2T4DNQCZLKL', 'name': 'projects/ext-datasets/operations/WXHMRD53TXSEE2T4DNQCZLKL'}\n","Grid 53\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762654826365, 'update_timestamp_ms': 1762654826365, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMZSET7SI2P6SZT3O2GTTC6Q', 'name': 'projects/ext-datasets/operations/BMZSET7SI2P6SZT3O2GTTC6Q'}\n","Grid 54\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762654835460, 'update_timestamp_ms': 1762654835460, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DJ7QBEERICXDVW7S3EFUSEGQ', 'name': 'projects/ext-datasets/operations/DJ7QBEERICXDVW7S3EFUSEGQ'}\n","Grid 55\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762654841227, 'update_timestamp_ms': 1762654841227, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DZY3TALCKANBXFPZ3YKQ5GZP', 'name': 'projects/ext-datasets/operations/DZY3TALCKANBXFPZ3YKQ5GZP'}\n","Grid 56\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762654847526, 'update_timestamp_ms': 1762654847526, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y4ZBUKN5QJGU5BBU3XJPS3VZ', 'name': 'projects/ext-datasets/operations/Y4ZBUKN5QJGU5BBU3XJPS3VZ'}\n","Grid 57\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762654853996, 'update_timestamp_ms': 1762654853996, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LZZI4UADWIEV6DEFWGJUVMET', 'name': 'projects/ext-datasets/operations/LZZI4UADWIEV6DEFWGJUVMET'}\n","Grid 58\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762654861628, 'update_timestamp_ms': 1762654861628, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TNCY45SMZQLYF37UOTZC6TLT', 'name': 'projects/ext-datasets/operations/TNCY45SMZQLYF37UOTZC6TLT'}\n","Grid 59\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762654867107, 'update_timestamp_ms': 1762654867107, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TG2IIJXIUJBET3N5PBXDZ6L4', 'name': 'projects/ext-datasets/operations/TG2IIJXIUJBET3N5PBXDZ6L4'}\n","Grid 60\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762654877080, 'update_timestamp_ms': 1762654877080, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62FQR26OJQKQRQZRQ3C2LQOW', 'name': 'projects/ext-datasets/operations/62FQR26OJQKQRQZRQ3C2LQOW'}\n","Grid 61\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762654883093, 'update_timestamp_ms': 1762654883093, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6HX4QN2VANPWCKCTFGLTIQ5Q', 'name': 'projects/ext-datasets/operations/6HX4QN2VANPWCKCTFGLTIQ5Q'}\n","Grid 62\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762654889075, 'update_timestamp_ms': 1762654889075, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J4J7OZVT7DZ7VIZ54AMQUUGM', 'name': 'projects/ext-datasets/operations/J4J7OZVT7DZ7VIZ54AMQUUGM'}\n","Grid 63\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762654898050, 'update_timestamp_ms': 1762654898050, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6V6KGVQEOODRMADWTEC5VUIX', 'name': 'projects/ext-datasets/operations/6V6KGVQEOODRMADWTEC5VUIX'}\n","Grid 64\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762654907283, 'update_timestamp_ms': 1762654907283, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PXHUD3F4RPLGPJOENXUU7QLH', 'name': 'projects/ext-datasets/operations/PXHUD3F4RPLGPJOENXUU7QLH'}\n","Grid 65\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762654913799, 'update_timestamp_ms': 1762654913799, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X22HJFUCDZTOY3LEVAP475AG', 'name': 'projects/ext-datasets/operations/X22HJFUCDZTOY3LEVAP475AG'}\n","Grid 66\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762654918742, 'update_timestamp_ms': 1762654918742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KTJGUNDA4CFEUNAM56WYNUW5', 'name': 'projects/ext-datasets/operations/KTJGUNDA4CFEUNAM56WYNUW5'}\n","Grid 67\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762654925619, 'update_timestamp_ms': 1762654925619, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H5NS234TDT6RSMEP4GIGW3FT', 'name': 'projects/ext-datasets/operations/H5NS234TDT6RSMEP4GIGW3FT'}\n","Grid 68\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762654931695, 'update_timestamp_ms': 1762654931695, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CMXM2QRIPI5NQ46B6W3OZZ4E', 'name': 'projects/ext-datasets/operations/CMXM2QRIPI5NQ46B6W3OZZ4E'}\n","Grid 69\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762654937022, 'update_timestamp_ms': 1762654937022, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DFCGXRSZ5FPTOVNFXDYRLDJJ', 'name': 'projects/ext-datasets/operations/DFCGXRSZ5FPTOVNFXDYRLDJJ'}\n","Grid 70\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762654943181, 'update_timestamp_ms': 1762654943181, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VOCVCP5TOGPBCZ6DVT5MUCPL', 'name': 'projects/ext-datasets/operations/VOCVCP5TOGPBCZ6DVT5MUCPL'}\n","Grid 71\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762654951856, 'update_timestamp_ms': 1762654951856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZAI6CKVCWKYXPUH56URUNSEM', 'name': 'projects/ext-datasets/operations/ZAI6CKVCWKYXPUH56URUNSEM'}\n","5405.53115773201\n","Year 2017, District 22: North 24 Parganas, grids: 105\n","Grid 0\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762654976848, 'update_timestamp_ms': 1762654976848, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IX3YTELXICHR7WVK2Z62N5ML', 'name': 'projects/ext-datasets/operations/IX3YTELXICHR7WVK2Z62N5ML'}\n","Grid 1\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762654988635, 'update_timestamp_ms': 1762654988635, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HQTZLQNDDVIFV2COJGSYYUC3', 'name': 'projects/ext-datasets/operations/HQTZLQNDDVIFV2COJGSYYUC3'}\n","Grid 2\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762655000432, 'update_timestamp_ms': 1762655000432, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJEHLVGA7GCO2NXDI6UC6C2F', 'name': 'projects/ext-datasets/operations/HJEHLVGA7GCO2NXDI6UC6C2F'}\n","Grid 3\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762655012757, 'update_timestamp_ms': 1762655012757, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '57SZOWMBEBJ5WBYS4TKGECYJ', 'name': 'projects/ext-datasets/operations/57SZOWMBEBJ5WBYS4TKGECYJ'}\n","Grid 4\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762655030436, 'update_timestamp_ms': 1762655030436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DZL2TEFZSV4PTLOAACAPA7GC', 'name': 'projects/ext-datasets/operations/DZL2TEFZSV4PTLOAACAPA7GC'}\n","Grid 5\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762655044543, 'update_timestamp_ms': 1762655044543, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UOHJY4563SXEXLQESUBMHGF7', 'name': 'projects/ext-datasets/operations/UOHJY4563SXEXLQESUBMHGF7'}\n","Grid 6\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762655056942, 'update_timestamp_ms': 1762655056942, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QFUSNJ4LZ7Y4VI2AL4NUIF65', 'name': 'projects/ext-datasets/operations/QFUSNJ4LZ7Y4VI2AL4NUIF65'}\n","Grid 7\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762655076261, 'update_timestamp_ms': 1762655076261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7SXDEJIRQIBCJRVREOB4JIQB', 'name': 'projects/ext-datasets/operations/7SXDEJIRQIBCJRVREOB4JIQB'}\n","Grid 8\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762655088635, 'update_timestamp_ms': 1762655088635, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OU4EF7IU5VABENSGND35BQ7B', 'name': 'projects/ext-datasets/operations/OU4EF7IU5VABENSGND35BQ7B'}\n","Grid 9\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762655103067, 'update_timestamp_ms': 1762655103067, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7SEPBYXLSKKQ2OZI2W3FYKKP', 'name': 'projects/ext-datasets/operations/7SEPBYXLSKKQ2OZI2W3FYKKP'}\n","Grid 10\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762655119325, 'update_timestamp_ms': 1762655119325, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FGJALR34NPODVL3QEPSEFAB3', 'name': 'projects/ext-datasets/operations/FGJALR34NPODVL3QEPSEFAB3'}\n","Grid 11\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762655132956, 'update_timestamp_ms': 1762655132956, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WUUIQAY4G7HDHHXDCIIU53DT', 'name': 'projects/ext-datasets/operations/WUUIQAY4G7HDHHXDCIIU53DT'}\n","Grid 12\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762655143399, 'update_timestamp_ms': 1762655143399, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '47AR4NXM5ACLYTVF2MOUQGS6', 'name': 'projects/ext-datasets/operations/47AR4NXM5ACLYTVF2MOUQGS6'}\n","Grid 13\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762655161623, 'update_timestamp_ms': 1762655161623, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NXL5YFKZPM7J4UBKCZPMRRTI', 'name': 'projects/ext-datasets/operations/NXL5YFKZPM7J4UBKCZPMRRTI'}\n","Grid 14\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762655168524, 'update_timestamp_ms': 1762655168524, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IH44RSFCI7PYZ6ESQIS6DRGK', 'name': 'projects/ext-datasets/operations/IH44RSFCI7PYZ6ESQIS6DRGK'}\n","Grid 15\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762655180629, 'update_timestamp_ms': 1762655180629, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VXJC2T6FBLXSZEHJVAG57HOY', 'name': 'projects/ext-datasets/operations/VXJC2T6FBLXSZEHJVAG57HOY'}\n","Grid 16\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762655190174, 'update_timestamp_ms': 1762655190174, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NKDBBM5DZ2TNHTM7UFKR3A46', 'name': 'projects/ext-datasets/operations/NKDBBM5DZ2TNHTM7UFKR3A46'}\n","Grid 17\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762655202483, 'update_timestamp_ms': 1762655202483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CB5ERTFD4JPIXUEVYR34YVRY', 'name': 'projects/ext-datasets/operations/CB5ERTFD4JPIXUEVYR34YVRY'}\n","Grid 18\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762655212750, 'update_timestamp_ms': 1762655212750, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JV3HE5IDT23QL2IY7CR73QIM', 'name': 'projects/ext-datasets/operations/JV3HE5IDT23QL2IY7CR73QIM'}\n","Grid 19\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762655225852, 'update_timestamp_ms': 1762655225852, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KT2OPPZXMCHK7LARGF5A3I43', 'name': 'projects/ext-datasets/operations/KT2OPPZXMCHK7LARGF5A3I43'}\n","Grid 20\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762655244130, 'update_timestamp_ms': 1762655244130, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DWZ36N3TWAIMRRFLBZUGPMLT', 'name': 'projects/ext-datasets/operations/DWZ36N3TWAIMRRFLBZUGPMLT'}\n","Grid 21\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762655256212, 'update_timestamp_ms': 1762655256212, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4GRPMKLZNUQ6PYKCJWRJE4LA', 'name': 'projects/ext-datasets/operations/4GRPMKLZNUQ6PYKCJWRJE4LA'}\n","Grid 22\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762655270216, 'update_timestamp_ms': 1762655270216, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O6OVG3P3SUVO3Y5CZNCTZAVO', 'name': 'projects/ext-datasets/operations/O6OVG3P3SUVO3Y5CZNCTZAVO'}\n","Grid 23\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762655281646, 'update_timestamp_ms': 1762655281646, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37IZXH6ORMCC75QDZWKBG22S', 'name': 'projects/ext-datasets/operations/37IZXH6ORMCC75QDZWKBG22S'}\n","Grid 24\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762655295759, 'update_timestamp_ms': 1762655295759, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W6BZHPC5DVKLUT4AUFYFQG3G', 'name': 'projects/ext-datasets/operations/W6BZHPC5DVKLUT4AUFYFQG3G'}\n","Grid 25\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762655307696, 'update_timestamp_ms': 1762655307696, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E32DF6SECLI4JF2Q62UKBIEZ', 'name': 'projects/ext-datasets/operations/E32DF6SECLI4JF2Q62UKBIEZ'}\n","Grid 26\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762655315987, 'update_timestamp_ms': 1762655315987, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A7BTXPTBVCHH5FJADNUYVJQO', 'name': 'projects/ext-datasets/operations/A7BTXPTBVCHH5FJADNUYVJQO'}\n","Grid 27\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762655327366, 'update_timestamp_ms': 1762655327366, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BHO6MBWKMQOXTJQUOQJWQ5F6', 'name': 'projects/ext-datasets/operations/BHO6MBWKMQOXTJQUOQJWQ5F6'}\n","Grid 28\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762655342826, 'update_timestamp_ms': 1762655342826, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KRNTPQEAXGRCALHR6HC5KKDM', 'name': 'projects/ext-datasets/operations/KRNTPQEAXGRCALHR6HC5KKDM'}\n","Grid 29\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762655356008, 'update_timestamp_ms': 1762655356008, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JPU5JNDCC6IUNL3KNOGALPJY', 'name': 'projects/ext-datasets/operations/JPU5JNDCC6IUNL3KNOGALPJY'}\n","Grid 30\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762655367045, 'update_timestamp_ms': 1762655367045, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OHHR6AWOAWANUTFWOYT72LHL', 'name': 'projects/ext-datasets/operations/OHHR6AWOAWANUTFWOYT72LHL'}\n","Grid 31\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762655379618, 'update_timestamp_ms': 1762655379618, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLTHQ7QTAMO3MUJBHCPVBQCI', 'name': 'projects/ext-datasets/operations/XLTHQ7QTAMO3MUJBHCPVBQCI'}\n","Grid 32\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762655391240, 'update_timestamp_ms': 1762655391240, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6ZGIXYUNNVJFBM5LCJGVCM7', 'name': 'projects/ext-datasets/operations/X6ZGIXYUNNVJFBM5LCJGVCM7'}\n","Grid 33\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762655404979, 'update_timestamp_ms': 1762655404979, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OPFFGITEIHWSSTGXUJIJSVOB', 'name': 'projects/ext-datasets/operations/OPFFGITEIHWSSTGXUJIJSVOB'}\n","Grid 34\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762655416831, 'update_timestamp_ms': 1762655416831, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TMRMQRWM4NXFZLHUGQTNHPGG', 'name': 'projects/ext-datasets/operations/TMRMQRWM4NXFZLHUGQTNHPGG'}\n","Grid 35\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762655427035, 'update_timestamp_ms': 1762655427035, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64CUDXKEVKXTUB2NKGA7IHHO', 'name': 'projects/ext-datasets/operations/64CUDXKEVKXTUB2NKGA7IHHO'}\n","Grid 36\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762655436251, 'update_timestamp_ms': 1762655436251, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A6PB2MTGI7FROARAEHN7R4HT', 'name': 'projects/ext-datasets/operations/A6PB2MTGI7FROARAEHN7R4HT'}\n","Grid 37\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762655449694, 'update_timestamp_ms': 1762655449694, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BO5O4Y5JK6ZRCILR5PNWHPAE', 'name': 'projects/ext-datasets/operations/BO5O4Y5JK6ZRCILR5PNWHPAE'}\n","Grid 38\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762655461029, 'update_timestamp_ms': 1762655461029, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KQU6WKD25NUHN6SW7IMX3LVV', 'name': 'projects/ext-datasets/operations/KQU6WKD25NUHN6SW7IMX3LVV'}\n","Grid 39\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762655474265, 'update_timestamp_ms': 1762655474265, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NKEINJ2WOJDUUXP72THAERTP', 'name': 'projects/ext-datasets/operations/NKEINJ2WOJDUUXP72THAERTP'}\n","Grid 40\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762655487921, 'update_timestamp_ms': 1762655487921, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LTCKVS7DKSEAXJEXFW6U7UN4', 'name': 'projects/ext-datasets/operations/LTCKVS7DKSEAXJEXFW6U7UN4'}\n","Grid 41\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762655497459, 'update_timestamp_ms': 1762655497459, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PQNPYVU7YWNBXVYX77A62EVL', 'name': 'projects/ext-datasets/operations/PQNPYVU7YWNBXVYX77A62EVL'}\n","Grid 42\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762655508634, 'update_timestamp_ms': 1762655508634, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RVG2POCLM7KQU5Q26DD3ZWYM', 'name': 'projects/ext-datasets/operations/RVG2POCLM7KQU5Q26DD3ZWYM'}\n","Grid 43\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762655517217, 'update_timestamp_ms': 1762655517217, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H45J2KL2RYHLAA6E5QAEEFC3', 'name': 'projects/ext-datasets/operations/H45J2KL2RYHLAA6E5QAEEFC3'}\n","Grid 44\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762655523539, 'update_timestamp_ms': 1762655523539, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGNDTXX66NOQCT5RLTDNBKF7', 'name': 'projects/ext-datasets/operations/QGNDTXX66NOQCT5RLTDNBKF7'}\n","Grid 45\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762655529939, 'update_timestamp_ms': 1762655529939, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R4GA47JEOWN7CMGCXYPM5ME5', 'name': 'projects/ext-datasets/operations/R4GA47JEOWN7CMGCXYPM5ME5'}\n","Grid 46\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762655543210, 'update_timestamp_ms': 1762655543210, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AGVLEVA4ZYWNF3F75BY3FKIP', 'name': 'projects/ext-datasets/operations/AGVLEVA4ZYWNF3F75BY3FKIP'}\n","Grid 47\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762655549662, 'update_timestamp_ms': 1762655549662, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SOYKXSQHASSQCB5M5YJSYSHL', 'name': 'projects/ext-datasets/operations/SOYKXSQHASSQCB5M5YJSYSHL'}\n","Grid 48\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762655563350, 'update_timestamp_ms': 1762655563350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O7FMO7H5WC5XU3SPIZCX6NZE', 'name': 'projects/ext-datasets/operations/O7FMO7H5WC5XU3SPIZCX6NZE'}\n","Grid 49\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762655574655, 'update_timestamp_ms': 1762655574655, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PHQDH6UTU65QV6BOVIH26CJB', 'name': 'projects/ext-datasets/operations/PHQDH6UTU65QV6BOVIH26CJB'}\n","Grid 50\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762655585367, 'update_timestamp_ms': 1762655585367, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PKHHLPYXRAKWBVUEDGKPIAVY', 'name': 'projects/ext-datasets/operations/PKHHLPYXRAKWBVUEDGKPIAVY'}\n","Grid 51\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762655598013, 'update_timestamp_ms': 1762655598013, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCWDWGOBTXXEL3FZL66BXJBJ', 'name': 'projects/ext-datasets/operations/FCWDWGOBTXXEL3FZL66BXJBJ'}\n","Grid 52\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762655609770, 'update_timestamp_ms': 1762655609770, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WLQ7KIFF54BPQYOJHUNZJYTP', 'name': 'projects/ext-datasets/operations/WLQ7KIFF54BPQYOJHUNZJYTP'}\n","Grid 53\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762655620410, 'update_timestamp_ms': 1762655620410, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EWPX3KHW6CS2VJHRW7HCU64U', 'name': 'projects/ext-datasets/operations/EWPX3KHW6CS2VJHRW7HCU64U'}\n","Grid 54\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762655633339, 'update_timestamp_ms': 1762655633339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VND6DUIIUFA4UDNDC55IMNS6', 'name': 'projects/ext-datasets/operations/VND6DUIIUFA4UDNDC55IMNS6'}\n","Grid 55\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762655645576, 'update_timestamp_ms': 1762655645576, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4YEKEDVHF2G4OKN7UXRWGBYE', 'name': 'projects/ext-datasets/operations/4YEKEDVHF2G4OKN7UXRWGBYE'}\n","Grid 56\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762655658201, 'update_timestamp_ms': 1762655658201, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QN5AMLTUHMY6AHF4JWMCG3L3', 'name': 'projects/ext-datasets/operations/QN5AMLTUHMY6AHF4JWMCG3L3'}\n","Grid 57\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762655667677, 'update_timestamp_ms': 1762655667677, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HHME5LVG7V5RETPFCIIWWUCU', 'name': 'projects/ext-datasets/operations/HHME5LVG7V5RETPFCIIWWUCU'}\n","Grid 58\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762655678161, 'update_timestamp_ms': 1762655678161, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYG55LEM3YLQ2WJK7FTEP6YV', 'name': 'projects/ext-datasets/operations/ZYG55LEM3YLQ2WJK7FTEP6YV'}\n","Grid 59\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762655690215, 'update_timestamp_ms': 1762655690215, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KWN4WCPDRQFPJSNST622DE4N', 'name': 'projects/ext-datasets/operations/KWN4WCPDRQFPJSNST622DE4N'}\n","Grid 60\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762655696093, 'update_timestamp_ms': 1762655696093, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5S5O6LIB4YQQMFOAUBQHUSRP', 'name': 'projects/ext-datasets/operations/5S5O6LIB4YQQMFOAUBQHUSRP'}\n","Grid 61\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762655708255, 'update_timestamp_ms': 1762655708255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AIM3ISYMBBC5AGP4NYSX5V3X', 'name': 'projects/ext-datasets/operations/AIM3ISYMBBC5AGP4NYSX5V3X'}\n","Grid 62\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762655718928, 'update_timestamp_ms': 1762655718928, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PC3IQNDBADK6WHIXMUTI4SVS', 'name': 'projects/ext-datasets/operations/PC3IQNDBADK6WHIXMUTI4SVS'}\n","Grid 63\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762655725111, 'update_timestamp_ms': 1762655725111, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7IL4CBTLG5ESCTKBF5ZAG5HX', 'name': 'projects/ext-datasets/operations/7IL4CBTLG5ESCTKBF5ZAG5HX'}\n","Grid 64\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762655734244, 'update_timestamp_ms': 1762655734244, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VTPIDHJ7PU6XLEDBASD6OKHO', 'name': 'projects/ext-datasets/operations/VTPIDHJ7PU6XLEDBASD6OKHO'}\n","Grid 65\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762655747051, 'update_timestamp_ms': 1762655747051, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '73M47M7S64CCFJLMXFSAIUSP', 'name': 'projects/ext-datasets/operations/73M47M7S64CCFJLMXFSAIUSP'}\n","Grid 66\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762655758633, 'update_timestamp_ms': 1762655758633, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '36SO62NUWDUPMIXGCYEAZF36', 'name': 'projects/ext-datasets/operations/36SO62NUWDUPMIXGCYEAZF36'}\n","Grid 67\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762655767427, 'update_timestamp_ms': 1762655767427, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PTOMVNQSCULA2NAPAX4A7LUM', 'name': 'projects/ext-datasets/operations/PTOMVNQSCULA2NAPAX4A7LUM'}\n","Grid 68\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762655781290, 'update_timestamp_ms': 1762655781290, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LTKXVT2D2W6N2A52D6NT6HO5', 'name': 'projects/ext-datasets/operations/LTKXVT2D2W6N2A52D6NT6HO5'}\n","Grid 69\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762655794396, 'update_timestamp_ms': 1762655794396, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R7O5Q3QCBUHDD3X7FEALVVGK', 'name': 'projects/ext-datasets/operations/R7O5Q3QCBUHDD3X7FEALVVGK'}\n","Grid 70\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762655804805, 'update_timestamp_ms': 1762655804805, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCUUPEE7UGV5LWAKYAFFX62C', 'name': 'projects/ext-datasets/operations/DCUUPEE7UGV5LWAKYAFFX62C'}\n","Grid 71\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762655818999, 'update_timestamp_ms': 1762655818999, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AC4VLPBOUELDZEOFKNYEAYSK', 'name': 'projects/ext-datasets/operations/AC4VLPBOUELDZEOFKNYEAYSK'}\n","Grid 72\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762655830408, 'update_timestamp_ms': 1762655830408, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YIKMPSTGZRZ6XJVQZK5WPM6L', 'name': 'projects/ext-datasets/operations/YIKMPSTGZRZ6XJVQZK5WPM6L'}\n","Grid 73\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762655836444, 'update_timestamp_ms': 1762655836444, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2IFISRQJO3D4IOMBI4WZPFL7', 'name': 'projects/ext-datasets/operations/2IFISRQJO3D4IOMBI4WZPFL7'}\n","Grid 74\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762655846537, 'update_timestamp_ms': 1762655846537, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NPPKK3SL5QLEFNARGHEW4QSQ', 'name': 'projects/ext-datasets/operations/NPPKK3SL5QLEFNARGHEW4QSQ'}\n","Grid 75\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762655856602, 'update_timestamp_ms': 1762655856602, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T4UVWYLPOGAKFAJE7DYRDZ76', 'name': 'projects/ext-datasets/operations/T4UVWYLPOGAKFAJE7DYRDZ76'}\n","Grid 76\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762655868124, 'update_timestamp_ms': 1762655868124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JEZ25FEEXJM4LMJ56J3DBR4B', 'name': 'projects/ext-datasets/operations/JEZ25FEEXJM4LMJ56J3DBR4B'}\n","Grid 77\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762655878153, 'update_timestamp_ms': 1762655878153, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DBSDB7JA556AXJGHKT3N3MJ7', 'name': 'projects/ext-datasets/operations/DBSDB7JA556AXJGHKT3N3MJ7'}\n","Grid 78\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762655888430, 'update_timestamp_ms': 1762655888430, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z6WTJCMTQJ5QNDY6MJUJYK7I', 'name': 'projects/ext-datasets/operations/Z6WTJCMTQJ5QNDY6MJUJYK7I'}\n","Grid 79\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762655899704, 'update_timestamp_ms': 1762655899704, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J7KMXLL6WPC6RR54WXNP67C2', 'name': 'projects/ext-datasets/operations/J7KMXLL6WPC6RR54WXNP67C2'}\n","Grid 80\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762655908193, 'update_timestamp_ms': 1762655908193, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '22ZVGIHCZPFFTKELHEAG4J2H', 'name': 'projects/ext-datasets/operations/22ZVGIHCZPFFTKELHEAG4J2H'}\n","Grid 81\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762655919120, 'update_timestamp_ms': 1762655919120, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VDDSVPBRKZEQ2AGRUBCBZ5IQ', 'name': 'projects/ext-datasets/operations/VDDSVPBRKZEQ2AGRUBCBZ5IQ'}\n","Grid 82\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762655932454, 'update_timestamp_ms': 1762655932454, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SUOKV2YMIZ2GRASZ2HGX3LO7', 'name': 'projects/ext-datasets/operations/SUOKV2YMIZ2GRASZ2HGX3LO7'}\n","Grid 83\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762655944385, 'update_timestamp_ms': 1762655944385, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GRU4X6X53JPRGQQ2UMKYDMIQ', 'name': 'projects/ext-datasets/operations/GRU4X6X53JPRGQQ2UMKYDMIQ'}\n","Grid 84\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762655954682, 'update_timestamp_ms': 1762655954682, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F5YOTQSJ64LU7DUZJAL46QLL', 'name': 'projects/ext-datasets/operations/F5YOTQSJ64LU7DUZJAL46QLL'}\n","Grid 85\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762655969185, 'update_timestamp_ms': 1762655969185, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UWZQBZI76ZIRNEPITYPMK46P', 'name': 'projects/ext-datasets/operations/UWZQBZI76ZIRNEPITYPMK46P'}\n","Grid 86\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762655982255, 'update_timestamp_ms': 1762655982255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VMJKZLV2PVBXQQXL63SZKWVM', 'name': 'projects/ext-datasets/operations/VMJKZLV2PVBXQQXL63SZKWVM'}\n","Grid 87\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_87_2017', 'priority': 100, 'creation_timestamp_ms': 1762655993638, 'update_timestamp_ms': 1762655993638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ED5VBUYOJL52KIN7QTE4LJKI', 'name': 'projects/ext-datasets/operations/ED5VBUYOJL52KIN7QTE4LJKI'}\n","Grid 88\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_88_2017', 'priority': 100, 'creation_timestamp_ms': 1762656006534, 'update_timestamp_ms': 1762656006534, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P3YUV5F7TM2ZJEWT5UKRVM2S', 'name': 'projects/ext-datasets/operations/P3YUV5F7TM2ZJEWT5UKRVM2S'}\n","Grid 89\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_89_2017', 'priority': 100, 'creation_timestamp_ms': 1762656013201, 'update_timestamp_ms': 1762656013201, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3PADHC3TZWN44XTCZSC7BTWU', 'name': 'projects/ext-datasets/operations/3PADHC3TZWN44XTCZSC7BTWU'}\n","Grid 90\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_90_2017', 'priority': 100, 'creation_timestamp_ms': 1762656023237, 'update_timestamp_ms': 1762656023237, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MG5W4B57RFQUUHP2JAPEJJQU', 'name': 'projects/ext-datasets/operations/MG5W4B57RFQUUHP2JAPEJJQU'}\n","Grid 91\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_91_2017', 'priority': 100, 'creation_timestamp_ms': 1762656035260, 'update_timestamp_ms': 1762656035260, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3TUBH7I2NLEAW4UHUZOZZBQ', 'name': 'projects/ext-datasets/operations/K3TUBH7I2NLEAW4UHUZOZZBQ'}\n","Grid 92\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_92_2017', 'priority': 100, 'creation_timestamp_ms': 1762656048547, 'update_timestamp_ms': 1762656048547, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QSIW3MTO4K5FBZPUZ37HL3RB', 'name': 'projects/ext-datasets/operations/QSIW3MTO4K5FBZPUZ37HL3RB'}\n","Grid 93\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_93_2017', 'priority': 100, 'creation_timestamp_ms': 1762656054558, 'update_timestamp_ms': 1762656054558, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L242L32UERU36XZJUCVSPXBN', 'name': 'projects/ext-datasets/operations/L242L32UERU36XZJUCVSPXBN'}\n","Grid 94\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_94_2017', 'priority': 100, 'creation_timestamp_ms': 1762656065988, 'update_timestamp_ms': 1762656065988, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SSSQWNHQHBAXPYSYO5QNAIOS', 'name': 'projects/ext-datasets/operations/SSSQWNHQHBAXPYSYO5QNAIOS'}\n","Grid 95\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_95_2017', 'priority': 100, 'creation_timestamp_ms': 1762656075268, 'update_timestamp_ms': 1762656075268, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '42SVQ22STRZEWQBL4CEBOEQH', 'name': 'projects/ext-datasets/operations/42SVQ22STRZEWQBL4CEBOEQH'}\n","Grid 96\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_96_2017', 'priority': 100, 'creation_timestamp_ms': 1762656085224, 'update_timestamp_ms': 1762656085224, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NQGQXCWPFKOTQWX3SJCW74XM', 'name': 'projects/ext-datasets/operations/NQGQXCWPFKOTQWX3SJCW74XM'}\n","Grid 97\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_97_2017', 'priority': 100, 'creation_timestamp_ms': 1762656098910, 'update_timestamp_ms': 1762656098910, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EKILBAPLUR6HIHYST2K4C776', 'name': 'projects/ext-datasets/operations/EKILBAPLUR6HIHYST2K4C776'}\n","Grid 98\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_98_2017', 'priority': 100, 'creation_timestamp_ms': 1762656111954, 'update_timestamp_ms': 1762656111954, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OLWXDYBZJH5AP6XBS6YF6S3M', 'name': 'projects/ext-datasets/operations/OLWXDYBZJH5AP6XBS6YF6S3M'}\n","Grid 99\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_99_2017', 'priority': 100, 'creation_timestamp_ms': 1762656123282, 'update_timestamp_ms': 1762656123282, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SJ5JB5XL45YMAX3KWSRF3YHG', 'name': 'projects/ext-datasets/operations/SJ5JB5XL45YMAX3KWSRF3YHG'}\n","Grid 100\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_100_2017', 'priority': 100, 'creation_timestamp_ms': 1762656135162, 'update_timestamp_ms': 1762656135162, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EOWGBJLP42XPBVYFSO7PEZTR', 'name': 'projects/ext-datasets/operations/EOWGBJLP42XPBVYFSO7PEZTR'}\n","Grid 101\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_101_2017', 'priority': 100, 'creation_timestamp_ms': 1762656146769, 'update_timestamp_ms': 1762656146769, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZHUWTEUEEKVWUPJX2IPOKDLR', 'name': 'projects/ext-datasets/operations/ZHUWTEUEEKVWUPJX2IPOKDLR'}\n","Grid 102\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_102_2017', 'priority': 100, 'creation_timestamp_ms': 1762656158297, 'update_timestamp_ms': 1762656158297, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M6ULVNULWU4C2QTCCQWIACXT', 'name': 'projects/ext-datasets/operations/M6ULVNULWU4C2QTCCQWIACXT'}\n","Grid 103\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_103_2017', 'priority': 100, 'creation_timestamp_ms': 1762656168478, 'update_timestamp_ms': 1762656168478, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DQACBR4OLFAKEA73STZMIODJ', 'name': 'projects/ext-datasets/operations/DQACBR4OLFAKEA73STZMIODJ'}\n","Grid 104\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_104_2017', 'priority': 100, 'creation_timestamp_ms': 1762656179056, 'update_timestamp_ms': 1762656179056, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TGRBOCRF7CMNYEOPSAYYMBXB', 'name': 'projects/ext-datasets/operations/TGRBOCRF7CMNYEOPSAYYMBXB'}\n","6632.857327699661\n","Year 2017, District 23: Pashchim Medinipur, grids: 137\n","Grid 0\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762656191525, 'update_timestamp_ms': 1762656191525, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N5IXXWLNZPAOUEC2LUF6GBFG', 'name': 'projects/ext-datasets/operations/N5IXXWLNZPAOUEC2LUF6GBFG'}\n","Grid 1\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762656199110, 'update_timestamp_ms': 1762656199110, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3GPF6PQ4CNSVXY7Z7UELEKEC', 'name': 'projects/ext-datasets/operations/3GPF6PQ4CNSVXY7Z7UELEKEC'}\n","Grid 2\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762656205412, 'update_timestamp_ms': 1762656205412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KO7DGC6KXHXA7TENULOYJQOV', 'name': 'projects/ext-datasets/operations/KO7DGC6KXHXA7TENULOYJQOV'}\n","Grid 3\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762656211108, 'update_timestamp_ms': 1762656211108, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GGFAAJUZAOI6J7MMCSS3Z2IN', 'name': 'projects/ext-datasets/operations/GGFAAJUZAOI6J7MMCSS3Z2IN'}\n","Grid 4\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762656219479, 'update_timestamp_ms': 1762656219479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WOVFKMXZRPBR2AHE6J4UYZWT', 'name': 'projects/ext-datasets/operations/WOVFKMXZRPBR2AHE6J4UYZWT'}\n","Grid 5\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762656226538, 'update_timestamp_ms': 1762656226538, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A6LXPACANOCYOG5E3INVUFSX', 'name': 'projects/ext-datasets/operations/A6LXPACANOCYOG5E3INVUFSX'}\n","Grid 6\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762656233248, 'update_timestamp_ms': 1762656233248, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GYKC34WL45AXUBSP3MLXTMAR', 'name': 'projects/ext-datasets/operations/GYKC34WL45AXUBSP3MLXTMAR'}\n","Grid 7\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762656241331, 'update_timestamp_ms': 1762656241331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KVIJ7Q56RFCYRAPI4PW2ZHCC', 'name': 'projects/ext-datasets/operations/KVIJ7Q56RFCYRAPI4PW2ZHCC'}\n","Grid 8\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762656248402, 'update_timestamp_ms': 1762656248402, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X5SC6YCJTY5C5HFKL745JI6Z', 'name': 'projects/ext-datasets/operations/X5SC6YCJTY5C5HFKL745JI6Z'}\n","Grid 9\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762656257963, 'update_timestamp_ms': 1762656257963, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O6PQHD2BGKHGAEO6QSI6P5GC', 'name': 'projects/ext-datasets/operations/O6PQHD2BGKHGAEO6QSI6P5GC'}\n","Grid 10\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762656261641, 'update_timestamp_ms': 1762656261641, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '524RUKBLGXLDC4A6WALRMP3G', 'name': 'projects/ext-datasets/operations/524RUKBLGXLDC4A6WALRMP3G'}\n","Grid 11\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762656271484, 'update_timestamp_ms': 1762656271484, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YLU53JR2VIEKKHALFJZHDOGD', 'name': 'projects/ext-datasets/operations/YLU53JR2VIEKKHALFJZHDOGD'}\n","Grid 12\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762656276108, 'update_timestamp_ms': 1762656276108, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MW2TQDKNT6SO7ENF26DIKV5M', 'name': 'projects/ext-datasets/operations/MW2TQDKNT6SO7ENF26DIKV5M'}\n","Grid 13\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762656280867, 'update_timestamp_ms': 1762656280867, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RWFA2T4ICTSYJHAFZ235E2MU', 'name': 'projects/ext-datasets/operations/RWFA2T4ICTSYJHAFZ235E2MU'}\n","Grid 14\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762656288738, 'update_timestamp_ms': 1762656288738, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLDBQFW7US6GNAPO7XUL7XKV', 'name': 'projects/ext-datasets/operations/XLDBQFW7US6GNAPO7XUL7XKV'}\n","Grid 15\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762656292460, 'update_timestamp_ms': 1762656292460, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MXV4MNAWY2S6RXIX5P4SC4H4', 'name': 'projects/ext-datasets/operations/MXV4MNAWY2S6RXIX5P4SC4H4'}\n","Grid 16\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762656296534, 'update_timestamp_ms': 1762656296534, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5FVBS7U4SHMCGLG7LMCYWZCN', 'name': 'projects/ext-datasets/operations/5FVBS7U4SHMCGLG7LMCYWZCN'}\n","Grid 17\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762656300879, 'update_timestamp_ms': 1762656300879, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FQEB4PWKHHMNKF2Q5FDWZSSK', 'name': 'projects/ext-datasets/operations/FQEB4PWKHHMNKF2Q5FDWZSSK'}\n","Grid 18\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762656309319, 'update_timestamp_ms': 1762656309319, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GH4XP7S2XMN7Q5KMFBX4LQ2N', 'name': 'projects/ext-datasets/operations/GH4XP7S2XMN7Q5KMFBX4LQ2N'}\n","Grid 19\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762656316641, 'update_timestamp_ms': 1762656316641, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXSCVZEOV5O2BZDCLRYEQEWT', 'name': 'projects/ext-datasets/operations/IXSCVZEOV5O2BZDCLRYEQEWT'}\n","Grid 20\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762656320650, 'update_timestamp_ms': 1762656320650, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OB6CO3QWKHN5FDR3UYVX55FS', 'name': 'projects/ext-datasets/operations/OB6CO3QWKHN5FDR3UYVX55FS'}\n","Grid 21\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762656326833, 'update_timestamp_ms': 1762656326833, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CY6HGBILDRRCFQ3IMB52DXRE', 'name': 'projects/ext-datasets/operations/CY6HGBILDRRCFQ3IMB52DXRE'}\n","Grid 22\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762656335955, 'update_timestamp_ms': 1762656335955, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F7OBIUKKF6JTZXGZQ2AZD4IZ', 'name': 'projects/ext-datasets/operations/F7OBIUKKF6JTZXGZQ2AZD4IZ'}\n","Grid 23\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762656339758, 'update_timestamp_ms': 1762656339758, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HDOFNZZX3ERQYTN54S2YB5BF', 'name': 'projects/ext-datasets/operations/HDOFNZZX3ERQYTN54S2YB5BF'}\n","Grid 24\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762656346987, 'update_timestamp_ms': 1762656346987, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2HANCJRX2W33KZQJFG4F56RB', 'name': 'projects/ext-datasets/operations/2HANCJRX2W33KZQJFG4F56RB'}\n","Grid 25\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762656354290, 'update_timestamp_ms': 1762656354290, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UHONUPREWQUGU3FRAFBP4KZE', 'name': 'projects/ext-datasets/operations/UHONUPREWQUGU3FRAFBP4KZE'}\n","Grid 26\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762656362444, 'update_timestamp_ms': 1762656362444, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WY4KG7Z4KJWSYPEDMBU4XKBT', 'name': 'projects/ext-datasets/operations/WY4KG7Z4KJWSYPEDMBU4XKBT'}\n","Grid 27\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762656371100, 'update_timestamp_ms': 1762656371100, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7WIJYTVOPX5ETBM5JGYSFHE2', 'name': 'projects/ext-datasets/operations/7WIJYTVOPX5ETBM5JGYSFHE2'}\n","Grid 28\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762656378402, 'update_timestamp_ms': 1762656378402, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MFIEIXFUR6Y3UZJEG5MMA5FH', 'name': 'projects/ext-datasets/operations/MFIEIXFUR6Y3UZJEG5MMA5FH'}\n","Grid 29\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762656386206, 'update_timestamp_ms': 1762656386206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D7IF32YKD3DAFVPQ5JASEWGS', 'name': 'projects/ext-datasets/operations/D7IF32YKD3DAFVPQ5JASEWGS'}\n","Grid 30\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762656392645, 'update_timestamp_ms': 1762656392645, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4WGZ6NBUQQRZ4TXLRTFW2Y3', 'name': 'projects/ext-datasets/operations/V4WGZ6NBUQQRZ4TXLRTFW2Y3'}\n","Grid 31\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762656397093, 'update_timestamp_ms': 1762656397093, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LXQJV4BS4KANLHELC32HNS2T', 'name': 'projects/ext-datasets/operations/LXQJV4BS4KANLHELC32HNS2T'}\n","Grid 32\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762656404004, 'update_timestamp_ms': 1762656404004, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K5PVV7G3P7XSJ6BRNGM4QCQH', 'name': 'projects/ext-datasets/operations/K5PVV7G3P7XSJ6BRNGM4QCQH'}\n","Grid 33\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762656411957, 'update_timestamp_ms': 1762656411957, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MTJ3WWRWD4VLDRNN67JVG6BW', 'name': 'projects/ext-datasets/operations/MTJ3WWRWD4VLDRNN67JVG6BW'}\n","Grid 34\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762656418840, 'update_timestamp_ms': 1762656418840, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DYO3ALZEAUM2MONCEYUYB24F', 'name': 'projects/ext-datasets/operations/DYO3ALZEAUM2MONCEYUYB24F'}\n","Grid 35\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762656427719, 'update_timestamp_ms': 1762656427719, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HFOXK4MPZPS3PA5MVDFPINIZ', 'name': 'projects/ext-datasets/operations/HFOXK4MPZPS3PA5MVDFPINIZ'}\n","Grid 36\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762656432553, 'update_timestamp_ms': 1762656432553, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EATJNAJPJASL6NHED6GBMLC3', 'name': 'projects/ext-datasets/operations/EATJNAJPJASL6NHED6GBMLC3'}\n","Grid 37\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762656442254, 'update_timestamp_ms': 1762656442254, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OMX24IFOFKEYLPATVQANCTUD', 'name': 'projects/ext-datasets/operations/OMX24IFOFKEYLPATVQANCTUD'}\n","Grid 38\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762656452255, 'update_timestamp_ms': 1762656452255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N5RBFHAJFWIIV3V7BNEM46Q2', 'name': 'projects/ext-datasets/operations/N5RBFHAJFWIIV3V7BNEM46Q2'}\n","Grid 39\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762656459895, 'update_timestamp_ms': 1762656459895, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BJVJXEVOF42MDX5O4LEQU4EU', 'name': 'projects/ext-datasets/operations/BJVJXEVOF42MDX5O4LEQU4EU'}\n","Grid 40\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762656465910, 'update_timestamp_ms': 1762656465910, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NTCLSHMOPZEWQOBUSF7UZE4Z', 'name': 'projects/ext-datasets/operations/NTCLSHMOPZEWQOBUSF7UZE4Z'}\n","Grid 41\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762656474428, 'update_timestamp_ms': 1762656474428, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXNUJJMRO7PZHYZCFWTQS6NU', 'name': 'projects/ext-datasets/operations/IXNUJJMRO7PZHYZCFWTQS6NU'}\n","Grid 42\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762656480606, 'update_timestamp_ms': 1762656480606, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K5H7C4LNI4Z6IHYTZTSPFUSH', 'name': 'projects/ext-datasets/operations/K5H7C4LNI4Z6IHYTZTSPFUSH'}\n","Grid 43\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762656487456, 'update_timestamp_ms': 1762656487456, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S5DFHBRCBKCD3MAV2AIJJ5LN', 'name': 'projects/ext-datasets/operations/S5DFHBRCBKCD3MAV2AIJJ5LN'}\n","Grid 44\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762656495657, 'update_timestamp_ms': 1762656495657, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZOOVCQRKGQGW4GYQ6OSGTDI', 'name': 'projects/ext-datasets/operations/AZOOVCQRKGQGW4GYQ6OSGTDI'}\n","Grid 45\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762656505780, 'update_timestamp_ms': 1762656505780, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3KNLJXFSBGO6SCJDGHGW3BE', 'name': 'projects/ext-datasets/operations/K3KNLJXFSBGO6SCJDGHGW3BE'}\n","Grid 46\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762656512324, 'update_timestamp_ms': 1762656512324, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BFE4HB7KGSK6ZS6RBG337BXT', 'name': 'projects/ext-datasets/operations/BFE4HB7KGSK6ZS6RBG337BXT'}\n","Grid 47\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762656518176, 'update_timestamp_ms': 1762656518176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TRNVLEFFW3B4GSDB5UWPPUB4', 'name': 'projects/ext-datasets/operations/TRNVLEFFW3B4GSDB5UWPPUB4'}\n","Grid 48\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762656527454, 'update_timestamp_ms': 1762656527454, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JGRJKCYFQZY3GLDBVXF4VL5W', 'name': 'projects/ext-datasets/operations/JGRJKCYFQZY3GLDBVXF4VL5W'}\n","Grid 49\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762656534302, 'update_timestamp_ms': 1762656534302, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TVFKBPWGU3I2ANR5ZZ62QQ7V', 'name': 'projects/ext-datasets/operations/TVFKBPWGU3I2ANR5ZZ62QQ7V'}\n","Grid 50\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762656538293, 'update_timestamp_ms': 1762656538293, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QH4Y4UB2AEIVTKHSDZSZP77E', 'name': 'projects/ext-datasets/operations/QH4Y4UB2AEIVTKHSDZSZP77E'}\n","Grid 51\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762656545479, 'update_timestamp_ms': 1762656545479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3GBB4YCPVFLWB3R5ISX5RBOA', 'name': 'projects/ext-datasets/operations/3GBB4YCPVFLWB3R5ISX5RBOA'}\n","Grid 52\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762656552841, 'update_timestamp_ms': 1762656552841, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NAMJHCTMIPZ7EVBAMWVP2UXC', 'name': 'projects/ext-datasets/operations/NAMJHCTMIPZ7EVBAMWVP2UXC'}\n","Grid 53\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762656560184, 'update_timestamp_ms': 1762656560184, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YLAZNLYRQ7CN3HQFGUAQYDR7', 'name': 'projects/ext-datasets/operations/YLAZNLYRQ7CN3HQFGUAQYDR7'}\n","Grid 54\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762656566137, 'update_timestamp_ms': 1762656566137, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SENC3EBMKCJ6DBXFPTFHJT73', 'name': 'projects/ext-datasets/operations/SENC3EBMKCJ6DBXFPTFHJT73'}\n","Grid 55\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762656573607, 'update_timestamp_ms': 1762656573607, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P2BS6PMTLVP54KI4SR3444TD', 'name': 'projects/ext-datasets/operations/P2BS6PMTLVP54KI4SR3444TD'}\n","Grid 56\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762656579996, 'update_timestamp_ms': 1762656579996, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BNQELTIGCTOOI2DOJ53ZQVHE', 'name': 'projects/ext-datasets/operations/BNQELTIGCTOOI2DOJ53ZQVHE'}\n","Grid 57\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762656588102, 'update_timestamp_ms': 1762656588102, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K2IKQ44ZFZP3FP6OKVJ7UKPY', 'name': 'projects/ext-datasets/operations/K2IKQ44ZFZP3FP6OKVJ7UKPY'}\n","Grid 58\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762656593522, 'update_timestamp_ms': 1762656593522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DV6XT6WGMSCKULSFVXXOBTNP', 'name': 'projects/ext-datasets/operations/DV6XT6WGMSCKULSFVXXOBTNP'}\n","Grid 59\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762656596924, 'update_timestamp_ms': 1762656596924, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZOMXOXZH2QWG5CEYSPLAAA42', 'name': 'projects/ext-datasets/operations/ZOMXOXZH2QWG5CEYSPLAAA42'}\n","Grid 60\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762656600989, 'update_timestamp_ms': 1762656600989, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '53Y6PSKHDAKNNLWUAEBJUFYR', 'name': 'projects/ext-datasets/operations/53Y6PSKHDAKNNLWUAEBJUFYR'}\n","Grid 61\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762656613380, 'update_timestamp_ms': 1762656613380, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SN3YIREDMLZITHQQWJERXFR7', 'name': 'projects/ext-datasets/operations/SN3YIREDMLZITHQQWJERXFR7'}\n","Grid 62\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762656620943, 'update_timestamp_ms': 1762656620943, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KNCWZK6F2XN6MKPJO6HVYXVE', 'name': 'projects/ext-datasets/operations/KNCWZK6F2XN6MKPJO6HVYXVE'}\n","Grid 63\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762656625158, 'update_timestamp_ms': 1762656625158, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VAU3N6VKUG5HETJN7AW3SK2I', 'name': 'projects/ext-datasets/operations/VAU3N6VKUG5HETJN7AW3SK2I'}\n","Grid 64\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762656631973, 'update_timestamp_ms': 1762656631973, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L754LPKGNNRQ6N5ESAPOXWZM', 'name': 'projects/ext-datasets/operations/L754LPKGNNRQ6N5ESAPOXWZM'}\n","Grid 65\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762656639958, 'update_timestamp_ms': 1762656639958, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6RWXDNP6BLABZPKXMZK5YC2V', 'name': 'projects/ext-datasets/operations/6RWXDNP6BLABZPKXMZK5YC2V'}\n","Grid 66\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762656646795, 'update_timestamp_ms': 1762656646795, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TR3RQIKCRZZDCTFL6H35VVEL', 'name': 'projects/ext-datasets/operations/TR3RQIKCRZZDCTFL6H35VVEL'}\n","Grid 67\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762656654192, 'update_timestamp_ms': 1762656654192, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3536G55NHWZJRXCUNX5INMMX', 'name': 'projects/ext-datasets/operations/3536G55NHWZJRXCUNX5INMMX'}\n","Grid 68\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762656657940, 'update_timestamp_ms': 1762656657940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5AP4XWHGCVSSYF276DXPUXKY', 'name': 'projects/ext-datasets/operations/5AP4XWHGCVSSYF276DXPUXKY'}\n","Grid 69\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762656665004, 'update_timestamp_ms': 1762656665004, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5CBUYVJFTEXYX2LON3G6ZWWT', 'name': 'projects/ext-datasets/operations/5CBUYVJFTEXYX2LON3G6ZWWT'}\n","Grid 70\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762656672582, 'update_timestamp_ms': 1762656672582, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '47V4ESB3D6M22D52LU4XKILJ', 'name': 'projects/ext-datasets/operations/47V4ESB3D6M22D52LU4XKILJ'}\n","Grid 71\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762656676519, 'update_timestamp_ms': 1762656676519, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GPINVNQTY3O6B5Q3ZW3B6ICL', 'name': 'projects/ext-datasets/operations/GPINVNQTY3O6B5Q3ZW3B6ICL'}\n","Grid 72\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762656683911, 'update_timestamp_ms': 1762656683911, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BAAKIKPXZO55ODDFIRL54NBO', 'name': 'projects/ext-datasets/operations/BAAKIKPXZO55ODDFIRL54NBO'}\n","Grid 73\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762656690478, 'update_timestamp_ms': 1762656690478, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J32725Z3XMKNYRHR2DFJEIED', 'name': 'projects/ext-datasets/operations/J32725Z3XMKNYRHR2DFJEIED'}\n","Grid 74\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762656696440, 'update_timestamp_ms': 1762656696440, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ICSQFWP3TWZH757K3KWJV4AM', 'name': 'projects/ext-datasets/operations/ICSQFWP3TWZH757K3KWJV4AM'}\n","Grid 75\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762656705583, 'update_timestamp_ms': 1762656705583, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BUDJCDIJ5BXY4I7X6V2VXOVS', 'name': 'projects/ext-datasets/operations/BUDJCDIJ5BXY4I7X6V2VXOVS'}\n","Grid 76\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762656714025, 'update_timestamp_ms': 1762656714025, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XM7PPW7FECNNNKQ6M3O66TK2', 'name': 'projects/ext-datasets/operations/XM7PPW7FECNNNKQ6M3O66TK2'}\n","Grid 77\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762656721691, 'update_timestamp_ms': 1762656721691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2VKXQCYRMV5WIVN6ELMCSWLA', 'name': 'projects/ext-datasets/operations/2VKXQCYRMV5WIVN6ELMCSWLA'}\n","Grid 78\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762656729644, 'update_timestamp_ms': 1762656729644, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IKCUB72MJN4NYMBZ7CZ6KRLJ', 'name': 'projects/ext-datasets/operations/IKCUB72MJN4NYMBZ7CZ6KRLJ'}\n","Grid 79\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762656736150, 'update_timestamp_ms': 1762656736150, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3NS6QI5NYBN5WLJGMLLMQYCZ', 'name': 'projects/ext-datasets/operations/3NS6QI5NYBN5WLJGMLLMQYCZ'}\n","Grid 80\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762656744215, 'update_timestamp_ms': 1762656744215, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C5DMPQANFPK6R6JRXP4OZOIU', 'name': 'projects/ext-datasets/operations/C5DMPQANFPK6R6JRXP4OZOIU'}\n","Grid 81\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762656751264, 'update_timestamp_ms': 1762656751264, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MUK6GYTC6RFW5MGWS5ZFPXD6', 'name': 'projects/ext-datasets/operations/MUK6GYTC6RFW5MGWS5ZFPXD6'}\n","Grid 82\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762656757892, 'update_timestamp_ms': 1762656757892, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C5AYSNYGXU25AAVQO4W6PCCH', 'name': 'projects/ext-datasets/operations/C5AYSNYGXU25AAVQO4W6PCCH'}\n","Grid 83\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762656766108, 'update_timestamp_ms': 1762656766108, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JZ5XQCLNBBW2UWEYUAMHFA7W', 'name': 'projects/ext-datasets/operations/JZ5XQCLNBBW2UWEYUAMHFA7W'}\n","Grid 84\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762656773523, 'update_timestamp_ms': 1762656773523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GTSDY74FH5Q7LZ6PUWZMTEQF', 'name': 'projects/ext-datasets/operations/GTSDY74FH5Q7LZ6PUWZMTEQF'}\n","Grid 85\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762656780140, 'update_timestamp_ms': 1762656780140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4UDWEDAOVC7WA3PDZSQD5VKI', 'name': 'projects/ext-datasets/operations/4UDWEDAOVC7WA3PDZSQD5VKI'}\n","Grid 86\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762656787998, 'update_timestamp_ms': 1762656787998, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZB62CJ4FM2LRBQGNPFCHWOQN', 'name': 'projects/ext-datasets/operations/ZB62CJ4FM2LRBQGNPFCHWOQN'}\n","Grid 87\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_87_2017', 'priority': 100, 'creation_timestamp_ms': 1762656796603, 'update_timestamp_ms': 1762656796603, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UJZBUDA55TS4UTZ5QNACJROS', 'name': 'projects/ext-datasets/operations/UJZBUDA55TS4UTZ5QNACJROS'}\n","Grid 88\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_88_2017', 'priority': 100, 'creation_timestamp_ms': 1762656803339, 'update_timestamp_ms': 1762656803339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPYIAGJK2JSNFJ2ZSUY5YUUI', 'name': 'projects/ext-datasets/operations/PPYIAGJK2JSNFJ2ZSUY5YUUI'}\n","Grid 89\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_89_2017', 'priority': 100, 'creation_timestamp_ms': 1762656811006, 'update_timestamp_ms': 1762656811006, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DSMVS6TSJ5XCD4P3WSFZXBXN', 'name': 'projects/ext-datasets/operations/DSMVS6TSJ5XCD4P3WSFZXBXN'}\n","Grid 90\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_90_2017', 'priority': 100, 'creation_timestamp_ms': 1762656818857, 'update_timestamp_ms': 1762656818857, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LW3GQQMPYOUJND54C57CMQET', 'name': 'projects/ext-datasets/operations/LW3GQQMPYOUJND54C57CMQET'}\n","Grid 91\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_91_2017', 'priority': 100, 'creation_timestamp_ms': 1762656825725, 'update_timestamp_ms': 1762656825725, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYBPIQYELLUCX73RQYL4DPT4', 'name': 'projects/ext-datasets/operations/XYBPIQYELLUCX73RQYL4DPT4'}\n","Grid 92\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_92_2017', 'priority': 100, 'creation_timestamp_ms': 1762656829437, 'update_timestamp_ms': 1762656829437, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N7QT4WOOFY3NT2RW2XDD54LV', 'name': 'projects/ext-datasets/operations/N7QT4WOOFY3NT2RW2XDD54LV'}\n","Grid 93\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_93_2017', 'priority': 100, 'creation_timestamp_ms': 1762656836388, 'update_timestamp_ms': 1762656836388, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'U3FNIEREMAZJM3MVYKRIRQCW', 'name': 'projects/ext-datasets/operations/U3FNIEREMAZJM3MVYKRIRQCW'}\n","Grid 94\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_94_2017', 'priority': 100, 'creation_timestamp_ms': 1762656844471, 'update_timestamp_ms': 1762656844471, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7KEVA4YXW4LZDTKJMGDEBA23', 'name': 'projects/ext-datasets/operations/7KEVA4YXW4LZDTKJMGDEBA23'}\n","Grid 95\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_95_2017', 'priority': 100, 'creation_timestamp_ms': 1762656851187, 'update_timestamp_ms': 1762656851187, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TYJGQKQOLJNDSAZRI37YNKZS', 'name': 'projects/ext-datasets/operations/TYJGQKQOLJNDSAZRI37YNKZS'}\n","Grid 96\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_96_2017', 'priority': 100, 'creation_timestamp_ms': 1762656858006, 'update_timestamp_ms': 1762656858006, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M7NG73K2EKRNO7LQMGFV3AOH', 'name': 'projects/ext-datasets/operations/M7NG73K2EKRNO7LQMGFV3AOH'}\n","Grid 97\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_97_2017', 'priority': 100, 'creation_timestamp_ms': 1762656865190, 'update_timestamp_ms': 1762656865190, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7ASTVLZ4NF3MPHDM567SFJVT', 'name': 'projects/ext-datasets/operations/7ASTVLZ4NF3MPHDM567SFJVT'}\n","Grid 98\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_98_2017', 'priority': 100, 'creation_timestamp_ms': 1762656869442, 'update_timestamp_ms': 1762656869442, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4XMKVY3QE4XT752M4CLLUTIG', 'name': 'projects/ext-datasets/operations/4XMKVY3QE4XT752M4CLLUTIG'}\n","Grid 99\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_99_2017', 'priority': 100, 'creation_timestamp_ms': 1762656876779, 'update_timestamp_ms': 1762656876779, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZWD2ZDDWWX45EAEPEB65J3K7', 'name': 'projects/ext-datasets/operations/ZWD2ZDDWWX45EAEPEB65J3K7'}\n","Grid 100\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_100_2017', 'priority': 100, 'creation_timestamp_ms': 1762656883926, 'update_timestamp_ms': 1762656883926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GT753YC5YE7L3J3LYFNTRGD4', 'name': 'projects/ext-datasets/operations/GT753YC5YE7L3J3LYFNTRGD4'}\n","Grid 101\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_101_2017', 'priority': 100, 'creation_timestamp_ms': 1762656887934, 'update_timestamp_ms': 1762656887934, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L3YFCSA2TL6YTS3GLFOADGST', 'name': 'projects/ext-datasets/operations/L3YFCSA2TL6YTS3GLFOADGST'}\n","Grid 102\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_102_2017', 'priority': 100, 'creation_timestamp_ms': 1762656892037, 'update_timestamp_ms': 1762656892037, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RLR6X7Y7VREGJ7ZY2PDSEZQW', 'name': 'projects/ext-datasets/operations/RLR6X7Y7VREGJ7ZY2PDSEZQW'}\n","Grid 103\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_103_2017', 'priority': 100, 'creation_timestamp_ms': 1762656900537, 'update_timestamp_ms': 1762656900537, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C6GJS2NQGLNEWJANSA7AH3EE', 'name': 'projects/ext-datasets/operations/C6GJS2NQGLNEWJANSA7AH3EE'}\n","Grid 104\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_104_2017', 'priority': 100, 'creation_timestamp_ms': 1762656904748, 'update_timestamp_ms': 1762656904748, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4MZT6NOPFNCEDHHGRFNUL453', 'name': 'projects/ext-datasets/operations/4MZT6NOPFNCEDHHGRFNUL453'}\n","Grid 105\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_105_2017', 'priority': 100, 'creation_timestamp_ms': 1762656913234, 'update_timestamp_ms': 1762656913234, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7W6MQEGDDUFYQGTB6A5GXM4W', 'name': 'projects/ext-datasets/operations/7W6MQEGDDUFYQGTB6A5GXM4W'}\n","Grid 106\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_106_2017', 'priority': 100, 'creation_timestamp_ms': 1762656922137, 'update_timestamp_ms': 1762656922137, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XBGPKGS4G6PYW7DK2AZMHSRM', 'name': 'projects/ext-datasets/operations/XBGPKGS4G6PYW7DK2AZMHSRM'}\n","Grid 107\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_107_2017', 'priority': 100, 'creation_timestamp_ms': 1762656928297, 'update_timestamp_ms': 1762656928297, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XSMYFCI23ZGDWCISG7PNNHSI', 'name': 'projects/ext-datasets/operations/XSMYFCI23ZGDWCISG7PNNHSI'}\n","Grid 108\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_108_2017', 'priority': 100, 'creation_timestamp_ms': 1762656934892, 'update_timestamp_ms': 1762656934892, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'METXYW4NNU7GOR5QNQ55N2IA', 'name': 'projects/ext-datasets/operations/METXYW4NNU7GOR5QNQ55N2IA'}\n","Grid 109\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_109_2017', 'priority': 100, 'creation_timestamp_ms': 1762656942637, 'update_timestamp_ms': 1762656942637, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BVCD7DLCYT6UTC67HZSHSQS3', 'name': 'projects/ext-datasets/operations/BVCD7DLCYT6UTC67HZSHSQS3'}\n","Grid 110\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_110_2017', 'priority': 100, 'creation_timestamp_ms': 1762656951277, 'update_timestamp_ms': 1762656951277, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E2WUB6OT4IXJTAP3PKKESEPI', 'name': 'projects/ext-datasets/operations/E2WUB6OT4IXJTAP3PKKESEPI'}\n","Grid 111\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_111_2017', 'priority': 100, 'creation_timestamp_ms': 1762656959080, 'update_timestamp_ms': 1762656959080, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2RZVNFL6BGNK6Z5CCYKE5KIZ', 'name': 'projects/ext-datasets/operations/2RZVNFL6BGNK6Z5CCYKE5KIZ'}\n","Grid 112\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_112_2017', 'priority': 100, 'creation_timestamp_ms': 1762656962562, 'update_timestamp_ms': 1762656962562, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XCZSBBI3H3732SCYAI6UZ7LR', 'name': 'projects/ext-datasets/operations/XCZSBBI3H3732SCYAI6UZ7LR'}\n","Grid 113\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_113_2017', 'priority': 100, 'creation_timestamp_ms': 1762656966527, 'update_timestamp_ms': 1762656966527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BUGV2EWOG2CHKPF5F453MEE7', 'name': 'projects/ext-datasets/operations/BUGV2EWOG2CHKPF5F453MEE7'}\n","Grid 114\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_114_2017', 'priority': 100, 'creation_timestamp_ms': 1762656974449, 'update_timestamp_ms': 1762656974449, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y5QPJKP2AUXCVSHP7P7PN2ZO', 'name': 'projects/ext-datasets/operations/Y5QPJKP2AUXCVSHP7P7PN2ZO'}\n","Grid 115\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_115_2017', 'priority': 100, 'creation_timestamp_ms': 1762656983994, 'update_timestamp_ms': 1762656983994, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2F3ORFW6VJNRZ3DFENH53KJU', 'name': 'projects/ext-datasets/operations/2F3ORFW6VJNRZ3DFENH53KJU'}\n","Grid 116\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_116_2017', 'priority': 100, 'creation_timestamp_ms': 1762656988094, 'update_timestamp_ms': 1762656988094, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JXTE3DZQAQTAVLOWX4DT6UBR', 'name': 'projects/ext-datasets/operations/JXTE3DZQAQTAVLOWX4DT6UBR'}\n","Grid 117\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_117_2017', 'priority': 100, 'creation_timestamp_ms': 1762656996237, 'update_timestamp_ms': 1762656996237, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C7YIAUKEHE7T5ZWI677IWWAC', 'name': 'projects/ext-datasets/operations/C7YIAUKEHE7T5ZWI677IWWAC'}\n","Grid 118\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_118_2017', 'priority': 100, 'creation_timestamp_ms': 1762657000170, 'update_timestamp_ms': 1762657000170, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WMVWOGJPZL4JGZQEDDH7OZWU', 'name': 'projects/ext-datasets/operations/WMVWOGJPZL4JGZQEDDH7OZWU'}\n","Grid 119\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_119_2017', 'priority': 100, 'creation_timestamp_ms': 1762657004443, 'update_timestamp_ms': 1762657004443, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6JX7VU44KIYFEV3QFUQ7AVMC', 'name': 'projects/ext-datasets/operations/6JX7VU44KIYFEV3QFUQ7AVMC'}\n","Grid 120\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_120_2017', 'priority': 100, 'creation_timestamp_ms': 1762657011353, 'update_timestamp_ms': 1762657011353, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J3YRSQKUOEUKAIZWRAJIVGNO', 'name': 'projects/ext-datasets/operations/J3YRSQKUOEUKAIZWRAJIVGNO'}\n","Grid 121\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_121_2017', 'priority': 100, 'creation_timestamp_ms': 1762657020222, 'update_timestamp_ms': 1762657020222, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OTGVIPC6DG2X2IFLRARVM37O', 'name': 'projects/ext-datasets/operations/OTGVIPC6DG2X2IFLRARVM37O'}\n","Grid 122\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_122_2017', 'priority': 100, 'creation_timestamp_ms': 1762657027173, 'update_timestamp_ms': 1762657027173, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H6C6L7WSL4XAO7IZHUYSNQQH', 'name': 'projects/ext-datasets/operations/H6C6L7WSL4XAO7IZHUYSNQQH'}\n","Grid 123\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_123_2017', 'priority': 100, 'creation_timestamp_ms': 1762657034045, 'update_timestamp_ms': 1762657034045, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BH2QCAX4UY542XGJ3OTBL3UU', 'name': 'projects/ext-datasets/operations/BH2QCAX4UY542XGJ3OTBL3UU'}\n","Grid 124\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_124_2017', 'priority': 100, 'creation_timestamp_ms': 1762657040222, 'update_timestamp_ms': 1762657040222, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RFJGCG43WQHXWTEIZOO5PLHO', 'name': 'projects/ext-datasets/operations/RFJGCG43WQHXWTEIZOO5PLHO'}\n","Grid 125\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_125_2017', 'priority': 100, 'creation_timestamp_ms': 1762657047717, 'update_timestamp_ms': 1762657047717, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YZB4KBBV6YL77ZH2V5KYQVOH', 'name': 'projects/ext-datasets/operations/YZB4KBBV6YL77ZH2V5KYQVOH'}\n","Grid 126\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_126_2017', 'priority': 100, 'creation_timestamp_ms': 1762657055564, 'update_timestamp_ms': 1762657055564, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HN2YO27JJ655VQPWLRY3CKDJ', 'name': 'projects/ext-datasets/operations/HN2YO27JJ655VQPWLRY3CKDJ'}\n","Grid 127\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_127_2017', 'priority': 100, 'creation_timestamp_ms': 1762657062509, 'update_timestamp_ms': 1762657062509, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QH76BPVJSYGQCZRBOBQQCVB3', 'name': 'projects/ext-datasets/operations/QH76BPVJSYGQCZRBOBQQCVB3'}\n","Grid 128\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_128_2017', 'priority': 100, 'creation_timestamp_ms': 1762657066784, 'update_timestamp_ms': 1762657066784, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FY46YSUR3GJO6EVU7JRAQEY2', 'name': 'projects/ext-datasets/operations/FY46YSUR3GJO6EVU7JRAQEY2'}\n","Grid 129\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_129_2017', 'priority': 100, 'creation_timestamp_ms': 1762657075843, 'update_timestamp_ms': 1762657075843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCZM2OIBC4XWFCHZTL4C3Z2N', 'name': 'projects/ext-datasets/operations/DCZM2OIBC4XWFCHZTL4C3Z2N'}\n","Grid 130\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_130_2017', 'priority': 100, 'creation_timestamp_ms': 1762657079376, 'update_timestamp_ms': 1762657079376, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4OEAXEP6MTWKSKGQ7DE2CD5Q', 'name': 'projects/ext-datasets/operations/4OEAXEP6MTWKSKGQ7DE2CD5Q'}\n","Grid 131\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_131_2017', 'priority': 100, 'creation_timestamp_ms': 1762657089946, 'update_timestamp_ms': 1762657089946, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4JWFYNHQ3X5H2KW6VY4PN57X', 'name': 'projects/ext-datasets/operations/4JWFYNHQ3X5H2KW6VY4PN57X'}\n","Grid 132\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_132_2017', 'priority': 100, 'creation_timestamp_ms': 1762657096800, 'update_timestamp_ms': 1762657096800, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NTWDLE6Q3BILPFQGJLO2SJIS', 'name': 'projects/ext-datasets/operations/NTWDLE6Q3BILPFQGJLO2SJIS'}\n","Grid 133\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_133_2017', 'priority': 100, 'creation_timestamp_ms': 1762657102962, 'update_timestamp_ms': 1762657102962, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SPSTNZV2PHA3NH7ELB23XKDV', 'name': 'projects/ext-datasets/operations/SPSTNZV2PHA3NH7ELB23XKDV'}\n","Grid 134\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_134_2017', 'priority': 100, 'creation_timestamp_ms': 1762657108805, 'update_timestamp_ms': 1762657108805, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AWG4QRGYMWFW2N67LXPFTQ46', 'name': 'projects/ext-datasets/operations/AWG4QRGYMWFW2N67LXPFTQ46'}\n","Grid 135\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_135_2017', 'priority': 100, 'creation_timestamp_ms': 1762657116011, 'update_timestamp_ms': 1762657116011, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMREEUG36OXX7BDXOLY7IRCX', 'name': 'projects/ext-datasets/operations/BMREEUG36OXX7BDXOLY7IRCX'}\n","Grid 136\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_136_2017', 'priority': 100, 'creation_timestamp_ms': 1762657127187, 'update_timestamp_ms': 1762657127187, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MB5NLIHHLXPL47HCZXG5ZVP6', 'name': 'projects/ext-datasets/operations/MB5NLIHHLXPL47HCZXG5ZVP6'}\n","7580.883316516876\n","Year 2017, District 24: Purba Medinipur, grids: 66\n","Grid 0\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762657143044, 'update_timestamp_ms': 1762657143044, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NFKNSCZCXX6NWFUEEIA23I6P', 'name': 'projects/ext-datasets/operations/NFKNSCZCXX6NWFUEEIA23I6P'}\n","Grid 1\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762657149907, 'update_timestamp_ms': 1762657149907, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X3LL7FUYRIIXVHMNEYJLIEL4', 'name': 'projects/ext-datasets/operations/X3LL7FUYRIIXVHMNEYJLIEL4'}\n","Grid 2\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762657157370, 'update_timestamp_ms': 1762657157370, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EYL36YDY2FGDLEFWNAAL7FNX', 'name': 'projects/ext-datasets/operations/EYL36YDY2FGDLEFWNAAL7FNX'}\n","Grid 3\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762657165718, 'update_timestamp_ms': 1762657165718, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VHZAWT6Y4OZESX3S4MI3OYI7', 'name': 'projects/ext-datasets/operations/VHZAWT6Y4OZESX3S4MI3OYI7'}\n","Grid 4\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762657172956, 'update_timestamp_ms': 1762657172956, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TLT5VBL3NCFFLCC5RPKIGZLB', 'name': 'projects/ext-datasets/operations/TLT5VBL3NCFFLCC5RPKIGZLB'}\n","Grid 5\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762657179898, 'update_timestamp_ms': 1762657179898, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HFXM6IK3DPR5BYCNCTPG7CIV', 'name': 'projects/ext-datasets/operations/HFXM6IK3DPR5BYCNCTPG7CIV'}\n","Grid 6\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762657189258, 'update_timestamp_ms': 1762657189258, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YRYOEJG7PEYVYP3IXVBD6QMH', 'name': 'projects/ext-datasets/operations/YRYOEJG7PEYVYP3IXVBD6QMH'}\n","Grid 7\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762657195636, 'update_timestamp_ms': 1762657195636, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UNRTKAJKC7LWJ64MAEUPGGQQ', 'name': 'projects/ext-datasets/operations/UNRTKAJKC7LWJ64MAEUPGGQQ'}\n","Grid 8\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762657199840, 'update_timestamp_ms': 1762657199840, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NXTYHOQ6L5ZG5VQFLWRNXUHV', 'name': 'projects/ext-datasets/operations/NXTYHOQ6L5ZG5VQFLWRNXUHV'}\n","Grid 9\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762657208523, 'update_timestamp_ms': 1762657208523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HLOI6D76NAR6CKRMMZZNPYK3', 'name': 'projects/ext-datasets/operations/HLOI6D76NAR6CKRMMZZNPYK3'}\n","Grid 10\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762657213022, 'update_timestamp_ms': 1762657213022, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5CPS6XPVWQOQX3YPE5QJR34O', 'name': 'projects/ext-datasets/operations/5CPS6XPVWQOQX3YPE5QJR34O'}\n","Grid 11\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762657218824, 'update_timestamp_ms': 1762657218824, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QI2VU5JGZ26EGOXYFDZUCPNN', 'name': 'projects/ext-datasets/operations/QI2VU5JGZ26EGOXYFDZUCPNN'}\n","Grid 12\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762657227317, 'update_timestamp_ms': 1762657227317, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XN65OGOBPUAGWGXVCUP5XIZC', 'name': 'projects/ext-datasets/operations/XN65OGOBPUAGWGXVCUP5XIZC'}\n","Grid 13\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762657233690, 'update_timestamp_ms': 1762657233690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WNCVRHZJ2HA4ZUJ3XNFK7M6A', 'name': 'projects/ext-datasets/operations/WNCVRHZJ2HA4ZUJ3XNFK7M6A'}\n","Grid 14\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762657239566, 'update_timestamp_ms': 1762657239566, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SHA377MFE4VLLNTC7BCJK6X5', 'name': 'projects/ext-datasets/operations/SHA377MFE4VLLNTC7BCJK6X5'}\n","Grid 15\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762657244254, 'update_timestamp_ms': 1762657244254, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7HCKJKLFG27BJ6XQQKERHD3W', 'name': 'projects/ext-datasets/operations/7HCKJKLFG27BJ6XQQKERHD3W'}\n","Grid 16\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762657248546, 'update_timestamp_ms': 1762657248546, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2OL4T4ZXUFO5EN3TO6EHXAY2', 'name': 'projects/ext-datasets/operations/2OL4T4ZXUFO5EN3TO6EHXAY2'}\n","Grid 17\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762657252318, 'update_timestamp_ms': 1762657252318, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S3R53I57CP6FFMPDSJURSPLU', 'name': 'projects/ext-datasets/operations/S3R53I57CP6FFMPDSJURSPLU'}\n","Grid 18\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762657259317, 'update_timestamp_ms': 1762657259317, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4QCUGOYVM4AODWF4JSBNRK6H', 'name': 'projects/ext-datasets/operations/4QCUGOYVM4AODWF4JSBNRK6H'}\n","Grid 19\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762657266873, 'update_timestamp_ms': 1762657266873, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PQ7JQWSHIZSXNU3KHRWV226V', 'name': 'projects/ext-datasets/operations/PQ7JQWSHIZSXNU3KHRWV226V'}\n","Grid 20\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762657274261, 'update_timestamp_ms': 1762657274261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HR64SO7UJ3ZQIABZDZMTXN2S', 'name': 'projects/ext-datasets/operations/HR64SO7UJ3ZQIABZDZMTXN2S'}\n","Grid 21\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762657283377, 'update_timestamp_ms': 1762657283377, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NCMIUWATTM2MQ47PNU4QLQGX', 'name': 'projects/ext-datasets/operations/NCMIUWATTM2MQ47PNU4QLQGX'}\n","Grid 22\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762657289088, 'update_timestamp_ms': 1762657289088, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QHNNO2ELSV4WI6EG7PS5RTSV', 'name': 'projects/ext-datasets/operations/QHNNO2ELSV4WI6EG7PS5RTSV'}\n","Grid 23\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762657296286, 'update_timestamp_ms': 1762657296286, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VH33RAYDODUAOACTX5VDFGXM', 'name': 'projects/ext-datasets/operations/VH33RAYDODUAOACTX5VDFGXM'}\n","Grid 24\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762657303274, 'update_timestamp_ms': 1762657303274, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L6ZWWHFMMA6XQVOI4TPK2R6A', 'name': 'projects/ext-datasets/operations/L6ZWWHFMMA6XQVOI4TPK2R6A'}\n","Grid 25\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762657312339, 'update_timestamp_ms': 1762657312339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JOS6MHIGUDMJK6AG36XO7CGN', 'name': 'projects/ext-datasets/operations/JOS6MHIGUDMJK6AG36XO7CGN'}\n","Grid 26\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762657320926, 'update_timestamp_ms': 1762657320926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RSUGU6BKDPJE2JEPESARW2PS', 'name': 'projects/ext-datasets/operations/RSUGU6BKDPJE2JEPESARW2PS'}\n","Grid 27\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762657328345, 'update_timestamp_ms': 1762657328345, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QDRLKNCE35GCMASX2R2VOAJD', 'name': 'projects/ext-datasets/operations/QDRLKNCE35GCMASX2R2VOAJD'}\n","Grid 28\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762657334358, 'update_timestamp_ms': 1762657334358, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YE7AYLCHK2EAFOT2JMZ2PYUQ', 'name': 'projects/ext-datasets/operations/YE7AYLCHK2EAFOT2JMZ2PYUQ'}\n","Grid 29\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762657341340, 'update_timestamp_ms': 1762657341340, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L5JUS35KWHCPGC6WYRVEZ3NS', 'name': 'projects/ext-datasets/operations/L5JUS35KWHCPGC6WYRVEZ3NS'}\n","Grid 30\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762657349275, 'update_timestamp_ms': 1762657349275, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MKQY5NBG44JJ5QRVZHRPA2B4', 'name': 'projects/ext-datasets/operations/MKQY5NBG44JJ5QRVZHRPA2B4'}\n","Grid 31\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762657356682, 'update_timestamp_ms': 1762657356682, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3X447YFUNHEHIMOFNAWBXMJJ', 'name': 'projects/ext-datasets/operations/3X447YFUNHEHIMOFNAWBXMJJ'}\n","Grid 32\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762657363331, 'update_timestamp_ms': 1762657363331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2KJETJDPKLZJBSW772CLXJQA', 'name': 'projects/ext-datasets/operations/2KJETJDPKLZJBSW772CLXJQA'}\n","Grid 33\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762657368275, 'update_timestamp_ms': 1762657368275, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7FMTES43YOXVLO3EKXN5M6CX', 'name': 'projects/ext-datasets/operations/7FMTES43YOXVLO3EKXN5M6CX'}\n","Grid 34\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762657376229, 'update_timestamp_ms': 1762657376229, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NLRTYMEML47QL3MZLI5M7Y7A', 'name': 'projects/ext-datasets/operations/NLRTYMEML47QL3MZLI5M7Y7A'}\n","Grid 35\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762657383237, 'update_timestamp_ms': 1762657383237, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AHFEMQK3ZRKL2NSHSP47OHK3', 'name': 'projects/ext-datasets/operations/AHFEMQK3ZRKL2NSHSP47OHK3'}\n","Grid 36\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762657389555, 'update_timestamp_ms': 1762657389555, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5WUSEIDEOJWLMHCS2KADKWXC', 'name': 'projects/ext-datasets/operations/5WUSEIDEOJWLMHCS2KADKWXC'}\n","Grid 37\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762657397022, 'update_timestamp_ms': 1762657397022, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YE3U3KFAKUL6DOJMDYCXZBHU', 'name': 'projects/ext-datasets/operations/YE3U3KFAKUL6DOJMDYCXZBHU'}\n","Grid 38\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762657405477, 'update_timestamp_ms': 1762657405477, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XIMTQNVF3BZTESY35B5SJDTN', 'name': 'projects/ext-datasets/operations/XIMTQNVF3BZTESY35B5SJDTN'}\n","Grid 39\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762657413101, 'update_timestamp_ms': 1762657413101, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NMY3CNFHLH2NQMM4F66PEMSI', 'name': 'projects/ext-datasets/operations/NMY3CNFHLH2NQMM4F66PEMSI'}\n","Grid 40\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762657421166, 'update_timestamp_ms': 1762657421166, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A7TCPZ32MIPT3G7LXBOEXG63', 'name': 'projects/ext-datasets/operations/A7TCPZ32MIPT3G7LXBOEXG63'}\n","Grid 41\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762657428249, 'update_timestamp_ms': 1762657428249, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W2NXGIOOMEQV4QIODVZ5FOBW', 'name': 'projects/ext-datasets/operations/W2NXGIOOMEQV4QIODVZ5FOBW'}\n","Grid 42\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762657436025, 'update_timestamp_ms': 1762657436025, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64V3QFTQXEFBI3XVAYC3NHJN', 'name': 'projects/ext-datasets/operations/64V3QFTQXEFBI3XVAYC3NHJN'}\n","Grid 43\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762657443414, 'update_timestamp_ms': 1762657443414, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VLVA4DDQOGFYDZ5WZJP2UDAJ', 'name': 'projects/ext-datasets/operations/VLVA4DDQOGFYDZ5WZJP2UDAJ'}\n","Grid 44\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762657451191, 'update_timestamp_ms': 1762657451191, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7NYB47PM4MVRZ6NIPA5WORN4', 'name': 'projects/ext-datasets/operations/7NYB47PM4MVRZ6NIPA5WORN4'}\n","Grid 45\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762657459746, 'update_timestamp_ms': 1762657459746, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LUU6WH2SK7PDN4PK3CHEDO3M', 'name': 'projects/ext-datasets/operations/LUU6WH2SK7PDN4PK3CHEDO3M'}\n","Grid 46\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762657466211, 'update_timestamp_ms': 1762657466211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UVS2QOYH2PLGTMFSHM3XMPL3', 'name': 'projects/ext-datasets/operations/UVS2QOYH2PLGTMFSHM3XMPL3'}\n","Grid 47\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762657474364, 'update_timestamp_ms': 1762657474364, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HYMGFLYI3EUHTCRX2G4YSIBJ', 'name': 'projects/ext-datasets/operations/HYMGFLYI3EUHTCRX2G4YSIBJ'}\n","Grid 48\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762657481945, 'update_timestamp_ms': 1762657481945, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4BN76EHEBBDZGQ36ZFRXQUQS', 'name': 'projects/ext-datasets/operations/4BN76EHEBBDZGQ36ZFRXQUQS'}\n","Grid 49\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762657486739, 'update_timestamp_ms': 1762657486739, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G2QERUTI7ZNPL2SGP6BHQG3T', 'name': 'projects/ext-datasets/operations/G2QERUTI7ZNPL2SGP6BHQG3T'}\n","Grid 50\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762657492966, 'update_timestamp_ms': 1762657492966, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GYW4IE7E6CWEFZ4W4PPYTQHN', 'name': 'projects/ext-datasets/operations/GYW4IE7E6CWEFZ4W4PPYTQHN'}\n","Grid 51\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762657501242, 'update_timestamp_ms': 1762657501242, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'STFV4MBGEI4IU5WJJV6FGP7M', 'name': 'projects/ext-datasets/operations/STFV4MBGEI4IU5WJJV6FGP7M'}\n","Grid 52\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762657508705, 'update_timestamp_ms': 1762657508705, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7EJGN4K6ZQ5BKTGOKWOUADDC', 'name': 'projects/ext-datasets/operations/7EJGN4K6ZQ5BKTGOKWOUADDC'}\n","Grid 53\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762657517426, 'update_timestamp_ms': 1762657517426, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MGHCS3PDQ43H5AWWB7ZLYMAR', 'name': 'projects/ext-datasets/operations/MGHCS3PDQ43H5AWWB7ZLYMAR'}\n","Grid 54\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762657523343, 'update_timestamp_ms': 1762657523343, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ECSDKPVOKNRGOWKRPHD6YQO2', 'name': 'projects/ext-datasets/operations/ECSDKPVOKNRGOWKRPHD6YQO2'}\n","Grid 55\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762657531327, 'update_timestamp_ms': 1762657531327, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HTBG5GMB44VGTI2DQMDFAUHQ', 'name': 'projects/ext-datasets/operations/HTBG5GMB44VGTI2DQMDFAUHQ'}\n","Grid 56\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762657539664, 'update_timestamp_ms': 1762657539664, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UTURDEYASPPGZ2JAKQIWVTX6', 'name': 'projects/ext-datasets/operations/UTURDEYASPPGZ2JAKQIWVTX6'}\n","Grid 57\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762657547246, 'update_timestamp_ms': 1762657547246, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T3L2GNWKIVO6OA64TECF5IFC', 'name': 'projects/ext-datasets/operations/T3L2GNWKIVO6OA64TECF5IFC'}\n","Grid 58\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762657551131, 'update_timestamp_ms': 1762657551131, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2LPEF26DVQLNIFSGD6HIT25S', 'name': 'projects/ext-datasets/operations/2LPEF26DVQLNIFSGD6HIT25S'}\n","Grid 59\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762657559342, 'update_timestamp_ms': 1762657559342, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WLGCU5XBQGP6MXJDOXWER3V6', 'name': 'projects/ext-datasets/operations/WLGCU5XBQGP6MXJDOXWER3V6'}\n","Grid 60\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762657572975, 'update_timestamp_ms': 1762657572975, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GOEHV2ZA4X664FMUNO62HUM5', 'name': 'projects/ext-datasets/operations/GOEHV2ZA4X664FMUNO62HUM5'}\n","Grid 61\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762657580419, 'update_timestamp_ms': 1762657580419, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ACJYYPK7NQXZNZUHTBICPG42', 'name': 'projects/ext-datasets/operations/ACJYYPK7NQXZNZUHTBICPG42'}\n","Grid 62\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762657584248, 'update_timestamp_ms': 1762657584248, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2HHKDJSRNZMEI5KMBDLFFWD5', 'name': 'projects/ext-datasets/operations/2HHKDJSRNZMEI5KMBDLFFWD5'}\n","Grid 63\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762657591412, 'update_timestamp_ms': 1762657591412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F2B2IYVKSVYZ56Y7WV6XHBI2', 'name': 'projects/ext-datasets/operations/F2B2IYVKSVYZ56Y7WV6XHBI2'}\n","Grid 64\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762657598365, 'update_timestamp_ms': 1762657598365, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q5TXVMHH6QL5F3EVHOZIB2QZ', 'name': 'projects/ext-datasets/operations/Q5TXVMHH6QL5F3EVHOZIB2QZ'}\n","Grid 65\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762657605363, 'update_timestamp_ms': 1762657605363, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZUGC6PKQR6AFOA4ID5ELHTL', 'name': 'projects/ext-datasets/operations/AZUGC6PKQR6AFOA4ID5ELHTL'}\n","8059.015738725662\n","Year 2017, District 25: Puruliya, grids: 14\n","Grid 0\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762657617740, 'update_timestamp_ms': 1762657617740, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5M4FOJF74A6FYVNEWN7QBZRV', 'name': 'projects/ext-datasets/operations/5M4FOJF74A6FYVNEWN7QBZRV'}\n","Grid 1\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762657627843, 'update_timestamp_ms': 1762657627843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4P6PUJOLKOGAWBVRANK67ES', 'name': 'projects/ext-datasets/operations/V4P6PUJOLKOGAWBVRANK67ES'}\n","Grid 2\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762657633527, 'update_timestamp_ms': 1762657633527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CHHXOWHFOOSQY6C5DKEY77JN', 'name': 'projects/ext-datasets/operations/CHHXOWHFOOSQY6C5DKEY77JN'}\n","Grid 3\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762657638867, 'update_timestamp_ms': 1762657638867, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IBFQ6N35DOGLCCN5G5EBIKTP', 'name': 'projects/ext-datasets/operations/IBFQ6N35DOGLCCN5G5EBIKTP'}\n","Grid 4\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762657646397, 'update_timestamp_ms': 1762657646397, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ITON6E27PXDBKJCJ5DJXWXLH', 'name': 'projects/ext-datasets/operations/ITON6E27PXDBKJCJ5DJXWXLH'}\n","Grid 5\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762657654320, 'update_timestamp_ms': 1762657654320, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M253CVPDA5FTXKXKP5FKOXEH', 'name': 'projects/ext-datasets/operations/M253CVPDA5FTXKXKP5FKOXEH'}\n","Grid 6\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762657662112, 'update_timestamp_ms': 1762657662112, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IDACHDF74JE7SJFBRCGNOS7A', 'name': 'projects/ext-datasets/operations/IDACHDF74JE7SJFBRCGNOS7A'}\n","Grid 7\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762657668483, 'update_timestamp_ms': 1762657668483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7C6PQT5WVKHTOT7FYOPWBOGQ', 'name': 'projects/ext-datasets/operations/7C6PQT5WVKHTOT7FYOPWBOGQ'}\n","Grid 8\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762657674895, 'update_timestamp_ms': 1762657674895, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LAY5XYLYOEAGFFD67X2CLZS7', 'name': 'projects/ext-datasets/operations/LAY5XYLYOEAGFFD67X2CLZS7'}\n","Grid 9\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762657683603, 'update_timestamp_ms': 1762657683603, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '57YVND4GM65EWUX5C3SKUTGY', 'name': 'projects/ext-datasets/operations/57YVND4GM65EWUX5C3SKUTGY'}\n","Grid 10\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762657690623, 'update_timestamp_ms': 1762657690623, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NWEGVDZQ7XZKA75ZJ6JEVBB', 'name': 'projects/ext-datasets/operations/4NWEGVDZQ7XZKA75ZJ6JEVBB'}\n","Grid 11\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762657697680, 'update_timestamp_ms': 1762657697680, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XJR3AEJSUWFQVS2AC5S3L7SE', 'name': 'projects/ext-datasets/operations/XJR3AEJSUWFQVS2AC5S3L7SE'}\n","Grid 12\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762657703789, 'update_timestamp_ms': 1762657703789, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DINWXLEKJUU2VZWR6AACYX7T', 'name': 'projects/ext-datasets/operations/DINWXLEKJUU2VZWR6AACYX7T'}\n","Grid 13\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762657710134, 'update_timestamp_ms': 1762657710134, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VOFZSRN2Q24F3TWJNTGQSRZR', 'name': 'projects/ext-datasets/operations/VOFZSRN2Q24F3TWJNTGQSRZR'}\n","8163.815611839294\n","Year 2017, District 26: South 24 Parganas, grids: 96\n","Grid 0\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762657721617, 'update_timestamp_ms': 1762657721617, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CO7CVHBLECLP5O3BCXI2VNPK', 'name': 'projects/ext-datasets/operations/CO7CVHBLECLP5O3BCXI2VNPK'}\n","Grid 1\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762657726433, 'update_timestamp_ms': 1762657726433, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2H5I2G4GW4K5S4R4RKRLVOAO', 'name': 'projects/ext-datasets/operations/2H5I2G4GW4K5S4R4RKRLVOAO'}\n","Grid 2\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762657736622, 'update_timestamp_ms': 1762657736622, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2WIJVNRWZLDJJGSV7VJ6SNWV', 'name': 'projects/ext-datasets/operations/2WIJVNRWZLDJJGSV7VJ6SNWV'}\n","Grid 3\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762657743457, 'update_timestamp_ms': 1762657743457, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5PTR3G7WQSO5C4AQD2UYV6OC', 'name': 'projects/ext-datasets/operations/5PTR3G7WQSO5C4AQD2UYV6OC'}\n","Grid 4\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762657749135, 'update_timestamp_ms': 1762657749135, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZAZWNY6SF5H5QTLV2W3FX6AD', 'name': 'projects/ext-datasets/operations/ZAZWNY6SF5H5QTLV2W3FX6AD'}\n","Grid 5\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762657760790, 'update_timestamp_ms': 1762657760790, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62EE5QGJXR4TJKLPXQSSFEVQ', 'name': 'projects/ext-datasets/operations/62EE5QGJXR4TJKLPXQSSFEVQ'}\n","Grid 6\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762657774826, 'update_timestamp_ms': 1762657774826, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y3MW4Z3OJWSOEBFODVQGLMEN', 'name': 'projects/ext-datasets/operations/Y3MW4Z3OJWSOEBFODVQGLMEN'}\n","Grid 7\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762657786809, 'update_timestamp_ms': 1762657786809, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FZBZWJY5HIQ6VHYO6IAXI2P4', 'name': 'projects/ext-datasets/operations/FZBZWJY5HIQ6VHYO6IAXI2P4'}\n","Grid 8\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762657794926, 'update_timestamp_ms': 1762657794926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BIIP6T4I7WSMPUBS3MLW7LF6', 'name': 'projects/ext-datasets/operations/BIIP6T4I7WSMPUBS3MLW7LF6'}\n","Grid 9\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762657806207, 'update_timestamp_ms': 1762657806207, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2JG6KWR5AFWD2AYHRGX2O7PZ', 'name': 'projects/ext-datasets/operations/2JG6KWR5AFWD2AYHRGX2O7PZ'}\n","Grid 10\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762657815663, 'update_timestamp_ms': 1762657815663, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XUAJECSKIE4BD3IVPXQI4J2S', 'name': 'projects/ext-datasets/operations/XUAJECSKIE4BD3IVPXQI4J2S'}\n","Grid 11\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762657825834, 'update_timestamp_ms': 1762657825834, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XIFX4RYGD7KEQOZPW2M7Q2R6', 'name': 'projects/ext-datasets/operations/XIFX4RYGD7KEQOZPW2M7Q2R6'}\n","Grid 12\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762657836975, 'update_timestamp_ms': 1762657836975, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BJBAMUYGWTH43MX5TH7KVZDW', 'name': 'projects/ext-datasets/operations/BJBAMUYGWTH43MX5TH7KVZDW'}\n","Grid 13\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762657846609, 'update_timestamp_ms': 1762657846609, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2TQCMU7EHVXVHQL6HU7NF4QN', 'name': 'projects/ext-datasets/operations/2TQCMU7EHVXVHQL6HU7NF4QN'}\n","Grid 14\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762657853958, 'update_timestamp_ms': 1762657853958, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LSVMZ4URZV2HPVRNQ3KZAT55', 'name': 'projects/ext-datasets/operations/LSVMZ4URZV2HPVRNQ3KZAT55'}\n","Grid 15\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762657862953, 'update_timestamp_ms': 1762657862953, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PRP73XPBKBLQ4FKCGPNQVXB4', 'name': 'projects/ext-datasets/operations/PRP73XPBKBLQ4FKCGPNQVXB4'}\n","Grid 16\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762657872487, 'update_timestamp_ms': 1762657872487, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IA6XNM7FRZNTIGHDLO63YYZA', 'name': 'projects/ext-datasets/operations/IA6XNM7FRZNTIGHDLO63YYZA'}\n","Grid 17\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762657883397, 'update_timestamp_ms': 1762657883397, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NGZI7NSE6Q3RIUKLGVIB25NJ', 'name': 'projects/ext-datasets/operations/NGZI7NSE6Q3RIUKLGVIB25NJ'}\n","Grid 18\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762657894076, 'update_timestamp_ms': 1762657894076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7R7NGWVLTW5EGP6BHJYR7SXX', 'name': 'projects/ext-datasets/operations/7R7NGWVLTW5EGP6BHJYR7SXX'}\n","Grid 19\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762657905886, 'update_timestamp_ms': 1762657905886, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UKD4UTDCYP27YHBC2BDV4EWL', 'name': 'projects/ext-datasets/operations/UKD4UTDCYP27YHBC2BDV4EWL'}\n","Grid 20\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762657914676, 'update_timestamp_ms': 1762657914676, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '35Z66UZU7LC5PTK6GMVJ6V6J', 'name': 'projects/ext-datasets/operations/35Z66UZU7LC5PTK6GMVJ6V6J'}\n","Grid 21\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762657924865, 'update_timestamp_ms': 1762657924865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D2BMBYJJZ2ZLVEARBZJML565', 'name': 'projects/ext-datasets/operations/D2BMBYJJZ2ZLVEARBZJML565'}\n","Grid 22\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762657941140, 'update_timestamp_ms': 1762657941140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S4K2JP3VDT5UKVFPI7HABIHR', 'name': 'projects/ext-datasets/operations/S4K2JP3VDT5UKVFPI7HABIHR'}\n","Grid 23\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762657949056, 'update_timestamp_ms': 1762657949056, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXOGIGFDDNUA72RRHV3ZLI3C', 'name': 'projects/ext-datasets/operations/IXOGIGFDDNUA72RRHV3ZLI3C'}\n","Grid 24\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762657954728, 'update_timestamp_ms': 1762657954728, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XKRWEJTXI5TZCHJ5Q7FEEIEZ', 'name': 'projects/ext-datasets/operations/XKRWEJTXI5TZCHJ5Q7FEEIEZ'}\n","Grid 25\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762657967115, 'update_timestamp_ms': 1762657967115, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IJ2FCKDETNQKZKF55VR5L6TM', 'name': 'projects/ext-datasets/operations/IJ2FCKDETNQKZKF55VR5L6TM'}\n","Grid 26\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762657977118, 'update_timestamp_ms': 1762657977118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PJW2WA7SH4TBRYBBBQY6KKBD', 'name': 'projects/ext-datasets/operations/PJW2WA7SH4TBRYBBBQY6KKBD'}\n","Grid 27\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762657982147, 'update_timestamp_ms': 1762657982147, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YQR3742F6Y5NL53KSI56C4GC', 'name': 'projects/ext-datasets/operations/YQR3742F6Y5NL53KSI56C4GC'}\n","Grid 28\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762657993527, 'update_timestamp_ms': 1762657993527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C3GNJPDZHAKUU3V2MB2A7K2D', 'name': 'projects/ext-datasets/operations/C3GNJPDZHAKUU3V2MB2A7K2D'}\n","Grid 29\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762658004659, 'update_timestamp_ms': 1762658004659, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2G2FEI4UX2Y5CVDRCFSYPYE4', 'name': 'projects/ext-datasets/operations/2G2FEI4UX2Y5CVDRCFSYPYE4'}\n","Grid 30\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762658015257, 'update_timestamp_ms': 1762658015257, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WNGWXIIJZ63GHTKHWWJOQFPM', 'name': 'projects/ext-datasets/operations/WNGWXIIJZ63GHTKHWWJOQFPM'}\n","Grid 31\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762658023473, 'update_timestamp_ms': 1762658023473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NJ3JE2P2BHN2KRXTVR3PQ5HC', 'name': 'projects/ext-datasets/operations/NJ3JE2P2BHN2KRXTVR3PQ5HC'}\n","Grid 32\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762658028979, 'update_timestamp_ms': 1762658028979, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QV6U3AYQ5F75G2CIJHVHSWDG', 'name': 'projects/ext-datasets/operations/QV6U3AYQ5F75G2CIJHVHSWDG'}\n","Grid 33\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762658039477, 'update_timestamp_ms': 1762658039477, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZQIBCHQZ5GSCYOUFQI3A3UVG', 'name': 'projects/ext-datasets/operations/ZQIBCHQZ5GSCYOUFQI3A3UVG'}\n","Grid 34\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762658045365, 'update_timestamp_ms': 1762658045365, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4XOT7VGN5VILILS63LLUZHCK', 'name': 'projects/ext-datasets/operations/4XOT7VGN5VILILS63LLUZHCK'}\n","Grid 35\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762658050426, 'update_timestamp_ms': 1762658050426, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NTDMYCDK3IMXBZ2K7VKKN2EN', 'name': 'projects/ext-datasets/operations/NTDMYCDK3IMXBZ2K7VKKN2EN'}\n","Grid 36\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762658060828, 'update_timestamp_ms': 1762658060828, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V5ONBRTZRMNIIE5UNB4B46RT', 'name': 'projects/ext-datasets/operations/V5ONBRTZRMNIIE5UNB4B46RT'}\n","Grid 37\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762658070675, 'update_timestamp_ms': 1762658070675, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GF5TP6HSVDSEPOUTJJRKIK5X', 'name': 'projects/ext-datasets/operations/GF5TP6HSVDSEPOUTJJRKIK5X'}\n","Grid 38\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762658078959, 'update_timestamp_ms': 1762658078959, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CJXDQHZN5ME55NN7ZP4SF35N', 'name': 'projects/ext-datasets/operations/CJXDQHZN5ME55NN7ZP4SF35N'}\n","Grid 39\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762658084150, 'update_timestamp_ms': 1762658084150, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IOFWUZRXS2O4UVG4ZT5F5YYJ', 'name': 'projects/ext-datasets/operations/IOFWUZRXS2O4UVG4ZT5F5YYJ'}\n","Grid 40\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762658094491, 'update_timestamp_ms': 1762658094491, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SGHSFIDOMYNXJKBWODVTUE3I', 'name': 'projects/ext-datasets/operations/SGHSFIDOMYNXJKBWODVTUE3I'}\n","Grid 41\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762658099479, 'update_timestamp_ms': 1762658099479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZ6L6VGYP573JOYHI7AK6EVL', 'name': 'projects/ext-datasets/operations/XZ6L6VGYP573JOYHI7AK6EVL'}\n","Grid 42\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762658104123, 'update_timestamp_ms': 1762658104123, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4T46VWLU42FSHPQNNN7G2KS3', 'name': 'projects/ext-datasets/operations/4T46VWLU42FSHPQNNN7G2KS3'}\n","Grid 43\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762658114192, 'update_timestamp_ms': 1762658114192, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N34EYKWSDADLOYRZCZQ54UMY', 'name': 'projects/ext-datasets/operations/N34EYKWSDADLOYRZCZQ54UMY'}\n","Grid 44\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762658123158, 'update_timestamp_ms': 1762658123158, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2BHSCBJJHNJROJBZP27657WY', 'name': 'projects/ext-datasets/operations/2BHSCBJJHNJROJBZP27657WY'}\n","Grid 45\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762658131902, 'update_timestamp_ms': 1762658131902, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2B2HHU7LRQCTGWJFIZ2B6IZO', 'name': 'projects/ext-datasets/operations/2B2HHU7LRQCTGWJFIZ2B6IZO'}\n","Grid 46\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762658139038, 'update_timestamp_ms': 1762658139038, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KSEBDSG3ESM6ZZH2YL5SRAFF', 'name': 'projects/ext-datasets/operations/KSEBDSG3ESM6ZZH2YL5SRAFF'}\n","Grid 47\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762658148373, 'update_timestamp_ms': 1762658148373, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LWBZ7EZT3JLKW4PLGKBSURAE', 'name': 'projects/ext-datasets/operations/LWBZ7EZT3JLKW4PLGKBSURAE'}\n","Grid 48\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762658156748, 'update_timestamp_ms': 1762658156748, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64YDSCGZFGFYXDYOVZCIVKPU', 'name': 'projects/ext-datasets/operations/64YDSCGZFGFYXDYOVZCIVKPU'}\n","Grid 49\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762658163124, 'update_timestamp_ms': 1762658163124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HCSBKBCRPOD66HQU62UXAIRW', 'name': 'projects/ext-datasets/operations/HCSBKBCRPOD66HQU62UXAIRW'}\n","Grid 50\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762658172736, 'update_timestamp_ms': 1762658172736, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6CANJ4KVKVSHZVFXDHJLQEU6', 'name': 'projects/ext-datasets/operations/6CANJ4KVKVSHZVFXDHJLQEU6'}\n","Grid 51\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762658183522, 'update_timestamp_ms': 1762658183522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E2XDHC6U6ONKZ4SSNF2HL5YZ', 'name': 'projects/ext-datasets/operations/E2XDHC6U6ONKZ4SSNF2HL5YZ'}\n","Grid 52\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762658189528, 'update_timestamp_ms': 1762658189528, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XS2IZR7Y5P26CONDEK2FNN4Y', 'name': 'projects/ext-datasets/operations/XS2IZR7Y5P26CONDEK2FNN4Y'}\n","Grid 53\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762658198178, 'update_timestamp_ms': 1762658198178, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q4R3ZNMSLY64M4M7YM7MPW5R', 'name': 'projects/ext-datasets/operations/Q4R3ZNMSLY64M4M7YM7MPW5R'}\n","Grid 54\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762658208255, 'update_timestamp_ms': 1762658208255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NB53FPAQI4R4LNJVGW5PEADO', 'name': 'projects/ext-datasets/operations/NB53FPAQI4R4LNJVGW5PEADO'}\n","Grid 55\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762658218559, 'update_timestamp_ms': 1762658218559, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L2LCIJDEPBN5XK76UJB2KDKC', 'name': 'projects/ext-datasets/operations/L2LCIJDEPBN5XK76UJB2KDKC'}\n","Grid 56\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762658226990, 'update_timestamp_ms': 1762658226990, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '256PZ72MZY26EZB4LZQJIIY3', 'name': 'projects/ext-datasets/operations/256PZ72MZY26EZB4LZQJIIY3'}\n","Grid 57\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762658238553, 'update_timestamp_ms': 1762658238553, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RPJZSCLQAV5FOUYTKI6XGIOP', 'name': 'projects/ext-datasets/operations/RPJZSCLQAV5FOUYTKI6XGIOP'}\n","Grid 58\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762658250452, 'update_timestamp_ms': 1762658250452, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYUVV2JI7FN5APGHMBZG5A2F', 'name': 'projects/ext-datasets/operations/XYUVV2JI7FN5APGHMBZG5A2F'}\n","Grid 59\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762658256418, 'update_timestamp_ms': 1762658256418, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IOCEQRFICZYF7LG5DET6G433', 'name': 'projects/ext-datasets/operations/IOCEQRFICZYF7LG5DET6G433'}\n","Grid 60\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762658270323, 'update_timestamp_ms': 1762658270323, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LZ4JFOGPOPC4NGO2Z4JUZM3R', 'name': 'projects/ext-datasets/operations/LZ4JFOGPOPC4NGO2Z4JUZM3R'}\n","Grid 61\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762658280593, 'update_timestamp_ms': 1762658280593, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MQMOCAX5X2U4L4IBBKMOHAXA', 'name': 'projects/ext-datasets/operations/MQMOCAX5X2U4L4IBBKMOHAXA'}\n","Grid 62\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762658292209, 'update_timestamp_ms': 1762658292209, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RJ5EVGBN54NAWMTPA7W5N77Z', 'name': 'projects/ext-datasets/operations/RJ5EVGBN54NAWMTPA7W5N77Z'}\n","Grid 63\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762658302123, 'update_timestamp_ms': 1762658302123, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TGFFL47X7YC6OWXJZQO2XQYE', 'name': 'projects/ext-datasets/operations/TGFFL47X7YC6OWXJZQO2XQYE'}\n","Grid 64\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762658310424, 'update_timestamp_ms': 1762658310424, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3RTHW772TDXSGOMGTPBPCIIU', 'name': 'projects/ext-datasets/operations/3RTHW772TDXSGOMGTPBPCIIU'}\n","Grid 65\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762658319876, 'update_timestamp_ms': 1762658319876, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LQUWEEH2X7ZAQHBVKLPWRBJZ', 'name': 'projects/ext-datasets/operations/LQUWEEH2X7ZAQHBVKLPWRBJZ'}\n","Grid 66\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762658325522, 'update_timestamp_ms': 1762658325522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ALNBVM2EM24TUHHAIC6MIZLY', 'name': 'projects/ext-datasets/operations/ALNBVM2EM24TUHHAIC6MIZLY'}\n","Grid 67\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762658333125, 'update_timestamp_ms': 1762658333125, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UJQJYFMAUBQTHMB3R6KHPBS7', 'name': 'projects/ext-datasets/operations/UJQJYFMAUBQTHMB3R6KHPBS7'}\n","Grid 68\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762658341309, 'update_timestamp_ms': 1762658341309, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4KRD7OSP4CGHHPVIJAW55W6F', 'name': 'projects/ext-datasets/operations/4KRD7OSP4CGHHPVIJAW55W6F'}\n","Grid 69\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762658355706, 'update_timestamp_ms': 1762658355706, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JEJYU2S7LQX42S26BTHTA2UL', 'name': 'projects/ext-datasets/operations/JEJYU2S7LQX42S26BTHTA2UL'}\n","Grid 70\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762658361264, 'update_timestamp_ms': 1762658361264, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F5NAZNHD5E34DLUFOUXSIA4U', 'name': 'projects/ext-datasets/operations/F5NAZNHD5E34DLUFOUXSIA4U'}\n","Grid 71\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762658366655, 'update_timestamp_ms': 1762658366655, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ULJKUUTZ2PG67WRCGZFFLDZK', 'name': 'projects/ext-datasets/operations/ULJKUUTZ2PG67WRCGZFFLDZK'}\n","Grid 72\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762658382194, 'update_timestamp_ms': 1762658382194, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VORWVY26L24WUPPRW3FXLSDD', 'name': 'projects/ext-datasets/operations/VORWVY26L24WUPPRW3FXLSDD'}\n","Grid 73\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762658392229, 'update_timestamp_ms': 1762658392229, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7FJPJMR5LMLUUJFYLDNASLOF', 'name': 'projects/ext-datasets/operations/7FJPJMR5LMLUUJFYLDNASLOF'}\n","Grid 74\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762658403918, 'update_timestamp_ms': 1762658403918, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KEITH5KA5SUJXJAUYW3ECCQA', 'name': 'projects/ext-datasets/operations/KEITH5KA5SUJXJAUYW3ECCQA'}\n","Grid 75\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762658411665, 'update_timestamp_ms': 1762658411665, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2VKDQRLVUCXGXKKKH6SVUZAA', 'name': 'projects/ext-datasets/operations/2VKDQRLVUCXGXKKKH6SVUZAA'}\n","Grid 76\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762658420266, 'update_timestamp_ms': 1762658420266, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HKGODGCCMJIDHJLSBJCQSLLZ', 'name': 'projects/ext-datasets/operations/HKGODGCCMJIDHJLSBJCQSLLZ'}\n","Grid 77\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762658431524, 'update_timestamp_ms': 1762658431524, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TNJ7E433ZSQRKG4LKN2IIVYM', 'name': 'projects/ext-datasets/operations/TNJ7E433ZSQRKG4LKN2IIVYM'}\n","Grid 78\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762658437803, 'update_timestamp_ms': 1762658437803, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4QLZZOBRVOCEZJOYK7FXDAFH', 'name': 'projects/ext-datasets/operations/4QLZZOBRVOCEZJOYK7FXDAFH'}\n","Grid 79\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762658446019, 'update_timestamp_ms': 1762658446019, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EP5FJKS4UFY7BRL5YYLOBTBY', 'name': 'projects/ext-datasets/operations/EP5FJKS4UFY7BRL5YYLOBTBY'}\n","Grid 80\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762658450410, 'update_timestamp_ms': 1762658450410, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TC2UYMSQ33VR6Y3FVWQOSGVI', 'name': 'projects/ext-datasets/operations/TC2UYMSQ33VR6Y3FVWQOSGVI'}\n","Grid 81\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762658455489, 'update_timestamp_ms': 1762658455489, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6MPIM32AWN5QJRZ3XFYVJ2MD', 'name': 'projects/ext-datasets/operations/6MPIM32AWN5QJRZ3XFYVJ2MD'}\n","Grid 82\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762658465865, 'update_timestamp_ms': 1762658465865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7N7K6WQUV5CCL637CV4JWXB5', 'name': 'projects/ext-datasets/operations/7N7K6WQUV5CCL637CV4JWXB5'}\n","Grid 83\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762658474279, 'update_timestamp_ms': 1762658474279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NPOFQ37SR5OCIZ5C3V6NHIH3', 'name': 'projects/ext-datasets/operations/NPOFQ37SR5OCIZ5C3V6NHIH3'}\n","Grid 84\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762658483657, 'update_timestamp_ms': 1762658483657, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HHMJILEVDQPYR3FOR52XBP3Y', 'name': 'projects/ext-datasets/operations/HHMJILEVDQPYR3FOR52XBP3Y'}\n","Grid 85\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762658494915, 'update_timestamp_ms': 1762658494915, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y2Q3XCWM7A7PRAZ5KT4ZDAV6', 'name': 'projects/ext-datasets/operations/Y2Q3XCWM7A7PRAZ5KT4ZDAV6'}\n","Grid 86\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762658503819, 'update_timestamp_ms': 1762658503819, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FPYYKEFO4KM3TXHPYYJ5I3GX', 'name': 'projects/ext-datasets/operations/FPYYKEFO4KM3TXHPYYJ5I3GX'}\n","Grid 87\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_87_2017', 'priority': 100, 'creation_timestamp_ms': 1762658508782, 'update_timestamp_ms': 1762658508782, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GEFDNCSDWP4JQDVDRNNMTJDA', 'name': 'projects/ext-datasets/operations/GEFDNCSDWP4JQDVDRNNMTJDA'}\n","Grid 88\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_88_2017', 'priority': 100, 'creation_timestamp_ms': 1762658516796, 'update_timestamp_ms': 1762658516796, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HGMZ4DUJBVWIFRR2ZFMZN2MN', 'name': 'projects/ext-datasets/operations/HGMZ4DUJBVWIFRR2ZFMZN2MN'}\n","Grid 89\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_89_2017', 'priority': 100, 'creation_timestamp_ms': 1762658522030, 'update_timestamp_ms': 1762658522030, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VO5ODDADPHD456GDIW3PKDPF', 'name': 'projects/ext-datasets/operations/VO5ODDADPHD456GDIW3PKDPF'}\n","Grid 90\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_90_2017', 'priority': 100, 'creation_timestamp_ms': 1762658531380, 'update_timestamp_ms': 1762658531380, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RI3OI4JF5LXQUBZZA2N3QSSA', 'name': 'projects/ext-datasets/operations/RI3OI4JF5LXQUBZZA2N3QSSA'}\n","Grid 91\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_91_2017', 'priority': 100, 'creation_timestamp_ms': 1762658537526, 'update_timestamp_ms': 1762658537526, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHYVHZDHXUO575ETOIOGZBR3', 'name': 'projects/ext-datasets/operations/YHYVHZDHXUO575ETOIOGZBR3'}\n","Grid 92\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_92_2017', 'priority': 100, 'creation_timestamp_ms': 1762658546056, 'update_timestamp_ms': 1762658546056, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DLENUAUSWOUI6FUO3NJK5S4X', 'name': 'projects/ext-datasets/operations/DLENUAUSWOUI6FUO3NJK5S4X'}\n","Grid 93\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_93_2017', 'priority': 100, 'creation_timestamp_ms': 1762658556367, 'update_timestamp_ms': 1762658556367, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKVBGIOHI3KOZG4AXLQJCA3V', 'name': 'projects/ext-datasets/operations/SKVBGIOHI3KOZG4AXLQJCA3V'}\n","Grid 94\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_94_2017', 'priority': 100, 'creation_timestamp_ms': 1762658566574, 'update_timestamp_ms': 1762658566574, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NBKVSQ6ACYIHYENK3RVO6PGH', 'name': 'projects/ext-datasets/operations/NBKVSQ6ACYIHYENK3RVO6PGH'}\n","Grid 95\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_95_2017', 'priority': 100, 'creation_timestamp_ms': 1762658570912, 'update_timestamp_ms': 1762658570912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5UZ57KTGRFOTCHMAP2XZN6XK', 'name': 'projects/ext-datasets/operations/5UZ57KTGRFOTCHMAP2XZN6XK'}\n","9024.560793161392\n","Year 2017, District 27: Uttar Dinajpur, grids: 66\n","Grid 0\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762658581084, 'update_timestamp_ms': 1762658581084, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LWBBTEE4OQIGGA2Y4G7QRL3B', 'name': 'projects/ext-datasets/operations/LWBBTEE4OQIGGA2Y4G7QRL3B'}\n","Grid 1\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762658588334, 'update_timestamp_ms': 1762658588334, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BHSAMUE5CFJZXG7SAJW5PYEC', 'name': 'projects/ext-datasets/operations/BHSAMUE5CFJZXG7SAJW5PYEC'}\n","Grid 2\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762658594508, 'update_timestamp_ms': 1762658594508, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJSG7LDRDUZP7PB2D6NVHWKZ', 'name': 'projects/ext-datasets/operations/HJSG7LDRDUZP7PB2D6NVHWKZ'}\n","Grid 3\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762658601690, 'update_timestamp_ms': 1762658601690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DWN4F3MWMNC55ZAXAXJHRGQH', 'name': 'projects/ext-datasets/operations/DWN4F3MWMNC55ZAXAXJHRGQH'}\n","Grid 4\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762658608815, 'update_timestamp_ms': 1762658608815, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GUB7CAGUPH4KVCETAVJWTDYO', 'name': 'projects/ext-datasets/operations/GUB7CAGUPH4KVCETAVJWTDYO'}\n","Grid 5\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762658615599, 'update_timestamp_ms': 1762658615599, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SLB2C4FUXIRXA6URE7QN4BTX', 'name': 'projects/ext-datasets/operations/SLB2C4FUXIRXA6URE7QN4BTX'}\n","Grid 6\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762658623281, 'update_timestamp_ms': 1762658623281, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7PD3J3HTW4S2CBVRTAC6FZU4', 'name': 'projects/ext-datasets/operations/7PD3J3HTW4S2CBVRTAC6FZU4'}\n","Grid 7\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762658630336, 'update_timestamp_ms': 1762658630336, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64FLPTQ3NXI36KU5PZCN5PS4', 'name': 'projects/ext-datasets/operations/64FLPTQ3NXI36KU5PZCN5PS4'}\n","Grid 8\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762658633947, 'update_timestamp_ms': 1762658633947, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C2EHZ5CD5I7HLQJQWH3LXVXV', 'name': 'projects/ext-datasets/operations/C2EHZ5CD5I7HLQJQWH3LXVXV'}\n","Grid 9\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762658642174, 'update_timestamp_ms': 1762658642174, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3HEMYXEBW3WHHPG42ITU2YJ', 'name': 'projects/ext-datasets/operations/K3HEMYXEBW3WHHPG42ITU2YJ'}\n","Grid 10\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762658650276, 'update_timestamp_ms': 1762658650276, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MXRWUZPDO5OBXYJ7WYSSA5Y3', 'name': 'projects/ext-datasets/operations/MXRWUZPDO5OBXYJ7WYSSA5Y3'}\n","Grid 11\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762658657079, 'update_timestamp_ms': 1762658657079, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G3TZ7XSDMMJ2GZRH5GST2NIK', 'name': 'projects/ext-datasets/operations/G3TZ7XSDMMJ2GZRH5GST2NIK'}\n","Grid 12\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762658663272, 'update_timestamp_ms': 1762658663272, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BN6YANDTBN5D4X2MHJCJLCDA', 'name': 'projects/ext-datasets/operations/BN6YANDTBN5D4X2MHJCJLCDA'}\n","Grid 13\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762658669155, 'update_timestamp_ms': 1762658669155, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VFIYOHFXLL4T5SAFBKZ7UVIE', 'name': 'projects/ext-datasets/operations/VFIYOHFXLL4T5SAFBKZ7UVIE'}\n","Grid 14\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762658676404, 'update_timestamp_ms': 1762658676404, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KHQJVAUYSK5VIX3LBZXM2L5V', 'name': 'projects/ext-datasets/operations/KHQJVAUYSK5VIX3LBZXM2L5V'}\n","Grid 15\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762658684794, 'update_timestamp_ms': 1762658684794, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37USBI46MJ7PZVUQARNBFLYH', 'name': 'projects/ext-datasets/operations/37USBI46MJ7PZVUQARNBFLYH'}\n","Grid 16\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762658691124, 'update_timestamp_ms': 1762658691124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '27JZ2HGHE3UX2GM74QLKCOEF', 'name': 'projects/ext-datasets/operations/27JZ2HGHE3UX2GM74QLKCOEF'}\n","Grid 17\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762658697706, 'update_timestamp_ms': 1762658697706, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JADLTSZXIRCL2EW6ZCWMLMNQ', 'name': 'projects/ext-datasets/operations/JADLTSZXIRCL2EW6ZCWMLMNQ'}\n","Grid 18\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762658703798, 'update_timestamp_ms': 1762658703798, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N7LJ5V3OD3SJ6BLRH2UH4YJ7', 'name': 'projects/ext-datasets/operations/N7LJ5V3OD3SJ6BLRH2UH4YJ7'}\n","Grid 19\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762658709708, 'update_timestamp_ms': 1762658709708, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NPAHS4K5NYZX3ELW74FKPBMW', 'name': 'projects/ext-datasets/operations/NPAHS4K5NYZX3ELW74FKPBMW'}\n","Grid 20\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762658716807, 'update_timestamp_ms': 1762658716807, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXDUGDJI5HVT27NP6X7KW2G7', 'name': 'projects/ext-datasets/operations/WXDUGDJI5HVT27NP6X7KW2G7'}\n","Grid 21\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762658723206, 'update_timestamp_ms': 1762658723206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VVS75FZPKQVCHGVWSDTFLPOK', 'name': 'projects/ext-datasets/operations/VVS75FZPKQVCHGVWSDTFLPOK'}\n","Grid 22\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762658730930, 'update_timestamp_ms': 1762658730930, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GCKIQVZTHE76DTQVIC35YHHI', 'name': 'projects/ext-datasets/operations/GCKIQVZTHE76DTQVIC35YHHI'}\n","Grid 23\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762658739413, 'update_timestamp_ms': 1762658739413, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RQZFSXT2DG7GTPGPUDPDC5UU', 'name': 'projects/ext-datasets/operations/RQZFSXT2DG7GTPGPUDPDC5UU'}\n","Grid 24\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762658746912, 'update_timestamp_ms': 1762658746912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NKGQ373GYKR2GMNPF2GLAJJ', 'name': 'projects/ext-datasets/operations/4NKGQ373GYKR2GMNPF2GLAJJ'}\n","Grid 25\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762658750696, 'update_timestamp_ms': 1762658750696, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JHI6WM74MZ42YEQ733U462QE', 'name': 'projects/ext-datasets/operations/JHI6WM74MZ42YEQ733U462QE'}\n","Grid 26\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762658758452, 'update_timestamp_ms': 1762658758452, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UUGIT6D53J6YJ7XE2LDEPGTE', 'name': 'projects/ext-datasets/operations/UUGIT6D53J6YJ7XE2LDEPGTE'}\n","Grid 27\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762658766194, 'update_timestamp_ms': 1762658766194, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GDHLVVLG5UMB6LZEQZDF2FNU', 'name': 'projects/ext-datasets/operations/GDHLVVLG5UMB6LZEQZDF2FNU'}\n","Grid 28\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762658774377, 'update_timestamp_ms': 1762658774377, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GWI2DLEAB62E6LCEW5LBHAO5', 'name': 'projects/ext-datasets/operations/GWI2DLEAB62E6LCEW5LBHAO5'}\n","Grid 29\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762658782303, 'update_timestamp_ms': 1762658782303, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CBYE4GRNC6K7QCDMSAPYHT5S', 'name': 'projects/ext-datasets/operations/CBYE4GRNC6K7QCDMSAPYHT5S'}\n","Grid 30\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762658785716, 'update_timestamp_ms': 1762658785716, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NLNMCX7B7Z5VVFCXGAIWR33', 'name': 'projects/ext-datasets/operations/4NLNMCX7B7Z5VVFCXGAIWR33'}\n","Grid 31\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762658789331, 'update_timestamp_ms': 1762658789331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WMUMCQIC5YE4QL3BVUO3IFQK', 'name': 'projects/ext-datasets/operations/WMUMCQIC5YE4QL3BVUO3IFQK'}\n","Grid 32\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762658796969, 'update_timestamp_ms': 1762658796969, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IYPPIKH6YWZC35QORRFLVMQ3', 'name': 'projects/ext-datasets/operations/IYPPIKH6YWZC35QORRFLVMQ3'}\n","Grid 33\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762658803366, 'update_timestamp_ms': 1762658803366, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JBCZMUD2BSIALH3AFWH5ZLKC', 'name': 'projects/ext-datasets/operations/JBCZMUD2BSIALH3AFWH5ZLKC'}\n","Grid 34\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762658809327, 'update_timestamp_ms': 1762658809327, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JT3RHD7AG6MVTDFV5V3WOXL7', 'name': 'projects/ext-datasets/operations/JT3RHD7AG6MVTDFV5V3WOXL7'}\n","Grid 35\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762658813413, 'update_timestamp_ms': 1762658813413, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7UOFD5XSRLBJS632WJQ4CZBA', 'name': 'projects/ext-datasets/operations/7UOFD5XSRLBJS632WJQ4CZBA'}\n","Grid 36\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762658821265, 'update_timestamp_ms': 1762658821265, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GVZXFZ6IJZGHRGPPXMGBSNUE', 'name': 'projects/ext-datasets/operations/GVZXFZ6IJZGHRGPPXMGBSNUE'}\n","Grid 37\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762658828449, 'update_timestamp_ms': 1762658828449, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MSAJDQF7DT75USRI3SOVBJJC', 'name': 'projects/ext-datasets/operations/MSAJDQF7DT75USRI3SOVBJJC'}\n","Grid 38\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762658837439, 'update_timestamp_ms': 1762658837439, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JFEWEUKOQQD625JX3LLCJOND', 'name': 'projects/ext-datasets/operations/JFEWEUKOQQD625JX3LLCJOND'}\n","Grid 39\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762658844483, 'update_timestamp_ms': 1762658844483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OKNWNZH6N7WXC6JZOJ3IYXZQ', 'name': 'projects/ext-datasets/operations/OKNWNZH6N7WXC6JZOJ3IYXZQ'}\n","Grid 40\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762658850942, 'update_timestamp_ms': 1762658850942, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AGQGE46OLJA2Q57YAUBTBOTO', 'name': 'projects/ext-datasets/operations/AGQGE46OLJA2Q57YAUBTBOTO'}\n","Grid 41\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762658855118, 'update_timestamp_ms': 1762658855118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7TALTC5S5CMHMX5QSRE33QOY', 'name': 'projects/ext-datasets/operations/7TALTC5S5CMHMX5QSRE33QOY'}\n","Grid 42\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762658861803, 'update_timestamp_ms': 1762658861803, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FNIC44O2HXFHWDANPRNECDFZ', 'name': 'projects/ext-datasets/operations/FNIC44O2HXFHWDANPRNECDFZ'}\n","Grid 43\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762658865791, 'update_timestamp_ms': 1762658865791, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7APNDRV5FSXSL5RFPHI4XWU3', 'name': 'projects/ext-datasets/operations/7APNDRV5FSXSL5RFPHI4XWU3'}\n","Grid 44\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762658872691, 'update_timestamp_ms': 1762658872691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I5VL7VQMHOJJH22N3WBRKBVF', 'name': 'projects/ext-datasets/operations/I5VL7VQMHOJJH22N3WBRKBVF'}\n","Grid 45\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762658879662, 'update_timestamp_ms': 1762658879662, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GXAMQXEATIK6LMLCEQZTNFED', 'name': 'projects/ext-datasets/operations/GXAMQXEATIK6LMLCEQZTNFED'}\n","Grid 46\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762658888622, 'update_timestamp_ms': 1762658888622, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JV2Z6W5AYFCKYAFMUI4MBUD7', 'name': 'projects/ext-datasets/operations/JV2Z6W5AYFCKYAFMUI4MBUD7'}\n","Grid 47\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762658892549, 'update_timestamp_ms': 1762658892549, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2BF62FDMDSTNPOG3X7VVX2LD', 'name': 'projects/ext-datasets/operations/2BF62FDMDSTNPOG3X7VVX2LD'}\n","Grid 48\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762658900175, 'update_timestamp_ms': 1762658900175, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WNBRTHWUPCGZNG6FGEBD6CRE', 'name': 'projects/ext-datasets/operations/WNBRTHWUPCGZNG6FGEBD6CRE'}\n","Grid 49\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762658907619, 'update_timestamp_ms': 1762658907619, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DN2S6XIWJLNH5BR7XTXH5347', 'name': 'projects/ext-datasets/operations/DN2S6XIWJLNH5BR7XTXH5347'}\n","Grid 50\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762658917967, 'update_timestamp_ms': 1762658917967, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HUUEVATPM33A4GCBAXMVGDY5', 'name': 'projects/ext-datasets/operations/HUUEVATPM33A4GCBAXMVGDY5'}\n","Grid 51\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762658927085, 'update_timestamp_ms': 1762658927085, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F7K2YZTMLLWN36CJFTEYXSTQ', 'name': 'projects/ext-datasets/operations/F7K2YZTMLLWN36CJFTEYXSTQ'}\n","Grid 52\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762658933729, 'update_timestamp_ms': 1762658933729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RQL6KODKDQADPIVIYZOE6PSW', 'name': 'projects/ext-datasets/operations/RQL6KODKDQADPIVIYZOE6PSW'}\n","Grid 53\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762658940194, 'update_timestamp_ms': 1762658940194, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K44YFWNUH4QCSZL3SDBFRCZL', 'name': 'projects/ext-datasets/operations/K44YFWNUH4QCSZL3SDBFRCZL'}\n","Grid 54\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762658947968, 'update_timestamp_ms': 1762658947968, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T5NSIUR2WZQ3U4V2RCNMGSP4', 'name': 'projects/ext-datasets/operations/T5NSIUR2WZQ3U4V2RCNMGSP4'}\n","Grid 55\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762658953425, 'update_timestamp_ms': 1762658953425, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SZ6MS7SNXMTXJZW746CFWWGV', 'name': 'projects/ext-datasets/operations/SZ6MS7SNXMTXJZW746CFWWGV'}\n","Grid 56\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762658961614, 'update_timestamp_ms': 1762658961614, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AJTUSASAQMI7U4YPJJYT4MZO', 'name': 'projects/ext-datasets/operations/AJTUSASAQMI7U4YPJJYT4MZO'}\n","Grid 57\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762658967654, 'update_timestamp_ms': 1762658967654, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BILBZACEENGO3RQ6KYFOWFIA', 'name': 'projects/ext-datasets/operations/BILBZACEENGO3RQ6KYFOWFIA'}\n","Grid 58\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762658975388, 'update_timestamp_ms': 1762658975388, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37DNBONKMZIWZBDFL5BB5SKB', 'name': 'projects/ext-datasets/operations/37DNBONKMZIWZBDFL5BB5SKB'}\n","Grid 59\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762658982841, 'update_timestamp_ms': 1762658982841, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QCBCXIOSOXSCIJD65Z5RGS37', 'name': 'projects/ext-datasets/operations/QCBCXIOSOXSCIJD65Z5RGS37'}\n","Grid 60\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762658990571, 'update_timestamp_ms': 1762658990571, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FB73R4TB2R3G4GJF6LSHX3ZO', 'name': 'projects/ext-datasets/operations/FB73R4TB2R3G4GJF6LSHX3ZO'}\n","Grid 61\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762658998607, 'update_timestamp_ms': 1762658998607, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZIRZZXEGFVINO7RXYB2LTBM2', 'name': 'projects/ext-datasets/operations/ZIRZZXEGFVINO7RXYB2LTBM2'}\n","Grid 62\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762659006326, 'update_timestamp_ms': 1762659006326, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64BFXGRHWSYYKXTST73LWYTW', 'name': 'projects/ext-datasets/operations/64BFXGRHWSYYKXTST73LWYTW'}\n","Grid 63\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762659013764, 'update_timestamp_ms': 1762659013764, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HH6Y7YEXLISJDIJNZFKMPOEO', 'name': 'projects/ext-datasets/operations/HH6Y7YEXLISJDIJNZFKMPOEO'}\n","Grid 64\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762659020719, 'update_timestamp_ms': 1762659020719, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XQQ6EU4UYTNRJODBMWAAFA4P', 'name': 'projects/ext-datasets/operations/XQQ6EU4UYTNRJODBMWAAFA4P'}\n","Grid 65\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762659024344, 'update_timestamp_ms': 1762659024344, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6TVDYRTPWWZMLGBIE72ZVFYF', 'name': 'projects/ext-datasets/operations/6TVDYRTPWWZMLGBIE72ZVFYF'}\n","9478.025090694427\n","Waiting for last task to be completed...\n","Last task completed!\n","Total Time Taken: 11691.838443040848\n","Year 2018, District 0: Katihar, grids: 19\n","Grid 0\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661252729, 'update_timestamp_ms': 1762661252729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QZYGCE6PYALXOSC6G5TLKOI6', 'name': 'projects/ext-datasets/operations/QZYGCE6PYALXOSC6G5TLKOI6'}\n","Grid 1\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661259693, 'update_timestamp_ms': 1762661259693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L3CIIC4YVERUFHLZNGFQEM4U', 'name': 'projects/ext-datasets/operations/L3CIIC4YVERUFHLZNGFQEM4U'}\n","Grid 2\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661267234, 'update_timestamp_ms': 1762661267234, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GOOX3NPYWEG6DDKPVNUKDJMB', 'name': 'projects/ext-datasets/operations/GOOX3NPYWEG6DDKPVNUKDJMB'}\n","Grid 3\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661274822, 'update_timestamp_ms': 1762661274822, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7ENBZUSAEQTSFS5C5YYK3KD3', 'name': 'projects/ext-datasets/operations/7ENBZUSAEQTSFS5C5YYK3KD3'}\n","Grid 4\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661280196, 'update_timestamp_ms': 1762661280196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7TAH2FLF5OCUW3YH4NNB2DHK', 'name': 'projects/ext-datasets/operations/7TAH2FLF5OCUW3YH4NNB2DHK'}\n","Grid 5\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661287279, 'update_timestamp_ms': 1762661287279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2BAQGYWIEAEMIGF6FN5AHHYG', 'name': 'projects/ext-datasets/operations/2BAQGYWIEAEMIGF6FN5AHHYG'}\n","Grid 6\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661296008, 'update_timestamp_ms': 1762661296008, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FTW23QJPOIKIX2VXB2GNIEKH', 'name': 'projects/ext-datasets/operations/FTW23QJPOIKIX2VXB2GNIEKH'}\n","Grid 7\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661305453, 'update_timestamp_ms': 1762661305453, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CD4HQSTQX6J3TRY4YPIPGVKB', 'name': 'projects/ext-datasets/operations/CD4HQSTQX6J3TRY4YPIPGVKB'}\n","Grid 8\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661314221, 'update_timestamp_ms': 1762661314221, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H4PRAJ3M6CCRVVSEMVIXFH7W', 'name': 'projects/ext-datasets/operations/H4PRAJ3M6CCRVVSEMVIXFH7W'}\n","Grid 9\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661321253, 'update_timestamp_ms': 1762661321253, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJFXG5X4NLN4L6LWRJTM3VJO', 'name': 'projects/ext-datasets/operations/HJFXG5X4NLN4L6LWRJTM3VJO'}\n","Grid 10\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762661332720, 'update_timestamp_ms': 1762661332720, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NU63UW3MPLA4JJGQQRKOSE6M', 'name': 'projects/ext-datasets/operations/NU63UW3MPLA4JJGQQRKOSE6M'}\n","Grid 11\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762661341394, 'update_timestamp_ms': 1762661341394, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WY3BYQEUH54SKBD45DIS34CA', 'name': 'projects/ext-datasets/operations/WY3BYQEUH54SKBD45DIS34CA'}\n","Grid 12\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762661352695, 'update_timestamp_ms': 1762661352695, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7OJ5DW76UPJK4UDHOHAWSVC6', 'name': 'projects/ext-datasets/operations/7OJ5DW76UPJK4UDHOHAWSVC6'}\n","Grid 13\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762661359445, 'update_timestamp_ms': 1762661359445, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2WSTPBBVG46QES57363AHD7H', 'name': 'projects/ext-datasets/operations/2WSTPBBVG46QES57363AHD7H'}\n","Grid 14\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762661366850, 'update_timestamp_ms': 1762661366850, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CAGRXMPLNFMCRNIYR3NL4LQ6', 'name': 'projects/ext-datasets/operations/CAGRXMPLNFMCRNIYR3NL4LQ6'}\n","Grid 15\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762661372170, 'update_timestamp_ms': 1762661372170, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2ZGWCRGI3FMR4KUP7FNKGKYU', 'name': 'projects/ext-datasets/operations/2ZGWCRGI3FMR4KUP7FNKGKYU'}\n","Grid 16\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762661382418, 'update_timestamp_ms': 1762661382418, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RHSDM6ITGIDAIEJLVYOKERGB', 'name': 'projects/ext-datasets/operations/RHSDM6ITGIDAIEJLVYOKERGB'}\n","Grid 17\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762661390989, 'update_timestamp_ms': 1762661390989, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M2Y3DIHVC2HQBLRT4CV5N34Y', 'name': 'projects/ext-datasets/operations/M2Y3DIHVC2HQBLRT4CV5N34Y'}\n","Grid 18\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762661401527, 'update_timestamp_ms': 1762661401527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EWPEIRAXHB7QVSU36K4VV4TF', 'name': 'projects/ext-datasets/operations/EWPEIRAXHB7QVSU36K4VV4TF'}\n","163.33016920089722\n","Year 2018, District 1: Kishanganj, grids: 10\n","Grid 0\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661415169, 'update_timestamp_ms': 1762661415169, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TYPM26KHDKOTX5PMCOW4H5ZB', 'name': 'projects/ext-datasets/operations/TYPM26KHDKOTX5PMCOW4H5ZB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661421109, 'update_timestamp_ms': 1762661421109, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J7TRPDWY6BSAWHBDSV24FN6E', 'name': 'projects/ext-datasets/operations/J7TRPDWY6BSAWHBDSV24FN6E'}\n","Grid 2\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661428144, 'update_timestamp_ms': 1762661428144, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BUOJ7O7EF34JDCMOEVJSN2B7', 'name': 'projects/ext-datasets/operations/BUOJ7O7EF34JDCMOEVJSN2B7'}\n","Grid 3\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661432097, 'update_timestamp_ms': 1762661432097, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2MN4L7QBRR2JYQDFGCHRCNIV', 'name': 'projects/ext-datasets/operations/2MN4L7QBRR2JYQDFGCHRCNIV'}\n","Grid 4\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661441672, 'update_timestamp_ms': 1762661441672, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XFPIJKAZOPHDNLTCO667NCMM', 'name': 'projects/ext-datasets/operations/XFPIJKAZOPHDNLTCO667NCMM'}\n","Grid 5\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661446896, 'update_timestamp_ms': 1762661446896, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E5MGGGYFNMJFD3CQG77ZXZVN', 'name': 'projects/ext-datasets/operations/E5MGGGYFNMJFD3CQG77ZXZVN'}\n","Grid 6\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661454885, 'update_timestamp_ms': 1762661454885, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2RMT6KWPNLRPZK6UHA5CZV4G', 'name': 'projects/ext-datasets/operations/2RMT6KWPNLRPZK6UHA5CZV4G'}\n","Grid 7\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661461899, 'update_timestamp_ms': 1762661461899, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y3DACYNR4M7LWTQBJZTSHSMM', 'name': 'projects/ext-datasets/operations/Y3DACYNR4M7LWTQBJZTSHSMM'}\n","Grid 8\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661465511, 'update_timestamp_ms': 1762661465511, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFY7YVTGEWUCBE23HC6MBRTJ', 'name': 'projects/ext-datasets/operations/IFY7YVTGEWUCBE23HC6MBRTJ'}\n","Grid 9\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661472919, 'update_timestamp_ms': 1762661472919, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BSACOQVJ5JRL55WG3XCZKURD', 'name': 'projects/ext-datasets/operations/BSACOQVJ5JRL55WG3XCZKURD'}\n","234.68766856193542\n","Year 2018, District 2: Purnia, grids: 1\n","Grid 0\n","curr_year 2018\n","Saving data for Purnia 2018\n","Task Started {'state': 'READY', 'description': 'Purnia_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661485177, 'update_timestamp_ms': 1762661485177, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LM7HP5TLZDFEPZ5JILM3GOTQ', 'name': 'projects/ext-datasets/operations/LM7HP5TLZDFEPZ5JILM3GOTQ'}\n","247.0161051750183\n","Year 2018, District 3: Dhanbad, grids: 1\n","Grid 0\n","curr_year 2018\n","Saving data for Dhanbad 2018\n","Task Started {'state': 'READY', 'description': 'Dhanbad_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661499029, 'update_timestamp_ms': 1762661499029, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RRQEKBZFOZAUYQGOOFRTJZKW', 'name': 'projects/ext-datasets/operations/RRQEKBZFOZAUYQGOOFRTJZKW'}\n","260.836279630661\n","Year 2018, District 4: Dumka, grids: 10\n","Grid 0\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661514902, 'update_timestamp_ms': 1762661514902, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QOHMMPKJFBL62RJZT34C2KWO', 'name': 'projects/ext-datasets/operations/QOHMMPKJFBL62RJZT34C2KWO'}\n","Grid 1\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661518980, 'update_timestamp_ms': 1762661518980, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WUDELHZTOL7ULYA6E6BAMJCG', 'name': 'projects/ext-datasets/operations/WUDELHZTOL7ULYA6E6BAMJCG'}\n","Grid 2\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661530802, 'update_timestamp_ms': 1762661530802, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y22SNWU3GELIZSVXNANU6T7T', 'name': 'projects/ext-datasets/operations/Y22SNWU3GELIZSVXNANU6T7T'}\n","Grid 3\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661538964, 'update_timestamp_ms': 1762661538964, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NEVS5TJX4XXIFSUUR2X5XBRD', 'name': 'projects/ext-datasets/operations/NEVS5TJX4XXIFSUUR2X5XBRD'}\n","Grid 4\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661545993, 'update_timestamp_ms': 1762661545993, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PEFVP6IGH6UZZW4OUNRWHONY', 'name': 'projects/ext-datasets/operations/PEFVP6IGH6UZZW4OUNRWHONY'}\n","Grid 5\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661552503, 'update_timestamp_ms': 1762661552503, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4GHVZWFUEPUIF26V7XEFCLXS', 'name': 'projects/ext-datasets/operations/4GHVZWFUEPUIF26V7XEFCLXS'}\n","Grid 6\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661561607, 'update_timestamp_ms': 1762661561607, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W4JBXELP2EUVFLWBYAGIOHBU', 'name': 'projects/ext-datasets/operations/W4JBXELP2EUVFLWBYAGIOHBU'}\n","Grid 7\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661572246, 'update_timestamp_ms': 1762661572246, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TUCB6WINJJKOI4VLONISQKRE', 'name': 'projects/ext-datasets/operations/TUCB6WINJJKOI4VLONISQKRE'}\n","Grid 8\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661579463, 'update_timestamp_ms': 1762661579463, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZTFPY63GXGI3H7VTPYAYUR6', 'name': 'projects/ext-datasets/operations/KZTFPY63GXGI3H7VTPYAYUR6'}\n","Grid 9\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661587387, 'update_timestamp_ms': 1762661587387, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2Q4RZNFEG6UUONYB2X732F2U', 'name': 'projects/ext-datasets/operations/2Q4RZNFEG6UUONYB2X732F2U'}\n","349.2098460197449\n","Year 2018, District 5: Jamtara, grids: 10\n","Grid 0\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661600247, 'update_timestamp_ms': 1762661600247, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJXDMS2BXKXQ2L23P7KNW7XZ', 'name': 'projects/ext-datasets/operations/YJXDMS2BXKXQ2L23P7KNW7XZ'}\n","Grid 1\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661606632, 'update_timestamp_ms': 1762661606632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T5G3XPYJXY5QKXTEA7ZR25GS', 'name': 'projects/ext-datasets/operations/T5G3XPYJXY5QKXTEA7ZR25GS'}\n","Grid 2\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661613674, 'update_timestamp_ms': 1762661613674, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZD2QQCCV2JDR5BIHPHBQ7IE7', 'name': 'projects/ext-datasets/operations/ZD2QQCCV2JDR5BIHPHBQ7IE7'}\n","Grid 3\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661618531, 'update_timestamp_ms': 1762661618531, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MHWU4GXXNN72JNKE22DR6YGW', 'name': 'projects/ext-datasets/operations/MHWU4GXXNN72JNKE22DR6YGW'}\n","Grid 4\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661624769, 'update_timestamp_ms': 1762661624769, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CST5KGZVVXEBKR2HKWIY7AKU', 'name': 'projects/ext-datasets/operations/CST5KGZVVXEBKR2HKWIY7AKU'}\n","Grid 5\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661633742, 'update_timestamp_ms': 1762661633742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ONT23X7JJYEXWUHRKMZ2GZ35', 'name': 'projects/ext-datasets/operations/ONT23X7JJYEXWUHRKMZ2GZ35'}\n","Grid 6\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661639755, 'update_timestamp_ms': 1762661639755, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6KMZH673OJ5AX24CK3EB2QK', 'name': 'projects/ext-datasets/operations/X6KMZH673OJ5AX24CK3EB2QK'}\n","Grid 7\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661647080, 'update_timestamp_ms': 1762661647080, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7RHETXCLH4HDS2MEHSLAHEMJ', 'name': 'projects/ext-datasets/operations/7RHETXCLH4HDS2MEHSLAHEMJ'}\n","Grid 8\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661653205, 'update_timestamp_ms': 1762661653205, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZRHL4I7DKZ6SBAW5PQPZ4YE4', 'name': 'projects/ext-datasets/operations/ZRHL4I7DKZ6SBAW5PQPZ4YE4'}\n","Grid 9\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661660269, 'update_timestamp_ms': 1762661660269, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X3FZARIGPQAZS47Z2IR2TAZ7', 'name': 'projects/ext-datasets/operations/X3FZARIGPQAZS47Z2IR2TAZ7'}\n","422.12399768829346\n","Year 2018, District 6: Pakur, grids: 9\n","Grid 0\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661674965, 'update_timestamp_ms': 1762661674965, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E7QLVYQOYAQJ3B5GOVQ6YYGB', 'name': 'projects/ext-datasets/operations/E7QLVYQOYAQJ3B5GOVQ6YYGB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661682558, 'update_timestamp_ms': 1762661682558, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7B5AQSQKKOSPEXVXYXS6PC25', 'name': 'projects/ext-datasets/operations/7B5AQSQKKOSPEXVXYXS6PC25'}\n","Grid 2\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661690346, 'update_timestamp_ms': 1762661690346, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UNZA2L3HLU6URA3K7HDTPPVP', 'name': 'projects/ext-datasets/operations/UNZA2L3HLU6URA3K7HDTPPVP'}\n","Grid 3\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661697664, 'update_timestamp_ms': 1762661697664, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MT66LGWDTK4UX7DMVMUU5SS2', 'name': 'projects/ext-datasets/operations/MT66LGWDTK4UX7DMVMUU5SS2'}\n","Grid 4\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661704964, 'update_timestamp_ms': 1762661704964, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LBFAI47IEXNQ5TSK2J45QCY2', 'name': 'projects/ext-datasets/operations/LBFAI47IEXNQ5TSK2J45QCY2'}\n","Grid 5\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661712665, 'update_timestamp_ms': 1762661712665, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FIULRPUSO3BYRBXQFOLOEQDG', 'name': 'projects/ext-datasets/operations/FIULRPUSO3BYRBXQFOLOEQDG'}\n","Grid 6\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661720057, 'update_timestamp_ms': 1762661720057, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ERQPB7GQQGXTWY45ZE6CGYQF', 'name': 'projects/ext-datasets/operations/ERQPB7GQQGXTWY45ZE6CGYQF'}\n","Grid 7\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661726493, 'update_timestamp_ms': 1762661726493, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QSLTLOQ7I2UDADZHY6XXYYRK', 'name': 'projects/ext-datasets/operations/QSLTLOQ7I2UDADZHY6XXYYRK'}\n","Grid 8\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661732594, 'update_timestamp_ms': 1762661732594, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMYAEH5TNIX3ZSDJG7QUVLKE', 'name': 'projects/ext-datasets/operations/BMYAEH5TNIX3ZSDJG7QUVLKE'}\n","494.41910696029663\n","Year 2018, District 7: Purbi Singhbhum, grids: 5\n","Grid 0\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661746443, 'update_timestamp_ms': 1762661746443, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GSWALEKV4U7XTT44QJN4WEJF', 'name': 'projects/ext-datasets/operations/GSWALEKV4U7XTT44QJN4WEJF'}\n","Grid 1\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661755192, 'update_timestamp_ms': 1762661755192, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MW53TQGCIDDQMMB4YP54N2JK', 'name': 'projects/ext-datasets/operations/MW53TQGCIDDQMMB4YP54N2JK'}\n","Grid 2\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661762624, 'update_timestamp_ms': 1762661762624, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WKY2SNAKLC746OIREBOGKAAO', 'name': 'projects/ext-datasets/operations/WKY2SNAKLC746OIREBOGKAAO'}\n","Grid 3\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661770052, 'update_timestamp_ms': 1762661770052, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DGZRO2CORNNYUFATKIO5PNBT', 'name': 'projects/ext-datasets/operations/DGZRO2CORNNYUFATKIO5PNBT'}\n","Grid 4\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661774151, 'update_timestamp_ms': 1762661774151, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RFKUIDHDBBG3WU6C27YSXET4', 'name': 'projects/ext-datasets/operations/RFKUIDHDBBG3WU6C27YSXET4'}\n","535.9720237255096\n","Year 2018, District 8: Sahibganj, grids: 3\n","Grid 0\n","curr_year 2018\n","Saving data for Sahibganj 2018\n","Task Started {'state': 'READY', 'description': 'Sahibganj_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661786676, 'update_timestamp_ms': 1762661786676, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OQIR2E6NJ65MF6PGXJWYWN7A', 'name': 'projects/ext-datasets/operations/OQIR2E6NJ65MF6PGXJWYWN7A'}\n","Grid 1\n","curr_year 2018\n","Saving data for Sahibganj 2018\n","Task Started {'state': 'READY', 'description': 'Sahibganj_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661793641, 'update_timestamp_ms': 1762661793641, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SFO53APFK6KST2IDAJ4357TS', 'name': 'projects/ext-datasets/operations/SFO53APFK6KST2IDAJ4357TS'}\n","Grid 2\n","curr_year 2018\n","Saving data for Sahibganj 2018\n","Task Started {'state': 'READY', 'description': 'Sahibganj_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661797304, 'update_timestamp_ms': 1762661797304, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D5OPSTNUNJUCQVDPLYN4F5D7', 'name': 'projects/ext-datasets/operations/D5OPSTNUNJUCQVDPLYN4F5D7'}\n","559.1519672870636\n","Year 2018, District 9: Baleshwar, grids: 6\n","Grid 0\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661811076, 'update_timestamp_ms': 1762661811076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TDYSXAPAI3DCCCPVWMQMYKBB', 'name': 'projects/ext-datasets/operations/TDYSXAPAI3DCCCPVWMQMYKBB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661817544, 'update_timestamp_ms': 1762661817544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B7WL3G2LPT3AGTBTPUFTR5WV', 'name': 'projects/ext-datasets/operations/B7WL3G2LPT3AGTBTPUFTR5WV'}\n","Grid 2\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661824254, 'update_timestamp_ms': 1762661824254, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B2YI4U7QZLL2EP5WK5K44QX2', 'name': 'projects/ext-datasets/operations/B2YI4U7QZLL2EP5WK5K44QX2'}\n","Grid 3\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661831304, 'update_timestamp_ms': 1762661831304, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y7SNA5MEAGB7OVYCJWYGQBD4', 'name': 'projects/ext-datasets/operations/Y7SNA5MEAGB7OVYCJWYGQBD4'}\n","Grid 4\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661836824, 'update_timestamp_ms': 1762661836824, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VWSA2I55M76LO5CYQRSNPGRZ', 'name': 'projects/ext-datasets/operations/VWSA2I55M76LO5CYQRSNPGRZ'}\n","Grid 5\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661844389, 'update_timestamp_ms': 1762661844389, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2NP4JS264MKWYZS3M76QIJO5', 'name': 'projects/ext-datasets/operations/2NP4JS264MKWYZS3M76QIJO5'}\n","606.1991579532623\n","Year 2018, District 10: Mayurbhanj, grids: 1\n","Grid 0\n","curr_year 2018\n","Saving data for Mayurbhanj 2018\n","Task Started {'state': 'READY', 'description': 'Mayurbhanj_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661856879, 'update_timestamp_ms': 1762661856879, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R5CLPSQU5NDLKRR325ZAUNXC', 'name': 'projects/ext-datasets/operations/R5CLPSQU5NDLKRR325ZAUNXC'}\n","618.6984307765961\n","Year 2018, District 11: Bankura, grids: 103\n","Grid 0\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661869759, 'update_timestamp_ms': 1762661869759, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'STAYYI6TSZPO5B4SQ6VC75WZ', 'name': 'projects/ext-datasets/operations/STAYYI6TSZPO5B4SQ6VC75WZ'}\n","Grid 1\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661876752, 'update_timestamp_ms': 1762661876752, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '72IZWFUBEQ7A65W3ZWXIRW3G', 'name': 'projects/ext-datasets/operations/72IZWFUBEQ7A65W3ZWXIRW3G'}\n","Grid 2\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661883727, 'update_timestamp_ms': 1762661883727, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZRN6K3MXNMIWACKXAPDLDXJC', 'name': 'projects/ext-datasets/operations/ZRN6K3MXNMIWACKXAPDLDXJC'}\n","Grid 3\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661894436, 'update_timestamp_ms': 1762661894436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UCNY4CKHOYZXT453ZSL73MY5', 'name': 'projects/ext-datasets/operations/UCNY4CKHOYZXT453ZSL73MY5'}\n","Grid 4\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661906210, 'update_timestamp_ms': 1762661906210, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MYRHJGIENHTG5AU4ES6WRCLC', 'name': 'projects/ext-datasets/operations/MYRHJGIENHTG5AU4ES6WRCLC'}\n","Grid 5\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661914342, 'update_timestamp_ms': 1762661914342, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LLZCTPRO3HBFQL5YWECLYMBP', 'name': 'projects/ext-datasets/operations/LLZCTPRO3HBFQL5YWECLYMBP'}\n","Grid 6\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661922702, 'update_timestamp_ms': 1762661922702, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JN5IV7RAI5ZBV26EWSS2UGJ2', 'name': 'projects/ext-datasets/operations/JN5IV7RAI5ZBV26EWSS2UGJ2'}\n","Grid 7\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661930590, 'update_timestamp_ms': 1762661930590, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A3CAKZIZEU2SKK7CPHJ5DU3K', 'name': 'projects/ext-datasets/operations/A3CAKZIZEU2SKK7CPHJ5DU3K'}\n","Grid 8\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661940164, 'update_timestamp_ms': 1762661940164, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TUV5FFETPUUO7GV4MPAITY64', 'name': 'projects/ext-datasets/operations/TUV5FFETPUUO7GV4MPAITY64'}\n","Grid 9\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661948118, 'update_timestamp_ms': 1762661948118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O5DGHSRQLFSQVSYZJL6C4RFA', 'name': 'projects/ext-datasets/operations/O5DGHSRQLFSQVSYZJL6C4RFA'}\n","Grid 10\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762661955447, 'update_timestamp_ms': 1762661955447, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XBLOKBW7CZ3DQHPHQVTTNZ7B', 'name': 'projects/ext-datasets/operations/XBLOKBW7CZ3DQHPHQVTTNZ7B'}\n","Grid 11\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762661965446, 'update_timestamp_ms': 1762661965446, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7B7LSH3Q5TGUJZXWCZFEUDR6', 'name': 'projects/ext-datasets/operations/7B7LSH3Q5TGUJZXWCZFEUDR6'}\n","Grid 12\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762661971465, 'update_timestamp_ms': 1762661971465, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AG7MEOWAL22OZPTNULN4JVAL', 'name': 'projects/ext-datasets/operations/AG7MEOWAL22OZPTNULN4JVAL'}\n","Grid 13\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762661982035, 'update_timestamp_ms': 1762661982035, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6OIFXZZ7S5OWH5T26Y7TUDH2', 'name': 'projects/ext-datasets/operations/6OIFXZZ7S5OWH5T26Y7TUDH2'}\n","Grid 14\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762661987865, 'update_timestamp_ms': 1762661987865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LBZIJZUR2GC67OYO226JFC5S', 'name': 'projects/ext-datasets/operations/LBZIJZUR2GC67OYO226JFC5S'}\n","Grid 15\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762661996872, 'update_timestamp_ms': 1762661996872, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IJG5VRFWREKO3OA73EAYGFQH', 'name': 'projects/ext-datasets/operations/IJG5VRFWREKO3OA73EAYGFQH'}\n","Grid 16\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762662002369, 'update_timestamp_ms': 1762662002369, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IHQZ4Q3HVNEI3HRRVBHYQ5UU', 'name': 'projects/ext-datasets/operations/IHQZ4Q3HVNEI3HRRVBHYQ5UU'}\n","Grid 17\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762662010331, 'update_timestamp_ms': 1762662010331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VDVXED3MPPGPPC5TC5OBBDCU', 'name': 'projects/ext-datasets/operations/VDVXED3MPPGPPC5TC5OBBDCU'}\n","Grid 18\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762662017478, 'update_timestamp_ms': 1762662017478, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CWL6AQYBJIE2Y375UN75KCMF', 'name': 'projects/ext-datasets/operations/CWL6AQYBJIE2Y375UN75KCMF'}\n","Grid 19\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762662025437, 'update_timestamp_ms': 1762662025437, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZR5ETYBGZBIQMS2MIAFOCKMH', 'name': 'projects/ext-datasets/operations/ZR5ETYBGZBIQMS2MIAFOCKMH'}\n","Grid 20\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762662032211, 'update_timestamp_ms': 1762662032211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2R2QB75376OUY2AEW4NUKVEK', 'name': 'projects/ext-datasets/operations/2R2QB75376OUY2AEW4NUKVEK'}\n","Grid 21\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762662039260, 'update_timestamp_ms': 1762662039260, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5UECIWMSVBETOS5XAGANZ6UY', 'name': 'projects/ext-datasets/operations/5UECIWMSVBETOS5XAGANZ6UY'}\n","Grid 22\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762662046370, 'update_timestamp_ms': 1762662046370, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLMQ5HTF43J5UJMA7PDMITTY', 'name': 'projects/ext-datasets/operations/XLMQ5HTF43J5UJMA7PDMITTY'}\n","Grid 23\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762662054979, 'update_timestamp_ms': 1762662054979, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EC2L2OPTK7BHK4T4ETPAMXAT', 'name': 'projects/ext-datasets/operations/EC2L2OPTK7BHK4T4ETPAMXAT'}\n","Grid 24\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762662063110, 'update_timestamp_ms': 1762662063110, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKCHOBTGXB7AUZR5MDMYBAQ3', 'name': 'projects/ext-datasets/operations/SKCHOBTGXB7AUZR5MDMYBAQ3'}\n","Grid 25\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762662073842, 'update_timestamp_ms': 1762662073842, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LNQH3EJVOR4FVPS7CHED6AB6', 'name': 'projects/ext-datasets/operations/LNQH3EJVOR4FVPS7CHED6AB6'}\n","Grid 26\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762662081995, 'update_timestamp_ms': 1762662081995, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6N4Q4XDT2BELBHT64FNLB4SP', 'name': 'projects/ext-datasets/operations/6N4Q4XDT2BELBHT64FNLB4SP'}\n","Grid 27\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762662089326, 'update_timestamp_ms': 1762662089326, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WQJ5ZMGWGSJXV4VBS4JE4ALQ', 'name': 'projects/ext-datasets/operations/WQJ5ZMGWGSJXV4VBS4JE4ALQ'}\n","Grid 28\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762662107477, 'update_timestamp_ms': 1762662107477, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V2AWXUTPUQEKFRSFULUCNE3M', 'name': 'projects/ext-datasets/operations/V2AWXUTPUQEKFRSFULUCNE3M'}\n","Grid 29\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762662114625, 'update_timestamp_ms': 1762662114625, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q5YQYH5WH3QQYGEATZOOKVJD', 'name': 'projects/ext-datasets/operations/Q5YQYH5WH3QQYGEATZOOKVJD'}\n","Grid 30\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762662121341, 'update_timestamp_ms': 1762662121341, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FPDEWOS5DMAI5N4VOSYA2LLH', 'name': 'projects/ext-datasets/operations/FPDEWOS5DMAI5N4VOSYA2LLH'}\n","Grid 31\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762662131124, 'update_timestamp_ms': 1762662131124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M2EGDWT6C3WHCGLEKCPT3TVK', 'name': 'projects/ext-datasets/operations/M2EGDWT6C3WHCGLEKCPT3TVK'}\n","Grid 32\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762662138013, 'update_timestamp_ms': 1762662138013, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YVX3LIC6VBOVZU23NU7TIA2D', 'name': 'projects/ext-datasets/operations/YVX3LIC6VBOVZU23NU7TIA2D'}\n","Grid 33\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762662146289, 'update_timestamp_ms': 1762662146289, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3MTP4WO7E3SO3XA44ZR3XZPU', 'name': 'projects/ext-datasets/operations/3MTP4WO7E3SO3XA44ZR3XZPU'}\n","Grid 34\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762662150335, 'update_timestamp_ms': 1762662150335, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OVQYXE4A4CF2FNTZD6VOLK2U', 'name': 'projects/ext-datasets/operations/OVQYXE4A4CF2FNTZD6VOLK2U'}\n","Grid 35\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762662158557, 'update_timestamp_ms': 1762662158557, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4G7JMMFDPHV42WUJRG3C3VMP', 'name': 'projects/ext-datasets/operations/4G7JMMFDPHV42WUJRG3C3VMP'}\n","Grid 36\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762662166584, 'update_timestamp_ms': 1762662166584, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6F5H3VPJ4YUG4KEA4J4DVMCY', 'name': 'projects/ext-datasets/operations/6F5H3VPJ4YUG4KEA4J4DVMCY'}\n","Grid 37\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_37_2018', 'priority': 100, 'creation_timestamp_ms': 1762662184617, 'update_timestamp_ms': 1762662184617, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GFXWSLLOAPWVW523H6VW43OV', 'name': 'projects/ext-datasets/operations/GFXWSLLOAPWVW523H6VW43OV'}\n","Grid 38\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_38_2018', 'priority': 100, 'creation_timestamp_ms': 1762662189413, 'update_timestamp_ms': 1762662189413, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C36EBKRXVVTKQBL2QP4HQDUW', 'name': 'projects/ext-datasets/operations/C36EBKRXVVTKQBL2QP4HQDUW'}\n","Grid 39\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_39_2018', 'priority': 100, 'creation_timestamp_ms': 1762662198287, 'update_timestamp_ms': 1762662198287, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YNDDSFXBTYEFGSVNBHSZJFP5', 'name': 'projects/ext-datasets/operations/YNDDSFXBTYEFGSVNBHSZJFP5'}\n","Grid 40\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_40_2018', 'priority': 100, 'creation_timestamp_ms': 1762662205602, 'update_timestamp_ms': 1762662205602, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MJWSGFBY5B6ANDJIHGIPGN7E', 'name': 'projects/ext-datasets/operations/MJWSGFBY5B6ANDJIHGIPGN7E'}\n","Grid 41\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_41_2018', 'priority': 100, 'creation_timestamp_ms': 1762662213239, 'update_timestamp_ms': 1762662213239, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCTYJB3FCOTI4WJ7NEBOMSXC', 'name': 'projects/ext-datasets/operations/DCTYJB3FCOTI4WJ7NEBOMSXC'}\n","Grid 42\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_42_2018', 'priority': 100, 'creation_timestamp_ms': 1762662221619, 'update_timestamp_ms': 1762662221619, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCXANHOQ6REJHDEZBHG7WNCA', 'name': 'projects/ext-datasets/operations/FCXANHOQ6REJHDEZBHG7WNCA'}\n","Grid 43\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_43_2018', 'priority': 100, 'creation_timestamp_ms': 1762662227723, 'update_timestamp_ms': 1762662227723, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KB7QEBRVCYJI7JSJJ6XU7WYB', 'name': 'projects/ext-datasets/operations/KB7QEBRVCYJI7JSJJ6XU7WYB'}\n","Grid 44\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_44_2018', 'priority': 100, 'creation_timestamp_ms': 1762662235717, 'update_timestamp_ms': 1762662235717, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5M26YN4LL22VBANA4ZGNN22G', 'name': 'projects/ext-datasets/operations/5M26YN4LL22VBANA4ZGNN22G'}\n","Grid 45\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_45_2018', 'priority': 100, 'creation_timestamp_ms': 1762662243442, 'update_timestamp_ms': 1762662243442, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UYZWOCUQPRAINGQU5AGWS6HU', 'name': 'projects/ext-datasets/operations/UYZWOCUQPRAINGQU5AGWS6HU'}\n","Grid 46\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_46_2018', 'priority': 100, 'creation_timestamp_ms': 1762662251072, 'update_timestamp_ms': 1762662251072, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2HN7JFZ5ZFCTIND4OUKA3M72', 'name': 'projects/ext-datasets/operations/2HN7JFZ5ZFCTIND4OUKA3M72'}\n","Grid 47\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_47_2018', 'priority': 100, 'creation_timestamp_ms': 1762662255803, 'update_timestamp_ms': 1762662255803, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M3COZQAWMK27SG3AQNGFZHE6', 'name': 'projects/ext-datasets/operations/M3COZQAWMK27SG3AQNGFZHE6'}\n","Grid 48\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_48_2018', 'priority': 100, 'creation_timestamp_ms': 1762662261699, 'update_timestamp_ms': 1762662261699, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NGYDGB7ZGDUGCXLSMBI5MHEG', 'name': 'projects/ext-datasets/operations/NGYDGB7ZGDUGCXLSMBI5MHEG'}\n","Grid 49\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_49_2018', 'priority': 100, 'creation_timestamp_ms': 1762662270096, 'update_timestamp_ms': 1762662270096, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YEAPN44AOQZSMMZ4AT3VK3B4', 'name': 'projects/ext-datasets/operations/YEAPN44AOQZSMMZ4AT3VK3B4'}\n","Grid 50\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_50_2018', 'priority': 100, 'creation_timestamp_ms': 1762662290667, 'update_timestamp_ms': 1762662290667, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PC7LH7UTGSX3Y33VSUIXHDWY', 'name': 'projects/ext-datasets/operations/PC7LH7UTGSX3Y33VSUIXHDWY'}\n","Grid 51\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_51_2018', 'priority': 100, 'creation_timestamp_ms': 1762662299690, 'update_timestamp_ms': 1762662299690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YRNKM6XCEXVI5E4SVC5QLEGV', 'name': 'projects/ext-datasets/operations/YRNKM6XCEXVI5E4SVC5QLEGV'}\n","Grid 52\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_52_2018', 'priority': 100, 'creation_timestamp_ms': 1762662305843, 'update_timestamp_ms': 1762662305843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OXE6HIN2RFKBCPSJ54XRBJUM', 'name': 'projects/ext-datasets/operations/OXE6HIN2RFKBCPSJ54XRBJUM'}\n","Grid 53\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_53_2018', 'priority': 100, 'creation_timestamp_ms': 1762662314131, 'update_timestamp_ms': 1762662314131, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLGA7DVBZXB4HS2BENXXBXOK', 'name': 'projects/ext-datasets/operations/JLGA7DVBZXB4HS2BENXXBXOK'}\n","Grid 54\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_54_2018', 'priority': 100, 'creation_timestamp_ms': 1762662320564, 'update_timestamp_ms': 1762662320564, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HRK3SGO4WFBUMHTWEFYO4SQ4', 'name': 'projects/ext-datasets/operations/HRK3SGO4WFBUMHTWEFYO4SQ4'}\n","Grid 55\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_55_2018', 'priority': 100, 'creation_timestamp_ms': 1762662328714, 'update_timestamp_ms': 1762662328714, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RLDODOP4KDUTKXOXMONXQUIK', 'name': 'projects/ext-datasets/operations/RLDODOP4KDUTKXOXMONXQUIK'}\n","Grid 56\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_56_2018', 'priority': 100, 'creation_timestamp_ms': 1762662336187, 'update_timestamp_ms': 1762662336187, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D6BLHDKIXDEQMMV6YKUFQJAL', 'name': 'projects/ext-datasets/operations/D6BLHDKIXDEQMMV6YKUFQJAL'}\n","Grid 57\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_57_2018', 'priority': 100, 'creation_timestamp_ms': 1762662339638, 'update_timestamp_ms': 1762662339638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6NQRBN7LKSGWSC4TS5MEIER', 'name': 'projects/ext-datasets/operations/X6NQRBN7LKSGWSC4TS5MEIER'}\n","Grid 58\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_58_2018', 'priority': 100, 'creation_timestamp_ms': 1762662345627, 'update_timestamp_ms': 1762662345627, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6IW4SBZEZN3T7BUBOZXR23ON', 'name': 'projects/ext-datasets/operations/6IW4SBZEZN3T7BUBOZXR23ON'}\n","Grid 59\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_59_2018', 'priority': 100, 'creation_timestamp_ms': 1762662352081, 'update_timestamp_ms': 1762662352081, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RHSKEKQO2WB5AFAAOGMFM4U6', 'name': 'projects/ext-datasets/operations/RHSKEKQO2WB5AFAAOGMFM4U6'}\n","Grid 60\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_60_2018', 'priority': 100, 'creation_timestamp_ms': 1762662357706, 'update_timestamp_ms': 1762662357706, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7FNZSJ7UQSW5IKMH4CNZPEX4', 'name': 'projects/ext-datasets/operations/7FNZSJ7UQSW5IKMH4CNZPEX4'}\n","Grid 61\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_61_2018', 'priority': 100, 'creation_timestamp_ms': 1762662366119, 'update_timestamp_ms': 1762662366119, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NGV3PBO7LJV3B456NN3BUA72', 'name': 'projects/ext-datasets/operations/NGV3PBO7LJV3B456NN3BUA72'}\n","Grid 62\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_62_2018', 'priority': 100, 'creation_timestamp_ms': 1762662371980, 'update_timestamp_ms': 1762662371980, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6WCVWZQZWWHNIILUOTRD5Q5B', 'name': 'projects/ext-datasets/operations/6WCVWZQZWWHNIILUOTRD5Q5B'}\n","Grid 63\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_63_2018', 'priority': 100, 'creation_timestamp_ms': 1762662379126, 'update_timestamp_ms': 1762662379126, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EDIVZOEFDLZPEVFVNZQRKOFY', 'name': 'projects/ext-datasets/operations/EDIVZOEFDLZPEVFVNZQRKOFY'}\n","Grid 64\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_64_2018', 'priority': 100, 'creation_timestamp_ms': 1762662384858, 'update_timestamp_ms': 1762662384858, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R5SKSY3IODVXAPZHFZ5VMRRW', 'name': 'projects/ext-datasets/operations/R5SKSY3IODVXAPZHFZ5VMRRW'}\n","Grid 65\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_65_2018', 'priority': 100, 'creation_timestamp_ms': 1762662394129, 'update_timestamp_ms': 1762662394129, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7BJQC7NTRKWRXODRISP4YWUR', 'name': 'projects/ext-datasets/operations/7BJQC7NTRKWRXODRISP4YWUR'}\n","Grid 66\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_66_2018', 'priority': 100, 'creation_timestamp_ms': 1762662402161, 'update_timestamp_ms': 1762662402161, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6YYJFW52P7W6WJA6I6W5LVRL', 'name': 'projects/ext-datasets/operations/6YYJFW52P7W6WJA6I6W5LVRL'}\n","Grid 67\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_67_2018', 'priority': 100, 'creation_timestamp_ms': 1762662410861, 'update_timestamp_ms': 1762662410861, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WR2VL2QTR44KFUXNVN22RCD5', 'name': 'projects/ext-datasets/operations/WR2VL2QTR44KFUXNVN22RCD5'}\n","Grid 68\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_68_2018', 'priority': 100, 'creation_timestamp_ms': 1762662414538, 'update_timestamp_ms': 1762662414538, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7DSU7AISKVNDQ5XHPHTDIL7G', 'name': 'projects/ext-datasets/operations/7DSU7AISKVNDQ5XHPHTDIL7G'}\n","Grid 69\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_69_2018', 'priority': 100, 'creation_timestamp_ms': 1762662423666, 'update_timestamp_ms': 1762662423666, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3AQBCBLOEI35ZASVHUVES5W4', 'name': 'projects/ext-datasets/operations/3AQBCBLOEI35ZASVHUVES5W4'}\n","Grid 70\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_70_2018', 'priority': 100, 'creation_timestamp_ms': 1762662432102, 'update_timestamp_ms': 1762662432102, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HZEYWT53DCO2FB3GMFMD2ZOM', 'name': 'projects/ext-datasets/operations/HZEYWT53DCO2FB3GMFMD2ZOM'}\n","Grid 71\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_71_2018', 'priority': 100, 'creation_timestamp_ms': 1762662441628, 'update_timestamp_ms': 1762662441628, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXCUHAU3PJ3GUO3RAMWQWWZO', 'name': 'projects/ext-datasets/operations/IXCUHAU3PJ3GUO3RAMWQWWZO'}\n","Grid 72\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_72_2018', 'priority': 100, 'creation_timestamp_ms': 1762662457917, 'update_timestamp_ms': 1762662457917, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6MSA2JK6TZZZCBKMWR4ZEDYW', 'name': 'projects/ext-datasets/operations/6MSA2JK6TZZZCBKMWR4ZEDYW'}\n","Grid 73\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_73_2018', 'priority': 100, 'creation_timestamp_ms': 1762662466701, 'update_timestamp_ms': 1762662466701, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UNFXUXZDVB5WXDVCJXMT6ZYX', 'name': 'projects/ext-datasets/operations/UNFXUXZDVB5WXDVCJXMT6ZYX'}\n","Grid 74\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_74_2018', 'priority': 100, 'creation_timestamp_ms': 1762662471448, 'update_timestamp_ms': 1762662471448, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AISEA5YKT46TYYP4ZUPGWGUU', 'name': 'projects/ext-datasets/operations/AISEA5YKT46TYYP4ZUPGWGUU'}\n","Grid 75\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_75_2018', 'priority': 100, 'creation_timestamp_ms': 1762662479102, 'update_timestamp_ms': 1762662479102, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KM7I4S7HQQNCH3FLWAINJBTP', 'name': 'projects/ext-datasets/operations/KM7I4S7HQQNCH3FLWAINJBTP'}\n","Grid 76\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_76_2018', 'priority': 100, 'creation_timestamp_ms': 1762662485846, 'update_timestamp_ms': 1762662485846, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HEE447VKHQ3H3HFFOEPNJVVM', 'name': 'projects/ext-datasets/operations/HEE447VKHQ3H3HFFOEPNJVVM'}\n","Grid 77\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_77_2018', 'priority': 100, 'creation_timestamp_ms': 1762662489539, 'update_timestamp_ms': 1762662489539, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NWJMXCWHAGTUT6WHE5DZDADQ', 'name': 'projects/ext-datasets/operations/NWJMXCWHAGTUT6WHE5DZDADQ'}\n","Grid 78\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_78_2018', 'priority': 100, 'creation_timestamp_ms': 1762662493898, 'update_timestamp_ms': 1762662493898, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FTPH3L7NDYOHSZLKK3DZM5IO', 'name': 'projects/ext-datasets/operations/FTPH3L7NDYOHSZLKK3DZM5IO'}\n","Grid 79\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_79_2018', 'priority': 100, 'creation_timestamp_ms': 1762662500167, 'update_timestamp_ms': 1762662500167, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QDLUJWKPXMOUDKW2DKKBPBGN', 'name': 'projects/ext-datasets/operations/QDLUJWKPXMOUDKW2DKKBPBGN'}\n","Grid 80\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_80_2018', 'priority': 100, 'creation_timestamp_ms': 1762662511569, 'update_timestamp_ms': 1762662511569, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XWNTVV5AFI737DY6FV5X7K77', 'name': 'projects/ext-datasets/operations/XWNTVV5AFI737DY6FV5X7K77'}\n","Grid 81\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_81_2018', 'priority': 100, 'creation_timestamp_ms': 1762662521065, 'update_timestamp_ms': 1762662521065, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LPMFCHNYD3CSSDV36C2UL4MV', 'name': 'projects/ext-datasets/operations/LPMFCHNYD3CSSDV36C2UL4MV'}\n","Grid 82\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_82_2018', 'priority': 100, 'creation_timestamp_ms': 1762662531383, 'update_timestamp_ms': 1762662531383, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZXADYANV7O4Q56UNXPE75IK', 'name': 'projects/ext-datasets/operations/XZXADYANV7O4Q56UNXPE75IK'}\n","Grid 83\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_83_2018', 'priority': 100, 'creation_timestamp_ms': 1762662548313, 'update_timestamp_ms': 1762662548313, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4WMULPTRLR2VRXIBIYBBDDI', 'name': 'projects/ext-datasets/operations/V4WMULPTRLR2VRXIBIYBBDDI'}\n","Grid 84\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_84_2018', 'priority': 100, 'creation_timestamp_ms': 1762662557900, 'update_timestamp_ms': 1762662557900, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O4FTHLVZODQWGD5GSV3YFWBN', 'name': 'projects/ext-datasets/operations/O4FTHLVZODQWGD5GSV3YFWBN'}\n","Grid 85\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_85_2018', 'priority': 100, 'creation_timestamp_ms': 1762662566782, 'update_timestamp_ms': 1762662566782, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4KC7OIL2XGF2SB2MTKJDDT4Y', 'name': 'projects/ext-datasets/operations/4KC7OIL2XGF2SB2MTKJDDT4Y'}\n","Grid 86\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_86_2018', 'priority': 100, 'creation_timestamp_ms': 1762662581004, 'update_timestamp_ms': 1762662581004, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DNHER2AA743EFZEIZTEEAFHD', 'name': 'projects/ext-datasets/operations/DNHER2AA743EFZEIZTEEAFHD'}\n","Grid 87\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_87_2018', 'priority': 100, 'creation_timestamp_ms': 1762662589639, 'update_timestamp_ms': 1762662589639, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EBZYKLFZDODZ6DGMP57JG5OD', 'name': 'projects/ext-datasets/operations/EBZYKLFZDODZ6DGMP57JG5OD'}\n","Grid 88\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_88_2018', 'priority': 100, 'creation_timestamp_ms': 1762662597190, 'update_timestamp_ms': 1762662597190, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7YVFLQI5JGODS4MSTMUA6JDO', 'name': 'projects/ext-datasets/operations/7YVFLQI5JGODS4MSTMUA6JDO'}\n","Grid 89\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_89_2018', 'priority': 100, 'creation_timestamp_ms': 1762662608586, 'update_timestamp_ms': 1762662608586, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OUNFSYOPKEUTECSJKWHHUKQA', 'name': 'projects/ext-datasets/operations/OUNFSYOPKEUTECSJKWHHUKQA'}\n","Grid 90\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_90_2018', 'priority': 100, 'creation_timestamp_ms': 1762662618337, 'update_timestamp_ms': 1762662618337, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LX6WCRWHFJCGAQ6YBNYZTYM4', 'name': 'projects/ext-datasets/operations/LX6WCRWHFJCGAQ6YBNYZTYM4'}\n","Grid 91\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_91_2018', 'priority': 100, 'creation_timestamp_ms': 1762662625221, 'update_timestamp_ms': 1762662625221, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6U47YJX5BJQ5C5DOO6JEIZUH', 'name': 'projects/ext-datasets/operations/6U47YJX5BJQ5C5DOO6JEIZUH'}\n","Grid 92\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_92_2018', 'priority': 100, 'creation_timestamp_ms': 1762662632164, 'update_timestamp_ms': 1762662632164, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YZUUKUDK5PZZ7FRHLMWYDP46', 'name': 'projects/ext-datasets/operations/YZUUKUDK5PZZ7FRHLMWYDP46'}\n","Grid 93\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_93_2018', 'priority': 100, 'creation_timestamp_ms': 1762662641124, 'update_timestamp_ms': 1762662641124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YPF3EA4T6HC4PZSO4DROILIQ', 'name': 'projects/ext-datasets/operations/YPF3EA4T6HC4PZSO4DROILIQ'}\n","Grid 94\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_94_2018', 'priority': 100, 'creation_timestamp_ms': 1762662649719, 'update_timestamp_ms': 1762662649719, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6OZH6FTJ5FTQWHINJV24ZDR', 'name': 'projects/ext-datasets/operations/X6OZH6FTJ5FTQWHINJV24ZDR'}\n","Grid 95\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_95_2018', 'priority': 100, 'creation_timestamp_ms': 1762662657088, 'update_timestamp_ms': 1762662657088, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DBIJGLF3F3V3VKQPR4VCWXHC', 'name': 'projects/ext-datasets/operations/DBIJGLF3F3V3VKQPR4VCWXHC'}\n","Grid 96\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_96_2018', 'priority': 100, 'creation_timestamp_ms': 1762662663908, 'update_timestamp_ms': 1762662663908, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HYHCQT2NV5ARFMF5FLAXTYX7', 'name': 'projects/ext-datasets/operations/HYHCQT2NV5ARFMF5FLAXTYX7'}\n","Grid 97\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_97_2018', 'priority': 100, 'creation_timestamp_ms': 1762662672397, 'update_timestamp_ms': 1762662672397, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YFFOWM2O3E7HBGR26H54Z6B5', 'name': 'projects/ext-datasets/operations/YFFOWM2O3E7HBGR26H54Z6B5'}\n","Grid 98\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_98_2018', 'priority': 100, 'creation_timestamp_ms': 1762662681198, 'update_timestamp_ms': 1762662681198, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZPDE35I5PJWJ5IJIL32IT5T', 'name': 'projects/ext-datasets/operations/AZPDE35I5PJWJ5IJIL32IT5T'}\n","Grid 99\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_99_2018', 'priority': 100, 'creation_timestamp_ms': 1762662688739, 'update_timestamp_ms': 1762662688739, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ER376KGTFSYINJ5TLGNKUCNZ', 'name': 'projects/ext-datasets/operations/ER376KGTFSYINJ5TLGNKUCNZ'}\n","Grid 100\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_100_2018', 'priority': 100, 'creation_timestamp_ms': 1762662691963, 'update_timestamp_ms': 1762662691963, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J4TM3N3GQZQSZLAA475SN6TH', 'name': 'projects/ext-datasets/operations/J4TM3N3GQZQSZLAA475SN6TH'}\n","Grid 101\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_101_2018', 'priority': 100, 'creation_timestamp_ms': 1762662700337, 'update_timestamp_ms': 1762662700337, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLWJBS3W6UHVJSWAJSL5KVLN', 'name': 'projects/ext-datasets/operations/JLWJBS3W6UHVJSWAJSL5KVLN'}\n","Grid 102\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_102_2018', 'priority': 100, 'creation_timestamp_ms': 1762662709090, 'update_timestamp_ms': 1762662709090, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UMYBJM2WLTINTWMYYRX5NWUS', 'name': 'projects/ext-datasets/operations/UMYBJM2WLTINTWMYYRX5NWUS'}\n","1470.9396209716797\n","Year 2018, District 12: Barddhaman, grids: 112\n","Grid 0\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762662720295, 'update_timestamp_ms': 1762662720295, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CVYAPFGUFSWB52RYSK5OM4WB', 'name': 'projects/ext-datasets/operations/CVYAPFGUFSWB52RYSK5OM4WB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762662725451, 'update_timestamp_ms': 1762662725451, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QSF4HUMEERLP4CPQLVKER4WV', 'name': 'projects/ext-datasets/operations/QSF4HUMEERLP4CPQLVKER4WV'}\n","Grid 2\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762662734484, 'update_timestamp_ms': 1762662734484, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CVY66EKENE6B4NMH2PJSUFCT', 'name': 'projects/ext-datasets/operations/CVY66EKENE6B4NMH2PJSUFCT'}\n","Grid 3\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762662741414, 'update_timestamp_ms': 1762662741414, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YT6ZTJUXFWG5H2MGCEVCUZZQ', 'name': 'projects/ext-datasets/operations/YT6ZTJUXFWG5H2MGCEVCUZZQ'}\n","Grid 4\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762662749134, 'update_timestamp_ms': 1762662749134, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'COCYHFDUQLAUJYXXD23R44BM', 'name': 'projects/ext-datasets/operations/COCYHFDUQLAUJYXXD23R44BM'}\n","Grid 5\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762662753867, 'update_timestamp_ms': 1762662753867, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F5WXMDHXOYC7STHYBR62V7O2', 'name': 'projects/ext-datasets/operations/F5WXMDHXOYC7STHYBR62V7O2'}\n","Grid 6\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762662759975, 'update_timestamp_ms': 1762662759975, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '776SZ2R2H4PBBQYTTUM2LMOE', 'name': 'projects/ext-datasets/operations/776SZ2R2H4PBBQYTTUM2LMOE'}\n","Grid 7\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762662767571, 'update_timestamp_ms': 1762662767571, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZV5M5TW7V6RDUGJUNLMPUC2C', 'name': 'projects/ext-datasets/operations/ZV5M5TW7V6RDUGJUNLMPUC2C'}\n","Grid 8\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762662772693, 'update_timestamp_ms': 1762662772693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5LJZRLN5EUN32TUMHM5Z6S6U', 'name': 'projects/ext-datasets/operations/5LJZRLN5EUN32TUMHM5Z6S6U'}\n","Grid 9\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762662778997, 'update_timestamp_ms': 1762662778997, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WQH7D6YHAGG2TK43TR427DXA', 'name': 'projects/ext-datasets/operations/WQH7D6YHAGG2TK43TR427DXA'}\n","Grid 10\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762662786704, 'update_timestamp_ms': 1762662786704, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O2OF6Y3FXIIOZMQJYAVTDB4T', 'name': 'projects/ext-datasets/operations/O2OF6Y3FXIIOZMQJYAVTDB4T'}\n","Grid 11\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762662793594, 'update_timestamp_ms': 1762662793594, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CUNYJ6XVKOK5JWNBM3PXPKII', 'name': 'projects/ext-datasets/operations/CUNYJ6XVKOK5JWNBM3PXPKII'}\n","Grid 12\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762662797432, 'update_timestamp_ms': 1762662797432, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZY42XPHLSJJTASYEPK3HWTNB', 'name': 'projects/ext-datasets/operations/ZY42XPHLSJJTASYEPK3HWTNB'}\n","Grid 13\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762662805630, 'update_timestamp_ms': 1762662805630, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BQUGNYOPDMOGXW3XUHS5XRMY', 'name': 'projects/ext-datasets/operations/BQUGNYOPDMOGXW3XUHS5XRMY'}\n","Grid 14\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762662813218, 'update_timestamp_ms': 1762662813218, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y3JKMYINEA45AOFFTZIYD6KC', 'name': 'projects/ext-datasets/operations/Y3JKMYINEA45AOFFTZIYD6KC'}\n","Grid 15\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762662818157, 'update_timestamp_ms': 1762662818157, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C3ZJVK7IXOW75XKIDHBDRBZD', 'name': 'projects/ext-datasets/operations/C3ZJVK7IXOW75XKIDHBDRBZD'}\n","Grid 16\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762662826279, 'update_timestamp_ms': 1762662826279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3VRJ37SIYS7Q2T4AYGWTA5UO', 'name': 'projects/ext-datasets/operations/3VRJ37SIYS7Q2T4AYGWTA5UO'}\n","Grid 17\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762662834377, 'update_timestamp_ms': 1762662834377, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NYI4WE744QRXEN5DLPHQCSOA', 'name': 'projects/ext-datasets/operations/NYI4WE744QRXEN5DLPHQCSOA'}\n","Grid 18\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762662838462, 'update_timestamp_ms': 1762662838462, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P73SELSFL5BEZG4LSXWCS34H', 'name': 'projects/ext-datasets/operations/P73SELSFL5BEZG4LSXWCS34H'}\n","Grid 19\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762662843350, 'update_timestamp_ms': 1762662843350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZKEYHR7WQ7YK3SAZ5NMBTUA', 'name': 'projects/ext-datasets/operations/XZKEYHR7WQ7YK3SAZ5NMBTUA'}\n","Grid 20\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762662849762, 'update_timestamp_ms': 1762662849762, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NQ573WM4Z25B2UIRGKI4IANW', 'name': 'projects/ext-datasets/operations/NQ573WM4Z25B2UIRGKI4IANW'}\n","Grid 21\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762662857674, 'update_timestamp_ms': 1762662857674, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I5ASBM24ZWGIEIMGWCZWTUI6', 'name': 'projects/ext-datasets/operations/I5ASBM24ZWGIEIMGWCZWTUI6'}\n","Grid 22\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762662864953, 'update_timestamp_ms': 1762662864953, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S5CG73D3JKYNCF7KBIL3Q5H4', 'name': 'projects/ext-datasets/operations/S5CG73D3JKYNCF7KBIL3Q5H4'}\n","Grid 23\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762662871848, 'update_timestamp_ms': 1762662871848, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W642R3OWEAVV57FVAS3K467P', 'name': 'projects/ext-datasets/operations/W642R3OWEAVV57FVAS3K467P'}\n","Grid 24\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762662876462, 'update_timestamp_ms': 1762662876462, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W62H5AE2JSAQTYZNIN7LLOQS', 'name': 'projects/ext-datasets/operations/W62H5AE2JSAQTYZNIN7LLOQS'}\n","Grid 25\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762662885779, 'update_timestamp_ms': 1762662885779, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ACYPIOUA4CU4XHTXKEWY5MOG', 'name': 'projects/ext-datasets/operations/ACYPIOUA4CU4XHTXKEWY5MOG'}\n","Grid 26\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762662893529, 'update_timestamp_ms': 1762662893529, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OUQPTYB24CYAL7Q6VZHTYC4I', 'name': 'projects/ext-datasets/operations/OUQPTYB24CYAL7Q6VZHTYC4I'}\n","Grid 27\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762662901313, 'update_timestamp_ms': 1762662901313, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'U5CDX3PVNJRXX7SJQU6FYSPF', 'name': 'projects/ext-datasets/operations/U5CDX3PVNJRXX7SJQU6FYSPF'}\n","Grid 28\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762662910250, 'update_timestamp_ms': 1762662910250, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SSA55FB26DNDZYFC6ENMNSGT', 'name': 'projects/ext-datasets/operations/SSA55FB26DNDZYFC6ENMNSGT'}\n","Grid 29\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762662917244, 'update_timestamp_ms': 1762662917244, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ASA5WKSDA4IR5ZQUJRH7TLLX', 'name': 'projects/ext-datasets/operations/ASA5WKSDA4IR5ZQUJRH7TLLX'}\n","Grid 30\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762662921261, 'update_timestamp_ms': 1762662921261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HWJKKJLJLX7C2VCCFXM27FR3', 'name': 'projects/ext-datasets/operations/HWJKKJLJLX7C2VCCFXM27FR3'}\n","Grid 31\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762662929748, 'update_timestamp_ms': 1762662929748, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y7QCW5U5SUG2YQRW723RJWAD', 'name': 'projects/ext-datasets/operations/Y7QCW5U5SUG2YQRW723RJWAD'}\n","Grid 32\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762662935935, 'update_timestamp_ms': 1762662935935, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CJGX6XIEY3LRQNJFUL3VV2L2', 'name': 'projects/ext-datasets/operations/CJGX6XIEY3LRQNJFUL3VV2L2'}\n","Grid 33\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762662943825, 'update_timestamp_ms': 1762662943825, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OPUGTR4BYJQ3ZKPYRJG5PZVQ', 'name': 'projects/ext-datasets/operations/OPUGTR4BYJQ3ZKPYRJG5PZVQ'}\n","Grid 34\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762662950206, 'update_timestamp_ms': 1762662950206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OYRCYIHMQTX7VXUIWWUZIX6K', 'name': 'projects/ext-datasets/operations/OYRCYIHMQTX7VXUIWWUZIX6K'}\n","Grid 35\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762662958440, 'update_timestamp_ms': 1762662958440, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6MGJ33DRKPT4Y74IXCKBIB5J', 'name': 'projects/ext-datasets/operations/6MGJ33DRKPT4Y74IXCKBIB5J'}\n","Grid 36\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762662963435, 'update_timestamp_ms': 1762662963435, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCVUFYMLGVUTMYZQBP567VO2', 'name': 'projects/ext-datasets/operations/FCVUFYMLGVUTMYZQBP567VO2'}\n","Grid 37\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_37_2018', 'priority': 100, 'creation_timestamp_ms': 1762662968436, 'update_timestamp_ms': 1762662968436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E6JZE4FE2W5WUUATWZMRDNTK', 'name': 'projects/ext-datasets/operations/E6JZE4FE2W5WUUATWZMRDNTK'}\n","Grid 38\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_38_2018', 'priority': 100, 'creation_timestamp_ms': 1762662973081, 'update_timestamp_ms': 1762662973081, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJ67UTGBXBH47VQMEOAT2YAX', 'name': 'projects/ext-datasets/operations/YJ67UTGBXBH47VQMEOAT2YAX'}\n","Grid 39\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_39_2018', 'priority': 100, 'creation_timestamp_ms': 1762662980971, 'update_timestamp_ms': 1762662980971, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BK6DT5O47SHMGFWAB5ANZJ3C', 'name': 'projects/ext-datasets/operations/BK6DT5O47SHMGFWAB5ANZJ3C'}\n","Grid 40\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_40_2018', 'priority': 100, 'creation_timestamp_ms': 1762662990232, 'update_timestamp_ms': 1762662990232, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MA4JSGLARA5IL3IZBMOSAMPQ', 'name': 'projects/ext-datasets/operations/MA4JSGLARA5IL3IZBMOSAMPQ'}\n","Grid 41\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_41_2018', 'priority': 100, 'creation_timestamp_ms': 1762662994422, 'update_timestamp_ms': 1762662994422, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WZXBNEPQ3XMKAAEVULH7H2D2', 'name': 'projects/ext-datasets/operations/WZXBNEPQ3XMKAAEVULH7H2D2'}\n","Grid 42\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_42_2018', 'priority': 100, 'creation_timestamp_ms': 1762662998487, 'update_timestamp_ms': 1762662998487, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SYCOWKQES63QQRNNEAAQSZUN', 'name': 'projects/ext-datasets/operations/SYCOWKQES63QQRNNEAAQSZUN'}\n","Grid 43\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_43_2018', 'priority': 100, 'creation_timestamp_ms': 1762663003545, 'update_timestamp_ms': 1762663003545, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DSVIOMBC7IONT3SLT5I6A7IG', 'name': 'projects/ext-datasets/operations/DSVIOMBC7IONT3SLT5I6A7IG'}\n","Grid 44\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_44_2018', 'priority': 100, 'creation_timestamp_ms': 1762663007458, 'update_timestamp_ms': 1762663007458, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WJ6XRMS4T2PZOJBHRDYZTWUN', 'name': 'projects/ext-datasets/operations/WJ6XRMS4T2PZOJBHRDYZTWUN'}\n","Grid 45\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_45_2018', 'priority': 100, 'creation_timestamp_ms': 1762663015024, 'update_timestamp_ms': 1762663015024, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VUMY6LTNLYIPQIH6HNXOH2DT', 'name': 'projects/ext-datasets/operations/VUMY6LTNLYIPQIH6HNXOH2DT'}\n","Grid 46\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_46_2018', 'priority': 100, 'creation_timestamp_ms': 1762663022934, 'update_timestamp_ms': 1762663022934, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5MYOP6VCGKLOQ2BRLAPTD5VU', 'name': 'projects/ext-datasets/operations/5MYOP6VCGKLOQ2BRLAPTD5VU'}\n","Grid 47\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_47_2018', 'priority': 100, 'creation_timestamp_ms': 1762663031299, 'update_timestamp_ms': 1762663031299, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z4JZ6IXCRQUSVQT22IYRDRXP', 'name': 'projects/ext-datasets/operations/Z4JZ6IXCRQUSVQT22IYRDRXP'}\n","Grid 48\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_48_2018', 'priority': 100, 'creation_timestamp_ms': 1762663041569, 'update_timestamp_ms': 1762663041569, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H37NQ7RTYBXWI62MU57FCIYK', 'name': 'projects/ext-datasets/operations/H37NQ7RTYBXWI62MU57FCIYK'}\n","Grid 49\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_49_2018', 'priority': 100, 'creation_timestamp_ms': 1762663048632, 'update_timestamp_ms': 1762663048632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OJQN3MJGBMNGZSLBFCIDKSHN', 'name': 'projects/ext-datasets/operations/OJQN3MJGBMNGZSLBFCIDKSHN'}\n","Grid 50\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_50_2018', 'priority': 100, 'creation_timestamp_ms': 1762663056130, 'update_timestamp_ms': 1762663056130, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZM72542SRA25SZ7H7YOIUGSR', 'name': 'projects/ext-datasets/operations/ZM72542SRA25SZ7H7YOIUGSR'}\n","Grid 51\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_51_2018', 'priority': 100, 'creation_timestamp_ms': 1762663064008, 'update_timestamp_ms': 1762663064008, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H6PKJHG5OTAASPDE2X3BQXRJ', 'name': 'projects/ext-datasets/operations/H6PKJHG5OTAASPDE2X3BQXRJ'}\n","Grid 52\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_52_2018', 'priority': 100, 'creation_timestamp_ms': 1762663073818, 'update_timestamp_ms': 1762663073818, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HBANIKF2AUIVEE7LN2YJ3HRG', 'name': 'projects/ext-datasets/operations/HBANIKF2AUIVEE7LN2YJ3HRG'}\n","Grid 53\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_53_2018', 'priority': 100, 'creation_timestamp_ms': 1762663081691, 'update_timestamp_ms': 1762663081691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AB6P57FWLTZ2TDXPKXL656OW', 'name': 'projects/ext-datasets/operations/AB6P57FWLTZ2TDXPKXL656OW'}\n","Grid 54\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_54_2018', 'priority': 100, 'creation_timestamp_ms': 1762663089843, 'update_timestamp_ms': 1762663089843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YC4KZ5YVVGY3GJSGAEEIU4VQ', 'name': 'projects/ext-datasets/operations/YC4KZ5YVVGY3GJSGAEEIU4VQ'}\n","Grid 55\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_55_2018', 'priority': 100, 'creation_timestamp_ms': 1762663096447, 'update_timestamp_ms': 1762663096447, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RYBNFECFMNKJLSVQH5KKG26K', 'name': 'projects/ext-datasets/operations/RYBNFECFMNKJLSVQH5KKG26K'}\n","Grid 56\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_56_2018', 'priority': 100, 'creation_timestamp_ms': 1762663108432, 'update_timestamp_ms': 1762663108432, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5AKLGISYOJY536SSIE4J5XPV', 'name': 'projects/ext-datasets/operations/5AKLGISYOJY536SSIE4J5XPV'}\n","Grid 57\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_57_2018', 'priority': 100, 'creation_timestamp_ms': 1762663116314, 'update_timestamp_ms': 1762663116314, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '65FXQIDVL7AAKPCQSXDIJVWL', 'name': 'projects/ext-datasets/operations/65FXQIDVL7AAKPCQSXDIJVWL'}\n","Grid 58\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_58_2018', 'priority': 100, 'creation_timestamp_ms': 1762663122438, 'update_timestamp_ms': 1762663122438, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YBDAFJBLFXJTACPQYMGOQBMK', 'name': 'projects/ext-datasets/operations/YBDAFJBLFXJTACPQYMGOQBMK'}\n","Grid 59\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_59_2018', 'priority': 100, 'creation_timestamp_ms': 1762663131585, 'update_timestamp_ms': 1762663131585, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AB6LY3PVHGZ747TMIB4YXZQ7', 'name': 'projects/ext-datasets/operations/AB6LY3PVHGZ747TMIB4YXZQ7'}\n","Grid 60\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_60_2018', 'priority': 100, 'creation_timestamp_ms': 1762663135551, 'update_timestamp_ms': 1762663135551, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5JDMOFKKXGHEK7LEIC77LH5A', 'name': 'projects/ext-datasets/operations/5JDMOFKKXGHEK7LEIC77LH5A'}\n","Grid 61\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_61_2018', 'priority': 100, 'creation_timestamp_ms': 1762663146649, 'update_timestamp_ms': 1762663146649, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZEP66S23AF6WRDZ4LBK36TC', 'name': 'projects/ext-datasets/operations/AZEP66S23AF6WRDZ4LBK36TC'}\n","Grid 62\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_62_2018', 'priority': 100, 'creation_timestamp_ms': 1762663153661, 'update_timestamp_ms': 1762663153661, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VTRIUL6HUXUIV535Z3ECYNG4', 'name': 'projects/ext-datasets/operations/VTRIUL6HUXUIV535Z3ECYNG4'}\n","Grid 63\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_63_2018', 'priority': 100, 'creation_timestamp_ms': 1762663160000, 'update_timestamp_ms': 1762663160000, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AEC6PK6LKUXF5JF6ENX375BT', 'name': 'projects/ext-datasets/operations/AEC6PK6LKUXF5JF6ENX375BT'}\n","Grid 64\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_64_2018', 'priority': 100, 'creation_timestamp_ms': 1762663166893, 'update_timestamp_ms': 1762663166893, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AJGFGVSX5IGYBQ3F47E6PG7P', 'name': 'projects/ext-datasets/operations/AJGFGVSX5IGYBQ3F47E6PG7P'}\n","Grid 65\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_65_2018', 'priority': 100, 'creation_timestamp_ms': 1762663173953, 'update_timestamp_ms': 1762663173953, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCZ3C5BFZU4OJXKNOW6DAMRL', 'name': 'projects/ext-datasets/operations/DCZ3C5BFZU4OJXKNOW6DAMRL'}\n","Grid 66\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_66_2018', 'priority': 100, 'creation_timestamp_ms': 1762663182967, 'update_timestamp_ms': 1762663182967, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLGDIO5DJPDONI2WYGYF3BLU', 'name': 'projects/ext-datasets/operations/XLGDIO5DJPDONI2WYGYF3BLU'}\n","Grid 67\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_67_2018', 'priority': 100, 'creation_timestamp_ms': 1762663190118, 'update_timestamp_ms': 1762663190118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYFQNFSPOFRECNPSHKFO5QAA', 'name': 'projects/ext-datasets/operations/XYFQNFSPOFRECNPSHKFO5QAA'}\n","Grid 68\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_68_2018', 'priority': 100, 'creation_timestamp_ms': 1762663194061, 'update_timestamp_ms': 1762663194061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WA7YEKTAQQLLTSRWAF7XRFUG', 'name': 'projects/ext-datasets/operations/WA7YEKTAQQLLTSRWAF7XRFUG'}\n","Grid 69\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_69_2018', 'priority': 100, 'creation_timestamp_ms': 1762663201149, 'update_timestamp_ms': 1762663201149, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VTZHVVRZY6I3DSFZ4ETCDI3A', 'name': 'projects/ext-datasets/operations/VTZHVVRZY6I3DSFZ4ETCDI3A'}\n","Grid 70\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_70_2018', 'priority': 100, 'creation_timestamp_ms': 1762663206455, 'update_timestamp_ms': 1762663206455, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RC4A3BRD7HKBCMHNTWDM7KIX', 'name': 'projects/ext-datasets/operations/RC4A3BRD7HKBCMHNTWDM7KIX'}\n","Grid 71\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_71_2018', 'priority': 100, 'creation_timestamp_ms': 1762663215535, 'update_timestamp_ms': 1762663215535, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WJVSAIOQZCFK7GYNGOOJZTPT', 'name': 'projects/ext-datasets/operations/WJVSAIOQZCFK7GYNGOOJZTPT'}\n","Grid 72\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_72_2018', 'priority': 100, 'creation_timestamp_ms': 1762663222778, 'update_timestamp_ms': 1762663222778, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NV7WT3N7XF7IOBORBI3VFM2K', 'name': 'projects/ext-datasets/operations/NV7WT3N7XF7IOBORBI3VFM2K'}\n","Grid 73\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_73_2018', 'priority': 100, 'creation_timestamp_ms': 1762663227215, 'update_timestamp_ms': 1762663227215, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6GA356K45UAKYMSM6T37BUF7', 'name': 'projects/ext-datasets/operations/6GA356K45UAKYMSM6T37BUF7'}\n","Grid 74\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_74_2018', 'priority': 100, 'creation_timestamp_ms': 1762663234448, 'update_timestamp_ms': 1762663234448, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OLWSBKKVMBWHQ64BNHTYWUM4', 'name': 'projects/ext-datasets/operations/OLWSBKKVMBWHQ64BNHTYWUM4'}\n","Grid 75\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_75_2018', 'priority': 100, 'creation_timestamp_ms': 1762663244824, 'update_timestamp_ms': 1762663244824, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RUBCPN5V42II5GI53VMEOHWG', 'name': 'projects/ext-datasets/operations/RUBCPN5V42II5GI53VMEOHWG'}\n","Grid 76\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_76_2018', 'priority': 100, 'creation_timestamp_ms': 1762663250937, 'update_timestamp_ms': 1762663250937, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HE2V5OOESE3NE7ABPGABA2XR', 'name': 'projects/ext-datasets/operations/HE2V5OOESE3NE7ABPGABA2XR'}\n","Grid 77\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_77_2018', 'priority': 100, 'creation_timestamp_ms': 1762663259804, 'update_timestamp_ms': 1762663259804, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L3DC3VJTRX36H3TCSQX4PYUZ', 'name': 'projects/ext-datasets/operations/L3DC3VJTRX36H3TCSQX4PYUZ'}\n","Grid 78\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_78_2018', 'priority': 100, 'creation_timestamp_ms': 1762663267880, 'update_timestamp_ms': 1762663267880, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ULONDXUUNB5HF2LA777GPOHE', 'name': 'projects/ext-datasets/operations/ULONDXUUNB5HF2LA777GPOHE'}\n","Grid 79\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_79_2018', 'priority': 100, 'creation_timestamp_ms': 1762663275114, 'update_timestamp_ms': 1762663275114, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VA5VHD2OWYAHA43AE4UH34WB', 'name': 'projects/ext-datasets/operations/VA5VHD2OWYAHA43AE4UH34WB'}\n","Grid 80\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_80_2018', 'priority': 100, 'creation_timestamp_ms': 1762663282179, 'update_timestamp_ms': 1762663282179, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5I5RISHZ67Y5E74MDD5PBTSX', 'name': 'projects/ext-datasets/operations/5I5RISHZ67Y5E74MDD5PBTSX'}\n","Grid 81\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_81_2018', 'priority': 100, 'creation_timestamp_ms': 1762663288154, 'update_timestamp_ms': 1762663288154, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T4HS6R3X6PEJSRUF53ZXJCFP', 'name': 'projects/ext-datasets/operations/T4HS6R3X6PEJSRUF53ZXJCFP'}\n","Grid 82\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_82_2018', 'priority': 100, 'creation_timestamp_ms': 1762663295485, 'update_timestamp_ms': 1762663295485, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A5PNFUVXZGFQM4BRSEDSFXXZ', 'name': 'projects/ext-datasets/operations/A5PNFUVXZGFQM4BRSEDSFXXZ'}\n","Grid 83\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_83_2018', 'priority': 100, 'creation_timestamp_ms': 1762663302995, 'update_timestamp_ms': 1762663302995, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2A3MCB6W3ITEZZPSWLPDGVHL', 'name': 'projects/ext-datasets/operations/2A3MCB6W3ITEZZPSWLPDGVHL'}\n","Grid 84\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_84_2018', 'priority': 100, 'creation_timestamp_ms': 1762663311352, 'update_timestamp_ms': 1762663311352, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KLFS36SLEME2NACN5TYIRV2O', 'name': 'projects/ext-datasets/operations/KLFS36SLEME2NACN5TYIRV2O'}\n","Grid 85\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_85_2018', 'priority': 100, 'creation_timestamp_ms': 1762663320281, 'update_timestamp_ms': 1762663320281, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '635EVSMRKTJNVKVMSCWHZRI5', 'name': 'projects/ext-datasets/operations/635EVSMRKTJNVKVMSCWHZRI5'}\n","Grid 86\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_86_2018', 'priority': 100, 'creation_timestamp_ms': 1762663327089, 'update_timestamp_ms': 1762663327089, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2KJYEKDKZLN7DT3FHHQF5Q7T', 'name': 'projects/ext-datasets/operations/2KJYEKDKZLN7DT3FHHQF5Q7T'}\n","Grid 87\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_87_2018', 'priority': 100, 'creation_timestamp_ms': 1762663335646, 'update_timestamp_ms': 1762663335646, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HYMXNPZCURLDUHXHMZW4IRFB', 'name': 'projects/ext-datasets/operations/HYMXNPZCURLDUHXHMZW4IRFB'}\n","Grid 88\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_88_2018', 'priority': 100, 'creation_timestamp_ms': 1762663340319, 'update_timestamp_ms': 1762663340319, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJTO5G3CDCRCHQGAJ33TFNHL', 'name': 'projects/ext-datasets/operations/YJTO5G3CDCRCHQGAJ33TFNHL'}\n","Grid 89\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_89_2018', 'priority': 100, 'creation_timestamp_ms': 1762663348259, 'update_timestamp_ms': 1762663348259, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DR4LZPVI52LQAZBSBKEMX6CF', 'name': 'projects/ext-datasets/operations/DR4LZPVI52LQAZBSBKEMX6CF'}\n","Grid 90\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_90_2018', 'priority': 100, 'creation_timestamp_ms': 1762663355018, 'update_timestamp_ms': 1762663355018, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JYNU56WQSWHVJCXNU4Q3YSBN', 'name': 'projects/ext-datasets/operations/JYNU56WQSWHVJCXNU4Q3YSBN'}\n","Grid 91\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_91_2018', 'priority': 100, 'creation_timestamp_ms': 1762663361507, 'update_timestamp_ms': 1762663361507, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NYOFRCDWKY6XN57GICRHY4YB', 'name': 'projects/ext-datasets/operations/NYOFRCDWKY6XN57GICRHY4YB'}\n","Grid 92\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_92_2018', 'priority': 100, 'creation_timestamp_ms': 1762663369331, 'update_timestamp_ms': 1762663369331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6DXI2S35UHUF474BBJOGMKR3', 'name': 'projects/ext-datasets/operations/6DXI2S35UHUF474BBJOGMKR3'}\n","Grid 93\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_93_2018', 'priority': 100, 'creation_timestamp_ms': 1762663375915, 'update_timestamp_ms': 1762663375915, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C4E2A3T3VFQBMPWKVZS5XKUA', 'name': 'projects/ext-datasets/operations/C4E2A3T3VFQBMPWKVZS5XKUA'}\n","Grid 94\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_94_2018', 'priority': 100, 'creation_timestamp_ms': 1762663382522, 'update_timestamp_ms': 1762663382522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QKYZUU5OH3B7ODQEERSJ577G', 'name': 'projects/ext-datasets/operations/QKYZUU5OH3B7ODQEERSJ577G'}\n","Grid 95\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_95_2018', 'priority': 100, 'creation_timestamp_ms': 1762663391225, 'update_timestamp_ms': 1762663391225, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MF2FN325BEDATDQ3Q36ALVWJ', 'name': 'projects/ext-datasets/operations/MF2FN325BEDATDQ3Q36ALVWJ'}\n","Grid 96\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_96_2018', 'priority': 100, 'creation_timestamp_ms': 1762663400147, 'update_timestamp_ms': 1762663400147, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S5T4MFZXEOIYBUX2UNZ6VCKA', 'name': 'projects/ext-datasets/operations/S5T4MFZXEOIYBUX2UNZ6VCKA'}\n","Grid 97\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_97_2018', 'priority': 100, 'creation_timestamp_ms': 1762663407099, 'update_timestamp_ms': 1762663407099, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UARCOETQB7CQ4PVNKAIPLJDN', 'name': 'projects/ext-datasets/operations/UARCOETQB7CQ4PVNKAIPLJDN'}\n","Grid 98\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_98_2018', 'priority': 100, 'creation_timestamp_ms': 1762663415117, 'update_timestamp_ms': 1762663415117, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I7UQA5G2IBIOLNAJSDMI5QH7', 'name': 'projects/ext-datasets/operations/I7UQA5G2IBIOLNAJSDMI5QH7'}\n","Grid 99\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_99_2018', 'priority': 100, 'creation_timestamp_ms': 1762663418758, 'update_timestamp_ms': 1762663418758, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EOJAWWDCL556YQVZTBZXZSB7', 'name': 'projects/ext-datasets/operations/EOJAWWDCL556YQVZTBZXZSB7'}\n","Grid 100\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_100_2018', 'priority': 100, 'creation_timestamp_ms': 1762663422228, 'update_timestamp_ms': 1762663422228, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MGULUVTSCMVEGLIRYY5MGGZX', 'name': 'projects/ext-datasets/operations/MGULUVTSCMVEGLIRYY5MGGZX'}\n","Grid 101\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_101_2018', 'priority': 100, 'creation_timestamp_ms': 1762663429666, 'update_timestamp_ms': 1762663429666, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGGGEUAVN5QJWIM3KNVEBROD', 'name': 'projects/ext-datasets/operations/QGGGEUAVN5QJWIM3KNVEBROD'}\n","Grid 102\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_102_2018', 'priority': 100, 'creation_timestamp_ms': 1762663435992, 'update_timestamp_ms': 1762663435992, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IRKN3Q2SUO57GS6GBRKUHUAR', 'name': 'projects/ext-datasets/operations/IRKN3Q2SUO57GS6GBRKUHUAR'}\n","Grid 103\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_103_2018', 'priority': 100, 'creation_timestamp_ms': 1762663443638, 'update_timestamp_ms': 1762663443638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6N5C7M3TD73HF72B4TSEDWRP', 'name': 'projects/ext-datasets/operations/6N5C7M3TD73HF72B4TSEDWRP'}\n","Grid 104\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_104_2018', 'priority': 100, 'creation_timestamp_ms': 1762663448017, 'update_timestamp_ms': 1762663448017, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LX4OUSI7VBMLE7MEW5ABRB5G', 'name': 'projects/ext-datasets/operations/LX4OUSI7VBMLE7MEW5ABRB5G'}\n","Grid 105\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_105_2018', 'priority': 100, 'creation_timestamp_ms': 1762663455659, 'update_timestamp_ms': 1762663455659, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4R4HCC4REODQHC7PIRJ6OJSW', 'name': 'projects/ext-datasets/operations/4R4HCC4REODQHC7PIRJ6OJSW'}\n","Grid 106\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_106_2018', 'priority': 100, 'creation_timestamp_ms': 1762663464144, 'update_timestamp_ms': 1762663464144, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLKJ7YXCPRAIQXOTVQFQDHGL', 'name': 'projects/ext-datasets/operations/JLKJ7YXCPRAIQXOTVQFQDHGL'}\n","Grid 107\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_107_2018', 'priority': 100, 'creation_timestamp_ms': 1762663471980, 'update_timestamp_ms': 1762663471980, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CPXTOZCKCJQPL22BOY4HJKB2', 'name': 'projects/ext-datasets/operations/CPXTOZCKCJQPL22BOY4HJKB2'}\n","Grid 108\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_108_2018', 'priority': 100, 'creation_timestamp_ms': 1762663480112, 'update_timestamp_ms': 1762663480112, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7WQUBZI63PAYLPWRN7TI7DUO', 'name': 'projects/ext-datasets/operations/7WQUBZI63PAYLPWRN7TI7DUO'}\n","Grid 109\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_109_2018', 'priority': 100, 'creation_timestamp_ms': 1762663484678, 'update_timestamp_ms': 1762663484678, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '26277CAVBGAF6SO3IDWCUQ23', 'name': 'projects/ext-datasets/operations/26277CAVBGAF6SO3IDWCUQ23'}\n","Grid 110\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_110_2018', 'priority': 100, 'creation_timestamp_ms': 1762663493443, 'update_timestamp_ms': 1762663493443, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GUVFSMFTRMKQQ66SYTHYTY7M', 'name': 'projects/ext-datasets/operations/GUVFSMFTRMKQQ66SYTHYTY7M'}\n","Grid 111\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_111_2018', 'priority': 100, 'creation_timestamp_ms': 1762663501199, 'update_timestamp_ms': 1762663501199, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BPIT2V4T7LJAIQ7SHCXRH623', 'name': 'projects/ext-datasets/operations/BPIT2V4T7LJAIQ7SHCXRH623'}\n","2263.010017156601\n","Year 2018, District 13: Birbhum, grids: 76\n","Grid 0\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762663511593, 'update_timestamp_ms': 1762663511593, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H2JM3MGIBTGUZZ6QWRTHS5WC', 'name': 'projects/ext-datasets/operations/H2JM3MGIBTGUZZ6QWRTHS5WC'}\n","Grid 1\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762663517428, 'update_timestamp_ms': 1762663517428, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OM7PMNB6W5GCYJSLARMRBMMH', 'name': 'projects/ext-datasets/operations/OM7PMNB6W5GCYJSLARMRBMMH'}\n","Grid 2\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762663523539, 'update_timestamp_ms': 1762663523539, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YKL6TAP2ZX3RUDTEFRAE72SP', 'name': 'projects/ext-datasets/operations/YKL6TAP2ZX3RUDTEFRAE72SP'}\n","Grid 3\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762663528193, 'update_timestamp_ms': 1762663528193, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CYAWF4ZRFEB3FQYH2XQDDLVP', 'name': 'projects/ext-datasets/operations/CYAWF4ZRFEB3FQYH2XQDDLVP'}\n","Grid 4\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762663533339, 'update_timestamp_ms': 1762663533339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H3DJUDHAMRAA6ZC2WJS2LITN', 'name': 'projects/ext-datasets/operations/H3DJUDHAMRAA6ZC2WJS2LITN'}\n","Grid 5\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762663537707, 'update_timestamp_ms': 1762663537707, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4CYTSCWBPT7UV557XNCHSE6D', 'name': 'projects/ext-datasets/operations/4CYTSCWBPT7UV557XNCHSE6D'}\n","Grid 6\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762663541366, 'update_timestamp_ms': 1762663541366, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JADB7APCIEYRKWLKQTDD3XDB', 'name': 'projects/ext-datasets/operations/JADB7APCIEYRKWLKQTDD3XDB'}\n","Grid 7\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762663548264, 'update_timestamp_ms': 1762663548264, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LIR5JNBB3JRRYRRGHSSILNLM', 'name': 'projects/ext-datasets/operations/LIR5JNBB3JRRYRRGHSSILNLM'}\n","Grid 8\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762663554849, 'update_timestamp_ms': 1762663554849, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZQMJOQSKYN46CWTGYVECLQP2', 'name': 'projects/ext-datasets/operations/ZQMJOQSKYN46CWTGYVECLQP2'}\n","Grid 9\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762663560776, 'update_timestamp_ms': 1762663560776, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QUAJVKJAADMX5B64YMBYMCCT', 'name': 'projects/ext-datasets/operations/QUAJVKJAADMX5B64YMBYMCCT'}\n","Grid 10\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762663564605, 'update_timestamp_ms': 1762663564605, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZGQMYXQBNSUCG7JOQNNLGMHQ', 'name': 'projects/ext-datasets/operations/ZGQMYXQBNSUCG7JOQNNLGMHQ'}\n","Grid 11\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762663574206, 'update_timestamp_ms': 1762663574206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BOHTBJI7X4RBKWCTAH2SGO42', 'name': 'projects/ext-datasets/operations/BOHTBJI7X4RBKWCTAH2SGO42'}\n","Grid 12\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762663578321, 'update_timestamp_ms': 1762663578321, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QOIF6NPM4NX7WXYVFTOD62HI', 'name': 'projects/ext-datasets/operations/QOIF6NPM4NX7WXYVFTOD62HI'}\n","Grid 13\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762663585003, 'update_timestamp_ms': 1762663585003, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FEWZDVACV3MM73VU56O6QKCR', 'name': 'projects/ext-datasets/operations/FEWZDVACV3MM73VU56O6QKCR'}\n","Grid 14\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762663592253, 'update_timestamp_ms': 1762663592253, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AEGN7KBLVW7TDCGDJZE3UPRK', 'name': 'projects/ext-datasets/operations/AEGN7KBLVW7TDCGDJZE3UPRK'}\n","Grid 15\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762663600087, 'update_timestamp_ms': 1762663600087, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6A35UOECIKO7IDU7VASDOPA2', 'name': 'projects/ext-datasets/operations/6A35UOECIKO7IDU7VASDOPA2'}\n","Grid 16\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762663607741, 'update_timestamp_ms': 1762663607741, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QUMNIBMBMU77XUHLLJNEWZLO', 'name': 'projects/ext-datasets/operations/QUMNIBMBMU77XUHLLJNEWZLO'}\n","Grid 17\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762663615561, 'update_timestamp_ms': 1762663615561, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GCARZBAT5AWWRPH3OJW4BSVN', 'name': 'projects/ext-datasets/operations/GCARZBAT5AWWRPH3OJW4BSVN'}\n","Grid 18\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762663625473, 'update_timestamp_ms': 1762663625473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXXV6TUVDZRCNLGCDAIRSLTY', 'name': 'projects/ext-datasets/operations/WXXV6TUVDZRCNLGCDAIRSLTY'}\n","Grid 19\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762663633418, 'update_timestamp_ms': 1762663633418, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NOOPAHRKEMANFDATGNQPBLF5', 'name': 'projects/ext-datasets/operations/NOOPAHRKEMANFDATGNQPBLF5'}\n","Grid 20\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762663640734, 'update_timestamp_ms': 1762663640734, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JC2KD7HRDGJJ4NGFRJ456FPF', 'name': 'projects/ext-datasets/operations/JC2KD7HRDGJJ4NGFRJ456FPF'}\n","Grid 21\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762663646211, 'update_timestamp_ms': 1762663646211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QTLTMLASNBBJ3K6P5N5ZLAMG', 'name': 'projects/ext-datasets/operations/QTLTMLASNBBJ3K6P5N5ZLAMG'}\n","Grid 22\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762663650001, 'update_timestamp_ms': 1762663650001, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMUV7BHDMUGYIR2DNNMAOXVY', 'name': 'projects/ext-datasets/operations/BMUV7BHDMUGYIR2DNNMAOXVY'}\n","Grid 23\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762663658530, 'update_timestamp_ms': 1762663658530, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G5Q323CK5XIMMXQ7R32EOCOY', 'name': 'projects/ext-datasets/operations/G5Q323CK5XIMMXQ7R32EOCOY'}\n","Grid 24\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762663665453, 'update_timestamp_ms': 1762663665453, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K2EXPBLVMPOQTMDKWNVEAGBV', 'name': 'projects/ext-datasets/operations/K2EXPBLVMPOQTMDKWNVEAGBV'}\n","Grid 25\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762663673125, 'update_timestamp_ms': 1762663673125, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NAAECTEI52Q7LNPEVTHHD7UJ', 'name': 'projects/ext-datasets/operations/NAAECTEI52Q7LNPEVTHHD7UJ'}\n","Grid 26\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762663679756, 'update_timestamp_ms': 1762663679756, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MWN2KXYJJ3YY5VETVU5DAGFX', 'name': 'projects/ext-datasets/operations/MWN2KXYJJ3YY5VETVU5DAGFX'}\n","Grid 27\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762663686456, 'update_timestamp_ms': 1762663686456, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3AFPA3QT2ZYJ7UQXU7WDTA5X', 'name': 'projects/ext-datasets/operations/3AFPA3QT2ZYJ7UQXU7WDTA5X'}\n","Grid 28\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762663692690, 'update_timestamp_ms': 1762663692690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DB7DD2TIVCR5ZDNZFPEIGEP5', 'name': 'projects/ext-datasets/operations/DB7DD2TIVCR5ZDNZFPEIGEP5'}\n","Grid 29\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762663698208, 'update_timestamp_ms': 1762663698208, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QPE57Y37LNDBQHGESKKDRBVQ', 'name': 'projects/ext-datasets/operations/QPE57Y37LNDBQHGESKKDRBVQ'}\n","Grid 30\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762663702674, 'update_timestamp_ms': 1762663702674, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y5I4DFMH7NH3XBFOXEXQPXKL', 'name': 'projects/ext-datasets/operations/Y5I4DFMH7NH3XBFOXEXQPXKL'}\n","Grid 31\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762663711360, 'update_timestamp_ms': 1762663711360, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PWM5YTRG3DPYRZGLX4QFGZT5', 'name': 'projects/ext-datasets/operations/PWM5YTRG3DPYRZGLX4QFGZT5'}\n","Grid 32\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762663720094, 'update_timestamp_ms': 1762663720094, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FIES3SUJYX3NLYX64UETMUTG', 'name': 'projects/ext-datasets/operations/FIES3SUJYX3NLYX64UETMUTG'}\n","Grid 33\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762663728790, 'update_timestamp_ms': 1762663728790, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SC6VMP7WP5RHNJFSQ747BCQ2', 'name': 'projects/ext-datasets/operations/SC6VMP7WP5RHNJFSQ747BCQ2'}\n","Grid 34\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762663735030, 'update_timestamp_ms': 1762663735030, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G6O3OU37XUQOKE4R5B6FXCYP', 'name': 'projects/ext-datasets/operations/G6O3OU37XUQOKE4R5B6FXCYP'}\n","Grid 35\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762663744081, 'update_timestamp_ms': 1762663744081, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7MP5LZ7IH4O5TWXMEBP7JVIS', 'name': 'projects/ext-datasets/operations/7MP5LZ7IH4O5TWXMEBP7JVIS'}\n","Grid 36\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762663751712, 'update_timestamp_ms': 1762663751712, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'USS54YJTU7N4VZ5VGUMYYT7P', 'name': 'projects/ext-datasets/operations/USS54YJTU7N4VZ5VGUMYYT7P'}\n","Grid 37\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_37_2018', 'priority': 100, 'creation_timestamp_ms': 1762663761134, 'update_timestamp_ms': 1762663761134, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ECL6WSNZMH5ICPE4VTV72YRL', 'name': 'projects/ext-datasets/operations/ECL6WSNZMH5ICPE4VTV72YRL'}\n","Grid 38\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_38_2018', 'priority': 100, 'creation_timestamp_ms': 1762663767823, 'update_timestamp_ms': 1762663767823, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EB2POV4JCEZ6QQ56VB7BCKJG', 'name': 'projects/ext-datasets/operations/EB2POV4JCEZ6QQ56VB7BCKJG'}\n","Grid 39\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_39_2018', 'priority': 100, 'creation_timestamp_ms': 1762663776544, 'update_timestamp_ms': 1762663776544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NHY7CBINS7J2ZJTF4LFPZ4UV', 'name': 'projects/ext-datasets/operations/NHY7CBINS7J2ZJTF4LFPZ4UV'}\n","Grid 40\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_40_2018', 'priority': 100, 'creation_timestamp_ms': 1762663781778, 'update_timestamp_ms': 1762663781778, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TEDC6SJ6XJVK2XVAIW7XKFUD', 'name': 'projects/ext-datasets/operations/TEDC6SJ6XJVK2XVAIW7XKFUD'}\n","Grid 41\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_41_2018', 'priority': 100, 'creation_timestamp_ms': 1762663789800, 'update_timestamp_ms': 1762663789800, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KS5GS7FHXPGJONAIHTEVHYC7', 'name': 'projects/ext-datasets/operations/KS5GS7FHXPGJONAIHTEVHYC7'}\n","Grid 42\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_42_2018', 'priority': 100, 'creation_timestamp_ms': 1762663796702, 'update_timestamp_ms': 1762663796702, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PSF7PEPBP6GDPHXHGVAGWECG', 'name': 'projects/ext-datasets/operations/PSF7PEPBP6GDPHXHGVAGWECG'}\n","Grid 43\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_43_2018', 'priority': 100, 'creation_timestamp_ms': 1762663805676, 'update_timestamp_ms': 1762663805676, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WWNGLA4RHU5YPSV4HOQZBDUC', 'name': 'projects/ext-datasets/operations/WWNGLA4RHU5YPSV4HOQZBDUC'}\n","Grid 44\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_44_2018', 'priority': 100, 'creation_timestamp_ms': 1762663810992, 'update_timestamp_ms': 1762663810992, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A3MHZDDTEW4RGWEDX5UCQMZH', 'name': 'projects/ext-datasets/operations/A3MHZDDTEW4RGWEDX5UCQMZH'}\n","Grid 45\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_45_2018', 'priority': 100, 'creation_timestamp_ms': 1762663819632, 'update_timestamp_ms': 1762663819632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WD6EHBLYUYLE5226KXJ45MUD', 'name': 'projects/ext-datasets/operations/WD6EHBLYUYLE5226KXJ45MUD'}\n","Grid 46\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_46_2018', 'priority': 100, 'creation_timestamp_ms': 1762663826176, 'update_timestamp_ms': 1762663826176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VKXFLHYNJWRUNQFS4ZA3G6AZ', 'name': 'projects/ext-datasets/operations/VKXFLHYNJWRUNQFS4ZA3G6AZ'}\n","Grid 47\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_47_2018', 'priority': 100, 'creation_timestamp_ms': 1762663833140, 'update_timestamp_ms': 1762663833140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PDHXEB24S7SAVVKYDYL2C4YE', 'name': 'projects/ext-datasets/operations/PDHXEB24S7SAVVKYDYL2C4YE'}\n","Grid 48\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_48_2018', 'priority': 100, 'creation_timestamp_ms': 1762663841579, 'update_timestamp_ms': 1762663841579, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SH5IKCYI6ORNBHBUQYG5CE5X', 'name': 'projects/ext-datasets/operations/SH5IKCYI6ORNBHBUQYG5CE5X'}\n","Grid 49\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_49_2018', 'priority': 100, 'creation_timestamp_ms': 1762663849742, 'update_timestamp_ms': 1762663849742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXGHZKERQ3WE3HTYGOVT6RVE', 'name': 'projects/ext-datasets/operations/WXGHZKERQ3WE3HTYGOVT6RVE'}\n","Grid 50\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_50_2018', 'priority': 100, 'creation_timestamp_ms': 1762663857103, 'update_timestamp_ms': 1762663857103, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B3JD2OLYY4LQTWG67I2ZUMNY', 'name': 'projects/ext-datasets/operations/B3JD2OLYY4LQTWG67I2ZUMNY'}\n","Grid 51\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_51_2018', 'priority': 100, 'creation_timestamp_ms': 1762663863121, 'update_timestamp_ms': 1762663863121, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZMLF5A446ZHJUZCDKADL5QNA', 'name': 'projects/ext-datasets/operations/ZMLF5A446ZHJUZCDKADL5QNA'}\n","Grid 52\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_52_2018', 'priority': 100, 'creation_timestamp_ms': 1762663870721, 'update_timestamp_ms': 1762663870721, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '56OHJBDKRWXYNPQHH6MFMBG3', 'name': 'projects/ext-datasets/operations/56OHJBDKRWXYNPQHH6MFMBG3'}\n","Grid 53\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_53_2018', 'priority': 100, 'creation_timestamp_ms': 1762663874076, 'update_timestamp_ms': 1762663874076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RU46ZESGMS4Q3ATK26TVNIHS', 'name': 'projects/ext-datasets/operations/RU46ZESGMS4Q3ATK26TVNIHS'}\n","Grid 54\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_54_2018', 'priority': 100, 'creation_timestamp_ms': 1762663883001, 'update_timestamp_ms': 1762663883001, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '65TAOCLGJN6DU7XGB4GZKRKK', 'name': 'projects/ext-datasets/operations/65TAOCLGJN6DU7XGB4GZKRKK'}\n","Grid 55\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_55_2018', 'priority': 100, 'creation_timestamp_ms': 1762663893940, 'update_timestamp_ms': 1762663893940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T5A7UTYK2VM3UFMM3MJ6BXRW', 'name': 'projects/ext-datasets/operations/T5A7UTYK2VM3UFMM3MJ6BXRW'}\n","Grid 56\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_56_2018', 'priority': 100, 'creation_timestamp_ms': 1762663899357, 'update_timestamp_ms': 1762663899357, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P6K3Q6R7ZC3MERGKZ6QXYM2N', 'name': 'projects/ext-datasets/operations/P6K3Q6R7ZC3MERGKZ6QXYM2N'}\n","Grid 57\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_57_2018', 'priority': 100, 'creation_timestamp_ms': 1762663907279, 'update_timestamp_ms': 1762663907279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RJH7DNMXHIH6PDL66LZFJCKS', 'name': 'projects/ext-datasets/operations/RJH7DNMXHIH6PDL66LZFJCKS'}\n","Grid 58\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_58_2018', 'priority': 100, 'creation_timestamp_ms': 1762663912561, 'update_timestamp_ms': 1762663912561, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4MXJ5EPJBGOMAWEAK6BDHVGE', 'name': 'projects/ext-datasets/operations/4MXJ5EPJBGOMAWEAK6BDHVGE'}\n","Grid 59\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_59_2018', 'priority': 100, 'creation_timestamp_ms': 1762663920998, 'update_timestamp_ms': 1762663920998, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '57HVEPIIR4JEKBG7J4T2FRLD', 'name': 'projects/ext-datasets/operations/57HVEPIIR4JEKBG7J4T2FRLD'}\n","Grid 60\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_60_2018', 'priority': 100, 'creation_timestamp_ms': 1762663925300, 'update_timestamp_ms': 1762663925300, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZB6N4YQM353T4CZBYLVUAH3D', 'name': 'projects/ext-datasets/operations/ZB6N4YQM353T4CZBYLVUAH3D'}\n","Grid 61\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_61_2018', 'priority': 100, 'creation_timestamp_ms': 1762663933407, 'update_timestamp_ms': 1762663933407, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VHTIGKFDTV7OMYGIBLDCAV22', 'name': 'projects/ext-datasets/operations/VHTIGKFDTV7OMYGIBLDCAV22'}\n","Grid 62\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_62_2018', 'priority': 100, 'creation_timestamp_ms': 1762663942021, 'update_timestamp_ms': 1762663942021, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NH2SNA63OXC76IN3WHWLEZ5T', 'name': 'projects/ext-datasets/operations/NH2SNA63OXC76IN3WHWLEZ5T'}\n","Grid 63\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_63_2018', 'priority': 100, 'creation_timestamp_ms': 1762663949713, 'update_timestamp_ms': 1762663949713, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NFW6EP2F7TWXCGZTGFKSNJJA', 'name': 'projects/ext-datasets/operations/NFW6EP2F7TWXCGZTGFKSNJJA'}\n","Grid 64\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_64_2018', 'priority': 100, 'creation_timestamp_ms': 1762663972275, 'update_timestamp_ms': 1762663972275, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IH6EHYSF6RICKGLTJXLEJTQT', 'name': 'projects/ext-datasets/operations/IH6EHYSF6RICKGLTJXLEJTQT'}\n","Grid 65\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_65_2018', 'priority': 100, 'creation_timestamp_ms': 1762663978337, 'update_timestamp_ms': 1762663978337, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IMD2EJILY3GBGP4ODN56RI3B', 'name': 'projects/ext-datasets/operations/IMD2EJILY3GBGP4ODN56RI3B'}\n","Grid 66\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_66_2018', 'priority': 100, 'creation_timestamp_ms': 1762663988697, 'update_timestamp_ms': 1762663988697, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YWQTVH5IYEM4S2OKPSPWIAPH', 'name': 'projects/ext-datasets/operations/YWQTVH5IYEM4S2OKPSPWIAPH'}\n","Grid 67\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_67_2018', 'priority': 100, 'creation_timestamp_ms': 1762663996373, 'update_timestamp_ms': 1762663996373, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '27N3EXCI3VARLUI2SDOTE5BR', 'name': 'projects/ext-datasets/operations/27N3EXCI3VARLUI2SDOTE5BR'}\n","Grid 68\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_68_2018', 'priority': 100, 'creation_timestamp_ms': 1762664004815, 'update_timestamp_ms': 1762664004815, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VL5AF6S4AOX7G7VGACADXMVJ', 'name': 'projects/ext-datasets/operations/VL5AF6S4AOX7G7VGACADXMVJ'}\n","Grid 69\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_69_2018', 'priority': 100, 'creation_timestamp_ms': 1762664014708, 'update_timestamp_ms': 1762664014708, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VLXNSDOSANSXYUJEFDHAPQ3B', 'name': 'projects/ext-datasets/operations/VLXNSDOSANSXYUJEFDHAPQ3B'}\n","Grid 70\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_70_2018', 'priority': 100, 'creation_timestamp_ms': 1762664024423, 'update_timestamp_ms': 1762664024423, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T35LURRRU3UHVLSHWZ66PHIU', 'name': 'projects/ext-datasets/operations/T35LURRRU3UHVLSHWZ66PHIU'}\n","Grid 71\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_71_2018', 'priority': 100, 'creation_timestamp_ms': 1762664032431, 'update_timestamp_ms': 1762664032431, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FYRKQWWRXKIXHX6X3DMGVIGE', 'name': 'projects/ext-datasets/operations/FYRKQWWRXKIXHX6X3DMGVIGE'}\n","Grid 72\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_72_2018', 'priority': 100, 'creation_timestamp_ms': 1762664036998, 'update_timestamp_ms': 1762664036998, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HILZGFL3KWGDCJA4OPKU2FHR', 'name': 'projects/ext-datasets/operations/HILZGFL3KWGDCJA4OPKU2FHR'}\n","Grid 73\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_73_2018', 'priority': 100, 'creation_timestamp_ms': 1762664045448, 'update_timestamp_ms': 1762664045448, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JAOF2L5EJ7VBNBN5GYFKVKBQ', 'name': 'projects/ext-datasets/operations/JAOF2L5EJ7VBNBN5GYFKVKBQ'}\n","Grid 74\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_74_2018', 'priority': 100, 'creation_timestamp_ms': 1762664051637, 'update_timestamp_ms': 1762664051637, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AGA2LYPNRCWEDRBYJWRAPOLH', 'name': 'projects/ext-datasets/operations/AGA2LYPNRCWEDRBYJWRAPOLH'}\n","Grid 75\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_75_2018', 'priority': 100, 'creation_timestamp_ms': 1762664059346, 'update_timestamp_ms': 1762664059346, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YO4SQXJZQKL4ZMHHZXF3BFNU', 'name': 'projects/ext-datasets/operations/YO4SQXJZQKL4ZMHHZXF3BFNU'}\n","2821.194188594818\n","Year 2018, District 14: Dakshin Dinajpur, grids: 37\n","Grid 0\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664073044, 'update_timestamp_ms': 1762664073044, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WRSC7TBNVGKGUOVTS2YX5GSS', 'name': 'projects/ext-datasets/operations/WRSC7TBNVGKGUOVTS2YX5GSS'}\n","Grid 1\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664080530, 'update_timestamp_ms': 1762664080530, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZR4WASTTV4KDNTKVN7M4YDLR', 'name': 'projects/ext-datasets/operations/ZR4WASTTV4KDNTKVN7M4YDLR'}\n","Grid 2\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762664088096, 'update_timestamp_ms': 1762664088096, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A2R342BUIFTPK3BF4B37PWVH', 'name': 'projects/ext-datasets/operations/A2R342BUIFTPK3BF4B37PWVH'}\n","Grid 3\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762664096150, 'update_timestamp_ms': 1762664096150, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCNGYVV6INADTDCGBVXAZZY5', 'name': 'projects/ext-datasets/operations/FCNGYVV6INADTDCGBVXAZZY5'}\n","Grid 4\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762664103831, 'update_timestamp_ms': 1762664103831, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62464TYAT2PKXK5YCIFPFIXQ', 'name': 'projects/ext-datasets/operations/62464TYAT2PKXK5YCIFPFIXQ'}\n","Grid 5\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762664112856, 'update_timestamp_ms': 1762664112856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EOTDLUXVVPCIHRDRPOCJ7AKP', 'name': 'projects/ext-datasets/operations/EOTDLUXVVPCIHRDRPOCJ7AKP'}\n","Grid 6\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762664118061, 'update_timestamp_ms': 1762664118061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WYKD5KNHUOYOZFWMDXNBSXCA', 'name': 'projects/ext-datasets/operations/WYKD5KNHUOYOZFWMDXNBSXCA'}\n","Grid 7\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762664122228, 'update_timestamp_ms': 1762664122228, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '53UAS25DUR45DOW7S2ZIHAZE', 'name': 'projects/ext-datasets/operations/53UAS25DUR45DOW7S2ZIHAZE'}\n","Grid 8\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762664129592, 'update_timestamp_ms': 1762664129592, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LKLX7A5H2IISEGQOMIUDJIZM', 'name': 'projects/ext-datasets/operations/LKLX7A5H2IISEGQOMIUDJIZM'}\n","Grid 9\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762664138306, 'update_timestamp_ms': 1762664138306, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ORDPCTPYHGFF2733LNV3ZPJG', 'name': 'projects/ext-datasets/operations/ORDPCTPYHGFF2733LNV3ZPJG'}\n","Grid 10\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762664148417, 'update_timestamp_ms': 1762664148417, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZ3ABRVLLCDG2T72JU7OIEDH', 'name': 'projects/ext-datasets/operations/KZ3ABRVLLCDG2T72JU7OIEDH'}\n","Grid 11\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762664155360, 'update_timestamp_ms': 1762664155360, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ATP2S4CRFW667RUCR4KU522Z', 'name': 'projects/ext-datasets/operations/ATP2S4CRFW667RUCR4KU522Z'}\n","Grid 12\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762664162579, 'update_timestamp_ms': 1762664162579, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IWAHODRCOHSRS2ICK5ES4PLW', 'name': 'projects/ext-datasets/operations/IWAHODRCOHSRS2ICK5ES4PLW'}\n","Grid 13\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762664170862, 'update_timestamp_ms': 1762664170862, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A3SMQDKTQZFSFDD4YFSNOC5U', 'name': 'projects/ext-datasets/operations/A3SMQDKTQZFSFDD4YFSNOC5U'}\n","Grid 14\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762664183395, 'update_timestamp_ms': 1762664183395, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I4SEFYNGSRJX2CA3MXLUSE7L', 'name': 'projects/ext-datasets/operations/I4SEFYNGSRJX2CA3MXLUSE7L'}\n","Grid 15\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762664191470, 'update_timestamp_ms': 1762664191470, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZNXGGZKBUGUTRCAK4O7K2ORW', 'name': 'projects/ext-datasets/operations/ZNXGGZKBUGUTRCAK4O7K2ORW'}\n","Grid 16\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762664196031, 'update_timestamp_ms': 1762664196031, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2TJN6PNP3HR77NNJ5R2PVVW5', 'name': 'projects/ext-datasets/operations/2TJN6PNP3HR77NNJ5R2PVVW5'}\n","Grid 17\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762664202924, 'update_timestamp_ms': 1762664202924, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L7AM2H3WPXAYWGDJTRM3HYFO', 'name': 'projects/ext-datasets/operations/L7AM2H3WPXAYWGDJTRM3HYFO'}\n","Grid 18\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762664217745, 'update_timestamp_ms': 1762664217745, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UXFAB6GZRTW2HJIHSQO72GOT', 'name': 'projects/ext-datasets/operations/UXFAB6GZRTW2HJIHSQO72GOT'}\n","Grid 19\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762664226176, 'update_timestamp_ms': 1762664226176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XDKZM4CYU7JXGIMQF3YY4PWD', 'name': 'projects/ext-datasets/operations/XDKZM4CYU7JXGIMQF3YY4PWD'}\n","Grid 20\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762664233704, 'update_timestamp_ms': 1762664233704, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2SZDRF7WC5X7656JH7KDZ3WF', 'name': 'projects/ext-datasets/operations/2SZDRF7WC5X7656JH7KDZ3WF'}\n","Grid 21\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762664248279, 'update_timestamp_ms': 1762664248279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3KD3BG3HIHJUROC6RCMQP4S4', 'name': 'projects/ext-datasets/operations/3KD3BG3HIHJUROC6RCMQP4S4'}\n","Grid 22\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762664255705, 'update_timestamp_ms': 1762664255705, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XVSNVNMLA4BPOVAVCGDCBSUP', 'name': 'projects/ext-datasets/operations/XVSNVNMLA4BPOVAVCGDCBSUP'}\n","Grid 23\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762664271104, 'update_timestamp_ms': 1762664271104, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KK2XZ6YFYF7H6IQ4Z3V2K6FR', 'name': 'projects/ext-datasets/operations/KK2XZ6YFYF7H6IQ4Z3V2K6FR'}\n","Grid 24\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762664279095, 'update_timestamp_ms': 1762664279095, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PL52ONVJZD7PKQNS2TBFND6J', 'name': 'projects/ext-datasets/operations/PL52ONVJZD7PKQNS2TBFND6J'}\n","Grid 25\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762664284347, 'update_timestamp_ms': 1762664284347, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UVMEMENXU3HOR43V6NHO6OM6', 'name': 'projects/ext-datasets/operations/UVMEMENXU3HOR43V6NHO6OM6'}\n","Grid 26\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762664292053, 'update_timestamp_ms': 1762664292053, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5VGYNHRU5MM77ZVACEZAI6SY', 'name': 'projects/ext-datasets/operations/5VGYNHRU5MM77ZVACEZAI6SY'}\n","Grid 27\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762664298908, 'update_timestamp_ms': 1762664298908, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6TV5UZ564K3RZ6RULWKZZSFN', 'name': 'projects/ext-datasets/operations/6TV5UZ564K3RZ6RULWKZZSFN'}\n","Grid 28\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762664303868, 'update_timestamp_ms': 1762664303868, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPRLKYSXBFI6F3XSSB2CALXJ', 'name': 'projects/ext-datasets/operations/PPRLKYSXBFI6F3XSSB2CALXJ'}\n","Grid 29\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762664311473, 'update_timestamp_ms': 1762664311473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4QKGB674VY46L6HHLFTAUDHK', 'name': 'projects/ext-datasets/operations/4QKGB674VY46L6HHLFTAUDHK'}\n","Grid 30\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762664318310, 'update_timestamp_ms': 1762664318310, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4F7BB7DJRPVBHCEVQ6ZV6OSN', 'name': 'projects/ext-datasets/operations/4F7BB7DJRPVBHCEVQ6ZV6OSN'}\n","Grid 31\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762664325371, 'update_timestamp_ms': 1762664325371, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LFXTJ4MDTBICB2J54A6NII7A', 'name': 'projects/ext-datasets/operations/LFXTJ4MDTBICB2J54A6NII7A'}\n","Grid 32\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762664331713, 'update_timestamp_ms': 1762664331713, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UGEKFPPPWSI6S2QECYLXUAIS', 'name': 'projects/ext-datasets/operations/UGEKFPPPWSI6S2QECYLXUAIS'}\n","Grid 33\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762664340044, 'update_timestamp_ms': 1762664340044, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OKWS5AKSZBNIJMDQZPX3DO5I', 'name': 'projects/ext-datasets/operations/OKWS5AKSZBNIJMDQZPX3DO5I'}\n","Grid 34\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762664348038, 'update_timestamp_ms': 1762664348038, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7B6CUKVIVPZLZH36SJJDIZC4', 'name': 'projects/ext-datasets/operations/7B6CUKVIVPZLZH36SJJDIZC4'}\n","Grid 35\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762664358407, 'update_timestamp_ms': 1762664358407, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GGKEM47OSNSJ4M2QVJL3CLD4', 'name': 'projects/ext-datasets/operations/GGKEM47OSNSJ4M2QVJL3CLD4'}\n","Grid 36\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762664364512, 'update_timestamp_ms': 1762664364512, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZI7LHFRUCDLSECQFOS6JAI2Q', 'name': 'projects/ext-datasets/operations/ZI7LHFRUCDLSECQFOS6JAI2Q'}\n","3126.3451647758484\n","Year 2018, District 15: Darjiling, grids: 2\n","Grid 0\n","curr_year 2018\n","Saving data for Darjiling 2018\n","Task Started {'state': 'READY', 'description': 'Darjiling_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664376817, 'update_timestamp_ms': 1762664376817, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFZCUUXASEZZKFGGUCP2P5GU', 'name': 'projects/ext-datasets/operations/IFZCUUXASEZZKFGGUCP2P5GU'}\n","Grid 1\n","curr_year 2018\n","Saving data for Darjiling 2018\n","Task Started {'state': 'READY', 'description': 'Darjiling_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664383693, 'update_timestamp_ms': 1762664383693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '44DTTT2UGSGPC5PJYRDSK4EV', 'name': 'projects/ext-datasets/operations/44DTTT2UGSGPC5PJYRDSK4EV'}\n","3145.506931781769\n","Year 2018, District 16: Haora, grids: 30\n","Grid 0\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664398092, 'update_timestamp_ms': 1762664398092, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4AJORJDRNG6LP53KMGIYCXMH', 'name': 'projects/ext-datasets/operations/4AJORJDRNG6LP53KMGIYCXMH'}\n","Grid 1\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664405353, 'update_timestamp_ms': 1762664405353, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DMGRBT4HG5WKSSM2CQDCFQGW', 'name': 'projects/ext-datasets/operations/DMGRBT4HG5WKSSM2CQDCFQGW'}\n","Grid 2\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762664412695, 'update_timestamp_ms': 1762664412695, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XU43F4PQAWPNJRLWOC3PZTTZ', 'name': 'projects/ext-datasets/operations/XU43F4PQAWPNJRLWOC3PZTTZ'}\n","Grid 3\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762664419564, 'update_timestamp_ms': 1762664419564, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MM4MFBBIP46U75JCTAWEF325', 'name': 'projects/ext-datasets/operations/MM4MFBBIP46U75JCTAWEF325'}\n","Grid 4\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762664427124, 'update_timestamp_ms': 1762664427124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFIN6KZ4KF4JYO3WIOIZQEQK', 'name': 'projects/ext-datasets/operations/IFIN6KZ4KF4JYO3WIOIZQEQK'}\n","Grid 5\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762664434906, 'update_timestamp_ms': 1762664434906, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PEZIF46GZ753LJRMW5V7EGWX', 'name': 'projects/ext-datasets/operations/PEZIF46GZ753LJRMW5V7EGWX'}\n","Grid 6\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762664443151, 'update_timestamp_ms': 1762664443151, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DLVEWDCZKMIJQPVDADOVYUBT', 'name': 'projects/ext-datasets/operations/DLVEWDCZKMIJQPVDADOVYUBT'}\n","Grid 7\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762664448495, 'update_timestamp_ms': 1762664448495, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K4QSIV3GJKSAUILFTVMPTPW3', 'name': 'projects/ext-datasets/operations/K4QSIV3GJKSAUILFTVMPTPW3'}\n","Grid 8\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762664453529, 'update_timestamp_ms': 1762664453529, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKMRID4ZKASKWRSPD5KOPS55', 'name': 'projects/ext-datasets/operations/SKMRID4ZKASKWRSPD5KOPS55'}\n","Grid 9\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762664463309, 'update_timestamp_ms': 1762664463309, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJTSZOTEFBWLQ2VR36PMUHCV', 'name': 'projects/ext-datasets/operations/YJTSZOTEFBWLQ2VR36PMUHCV'}\n","Grid 10\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762664469940, 'update_timestamp_ms': 1762664469940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LR5DDCWXWBLZWTTPTR6AB3HM', 'name': 'projects/ext-datasets/operations/LR5DDCWXWBLZWTTPTR6AB3HM'}\n","Grid 11\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762664478105, 'update_timestamp_ms': 1762664478105, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QWBZ24PODX3TCUEW7SK56EKU', 'name': 'projects/ext-datasets/operations/QWBZ24PODX3TCUEW7SK56EKU'}\n","Grid 12\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762664485766, 'update_timestamp_ms': 1762664485766, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6HQP2DXYEJ3RCR3PPY5SOHPZ', 'name': 'projects/ext-datasets/operations/6HQP2DXYEJ3RCR3PPY5SOHPZ'}\n","Grid 13\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762664493474, 'update_timestamp_ms': 1762664493474, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4CGS5URTKASIAASJOEYUTP7', 'name': 'projects/ext-datasets/operations/V4CGS5URTKASIAASJOEYUTP7'}\n","Grid 14\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762664500714, 'update_timestamp_ms': 1762664500714, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JWHRKPTAWQLVGGKDBABX36A3', 'name': 'projects/ext-datasets/operations/JWHRKPTAWQLVGGKDBABX36A3'}\n","Grid 15\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762664504652, 'update_timestamp_ms': 1762664504652, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IK765SYM32CH3J4YCKTH5YMT', 'name': 'projects/ext-datasets/operations/IK765SYM32CH3J4YCKTH5YMT'}\n","Grid 16\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762664512845, 'update_timestamp_ms': 1762664512845, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '66G25QAKNUVJJZU6K4GIAZLI', 'name': 'projects/ext-datasets/operations/66G25QAKNUVJJZU6K4GIAZLI'}\n","Grid 17\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762664519636, 'update_timestamp_ms': 1762664519636, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4Q4APFMPM37CWZNDNBQIIVL6', 'name': 'projects/ext-datasets/operations/4Q4APFMPM37CWZNDNBQIIVL6'}\n","Grid 18\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762664525286, 'update_timestamp_ms': 1762664525286, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KKHIYOLL6BV2VSDOLG2LTKTP', 'name': 'projects/ext-datasets/operations/KKHIYOLL6BV2VSDOLG2LTKTP'}\n","Grid 19\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762664536675, 'update_timestamp_ms': 1762664536675, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLT5QIEXY5TB2EBUKWLXSZYP', 'name': 'projects/ext-datasets/operations/JLT5QIEXY5TB2EBUKWLXSZYP'}\n","Grid 20\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762664544203, 'update_timestamp_ms': 1762664544203, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FHJRHJSOOMUO6PMBUJS2TS4F', 'name': 'projects/ext-datasets/operations/FHJRHJSOOMUO6PMBUJS2TS4F'}\n","Grid 21\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762664552129, 'update_timestamp_ms': 1762664552129, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '224FAS3URVBHBNTR2DMQO7LZ', 'name': 'projects/ext-datasets/operations/224FAS3URVBHBNTR2DMQO7LZ'}\n","Grid 22\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762664559936, 'update_timestamp_ms': 1762664559936, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F2O57WV3QQRWIR3FEDBM2OLS', 'name': 'projects/ext-datasets/operations/F2O57WV3QQRWIR3FEDBM2OLS'}\n","Grid 23\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762664567483, 'update_timestamp_ms': 1762664567483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NLSB5T3BA7WDJLPXRUWT7XCE', 'name': 'projects/ext-datasets/operations/NLSB5T3BA7WDJLPXRUWT7XCE'}\n","Grid 24\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762664575693, 'update_timestamp_ms': 1762664575693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RHY37T5Q2OQC6Q4K6WTPDKWY', 'name': 'projects/ext-datasets/operations/RHY37T5Q2OQC6Q4K6WTPDKWY'}\n","Grid 25\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762664584856, 'update_timestamp_ms': 1762664584856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYOYSH4L2WLKLGRUYDRO2IIL', 'name': 'projects/ext-datasets/operations/ZYOYSH4L2WLKLGRUYDRO2IIL'}\n","Grid 26\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762664593327, 'update_timestamp_ms': 1762664593327, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XUSJK6T5NUNK33IWDEWIKVQS', 'name': 'projects/ext-datasets/operations/XUSJK6T5NUNK33IWDEWIKVQS'}\n","Grid 27\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762664601828, 'update_timestamp_ms': 1762664601828, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZS2KGFD5LLDWONYEZARCUXMF', 'name': 'projects/ext-datasets/operations/ZS2KGFD5LLDWONYEZARCUXMF'}\n","Grid 28\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762664609121, 'update_timestamp_ms': 1762664609121, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZDTE4SYNHNMOT7XJC6EUM7KI', 'name': 'projects/ext-datasets/operations/ZDTE4SYNHNMOT7XJC6EUM7KI'}\n","Grid 29\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762664618077, 'update_timestamp_ms': 1762664618077, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZOPAN6335LX4MQDYKBGBIOI', 'name': 'projects/ext-datasets/operations/KZOPAN6335LX4MQDYKBGBIOI'}\n","3379.8888437747955\n","Year 2018, District 17: Hugli, grids: 55\n","Grid 0\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664632691, 'update_timestamp_ms': 1762664632691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4LSJGFY2HUKLUUD25DVIEWCH', 'name': 'projects/ext-datasets/operations/4LSJGFY2HUKLUUD25DVIEWCH'}\n","Grid 1\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664641441, 'update_timestamp_ms': 1762664641441, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A4WXJXOMDX4Y4U3JSNH7CAOP', 'name': 'projects/ext-datasets/operations/A4WXJXOMDX4Y4U3JSNH7CAOP'}\n","Grid 2\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762664648491, 'update_timestamp_ms': 1762664648491, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QCOZ2SE6IBF23LRZOWNIK7I5', 'name': 'projects/ext-datasets/operations/QCOZ2SE6IBF23LRZOWNIK7I5'}\n","Grid 3\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762664655077, 'update_timestamp_ms': 1762664655077, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGY3SXOYPZJRM6CCJVD7N44O', 'name': 'projects/ext-datasets/operations/QGY3SXOYPZJRM6CCJVD7N44O'}\n","Grid 4\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762664658729, 'update_timestamp_ms': 1762664658729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FEG7VEPW6AEF4TBAUNISMCVN', 'name': 'projects/ext-datasets/operations/FEG7VEPW6AEF4TBAUNISMCVN'}\n","Grid 5\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762664667503, 'update_timestamp_ms': 1762664667503, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6LFNZ5RUDRONF3QYFFTRTY2W', 'name': 'projects/ext-datasets/operations/6LFNZ5RUDRONF3QYFFTRTY2W'}\n","Grid 6\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762664676185, 'update_timestamp_ms': 1762664676185, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O23VN4G5WXMZZGQ5NIRKZDNF', 'name': 'projects/ext-datasets/operations/O23VN4G5WXMZZGQ5NIRKZDNF'}\n","Grid 7\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762664684106, 'update_timestamp_ms': 1762664684106, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AWFJ6TBAYZGDS4FGMAXOTC7I', 'name': 'projects/ext-datasets/operations/AWFJ6TBAYZGDS4FGMAXOTC7I'}\n","Grid 8\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762664690851, 'update_timestamp_ms': 1762664690851, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5I3ID4OIFJKVQZMU3HUI3XJ5', 'name': 'projects/ext-datasets/operations/5I3ID4OIFJKVQZMU3HUI3XJ5'}\n","Grid 9\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762664695983, 'update_timestamp_ms': 1762664695983, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XWFMYBV4GJQDNXQGETMD6PE6', 'name': 'projects/ext-datasets/operations/XWFMYBV4GJQDNXQGETMD6PE6'}\n","Grid 10\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762664701159, 'update_timestamp_ms': 1762664701159, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TF7RAOWBKSVHM6VZ6DS6ZH2A', 'name': 'projects/ext-datasets/operations/TF7RAOWBKSVHM6VZ6DS6ZH2A'}\n","Grid 11\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762664711085, 'update_timestamp_ms': 1762664711085, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '55HGWTLODW7NRPMW4EFOJ643', 'name': 'projects/ext-datasets/operations/55HGWTLODW7NRPMW4EFOJ643'}\n","Grid 12\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762664718074, 'update_timestamp_ms': 1762664718074, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XEYISSMQHOBBT45CSUTN5CC5', 'name': 'projects/ext-datasets/operations/XEYISSMQHOBBT45CSUTN5CC5'}\n","Grid 13\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762664723363, 'update_timestamp_ms': 1762664723363, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYDJYVBHE5MEXHJNFV6RI5D4', 'name': 'projects/ext-datasets/operations/XYDJYVBHE5MEXHJNFV6RI5D4'}\n","Grid 14\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762664730830, 'update_timestamp_ms': 1762664730830, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3D3I4DWRJUNUFQ3KMFCRPR65', 'name': 'projects/ext-datasets/operations/3D3I4DWRJUNUFQ3KMFCRPR65'}\n","Grid 15\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762664735785, 'update_timestamp_ms': 1762664735785, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5YR6LDIBSLFGY2JJNJHTLOSY', 'name': 'projects/ext-datasets/operations/5YR6LDIBSLFGY2JJNJHTLOSY'}\n","Grid 16\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762664736650, 'update_timestamp_ms': 1762664736650, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TFVTOFLDYMIPGB7HHLZ3JDZL', 'name': 'projects/ext-datasets/operations/TFVTOFLDYMIPGB7HHLZ3JDZL'}\n","Grid 17\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762664737289, 'update_timestamp_ms': 1762664737289, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EYOMWWQPIQVBDRE4YDUWDWL3', 'name': 'projects/ext-datasets/operations/EYOMWWQPIQVBDRE4YDUWDWL3'}\n","Grid 18\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762664743037, 'update_timestamp_ms': 1762664743037, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LQRI6YRY4TXUSC4GKF6LYOE6', 'name': 'projects/ext-datasets/operations/LQRI6YRY4TXUSC4GKF6LYOE6'}\n","Grid 19\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762664744354, 'update_timestamp_ms': 1762664744354, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ML45MRARB7WAQXAZWBJI43YD', 'name': 'projects/ext-datasets/operations/ML45MRARB7WAQXAZWBJI43YD'}\n","Grid 20\n","curr_year 2018\n"]}],"source":["# years = ['2016']\n","for curr_year in years:\n","\n"," total_time = 0\n","\n"," dist_cnt = 0\n"," for district in dist_list:\n","\n"," # if dist_cnt < 3:\n"," # dist_cnt += 1\n"," # continue\n","\n"," start_time = time.time()\n","\n"," district_aoi = india_district_boundary.filter(ee.Filter.eq('Name', district)).geometry()\n"," district_aoi = district_aoi.intersection(agrozone)\n"," features = createGrids(district_aoi)\n","\n"," print(f'Year {curr_year}, District {dist_cnt}: {district}, grids: {len(features)}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{curr_year}/'\n"," os.makedirs(path, exist_ok=True)\n","\n"," df = pd.DataFrame()\n"," df['grid_num'] = list(range(len(features)))\n"," tree_points_list = []\n","\n"," i = 0\n"," for feature in features:\n"," print(f'Grid {i}')\n","\n"," coord = feature['geometry']['coordinates'][0]\n"," aoi = ee.Geometry.Polygon(coord)\n"," aoi = aoi.intersection(district_aoi)\n","\n"," img_name = district + \"_\" + str(i) + \"_\" + str(curr_year)\n","\n"," start_date = START_DATE[int(curr_year)]\n"," end_date = END_DATE[int(curr_year)]\n","\n"," # Get tree cover using both Dynamic World and IndiaSat (union)\n"," tree_cover = get_tree_cover(aoi, curr_year, scale=25)\n","\n"," season = 'kharif'\n"," try:\n"," image = get_s1_image(aoi, start_date[season], end_date[season])\n"," image = image.updateMask(tree_cover)\n"," except Exception as exp:\n"," print(\"S1 Error occured: \", season, exp)\n","\n"," try:\n"," s2_data = get_s2_image(aoi, start_date[season], end_date[season])\n"," s2_data = s2_data.updateMask(tree_cover)\n"," image = image.addBands(s2_data).select(s1_bands + s2_bands)\n"," image = image.rename([band + '_kharif' for band in s1_bands + s2_bands])\n"," except Exception as exp:\n"," print(\"S2 Error occured: \", season, exp)\n","\n"," for season in ['rabi', 'zaid']:\n"," try:\n"," s1_data = get_s1_image(aoi, start_date[season], end_date[season])\n"," s1_data = s1_data.updateMask(tree_cover)\n","\n"," except Exception as exp:\n"," print(\"S1 Error occured: \", season, exp)\n","\n"," try:\n"," s2_data = get_s2_image(aoi, start_date[season], end_date[season])\n"," s2_data = s2_data.updateMask(tree_cover)\n"," image_merged = s1_data.addBands(s2_data).select(s1_bands + s2_bands)\n"," image_merged = image_merged.rename([band + '_' + season for band in s1_bands + s2_bands])\n"," image = image.addBands(image_merged)\n","\n"," except Exception as exp:\n"," print(\"S2 Error occured: \", season, exp)\n","\n"," sample_points = image.sample(\n"," region = aoi,\n"," scale = 25,\n"," factor = 1,\n"," tileScale = 10,\n"," geometries = True\n"," )\n","\n"," sample_tree_cover = tree_cover.sample(\n"," region = aoi,\n"," scale = 25,\n"," factor = 1,\n"," tileScale = 10,\n"," geometries = True\n"," )\n","\n"," try:\n"," tree_points_list.append(sample_tree_cover.size().getInfo())\n"," except:\n"," tree_points_list.append(0)\n","\n"," try:\n"," task = save_data_csv(sample_points, img_name, district, curr_year)\n"," prev_task = task\n"," # tasks[curr_year][district] = task\n","\n"," except Exception as e:\n"," print(e)\n"," while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," # time.sleep(10)\n"," continue\n"," task = save_data_csv(sample_points, img_name, path, curr_year)\n"," # tasks[curr_year][district] = task\n"," prev_task = task\n","\n"," i += 1\n","\n"," df['tree_points'] = tree_points_list\n"," df.to_csv(path + 'tree_cover_points.csv', index=False)\n","\n"," dist_cnt += 1\n"," end_time = time.time()\n","\n"," total_time += (end_time - start_time)\n"," print(total_time)\n","\n"," print(\"Waiting for last task to be completed...\")\n"," while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," continue\n"," print(\"Last task completed!\")\n","\n"," total_time += (time.time() - end_time)\n"," print(\"Total Time Taken:\", total_time)"]}],"metadata":{"colab":{"collapsed_sections":["snxgyeoRno-K"],"provenance":[{"file_id":"10rsYxkf8VtyBQYo6nDSxJeQg-Kr0OKDh","timestamp":1758123472823}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb b/utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb new file mode 100644 index 00000000..3b0c650b --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":3189,"status":"ok","timestamp":1775402326200,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"kMqXI9xEx9Ij"},"outputs":[],"source":["\n","import pandas as pd\n","from glob import glob\n","import joblib\n","import json\n","import time\n","from copy import deepcopy\n","import os\n","import re\n","import numpy as np\n","import ee\n","import ast"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":10,"status":"ok","timestamp":1771429377492,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"EX11mwSCcEls","outputId":"03f4e89d-5d52-4780-b0a8-6f7bf4864843"},"outputs":[{"name":"stdout","output_type":"stream","text":["Drive not mounted, so nothing to flush and unmount.\n"]}],"source":["# from google.colab import drive\n","# drive.flush_and_unmount()"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":22349,"status":"ok","timestamp":1775402348561,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"3AzHO9S6xtlx","outputId":"dc759937-64e1-4993-b5ea-30d480ae29d2"},"outputs":[{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":3,"metadata":{"executionInfo":{"elapsed":1,"status":"ok","timestamp":1775402348576,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"6v5AhpfqyCIX"},"outputs":[],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":4,"metadata":{"executionInfo":{"elapsed":2,"status":"ok","timestamp":1775402354345,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"27BoVlCPyGq5"},"outputs":[],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}\n"]},{"cell_type":"code","execution_count":5,"metadata":{"executionInfo":{"elapsed":2,"status":"ok","timestamp":1775402361003,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"z9qLXf0PyK7B"},"outputs":[],"source":["# agroclimatic_zone = 'Eastern Plateau & Hills Region'\n","agroclimatic_zone = 'Southern Plateau and Hills Region'\n","# agroclimatic_zone = \"East Coast Plains & Hills Region\"\n","# agroclimatic_zone = 'Western Plateau and Hills Region'\n","# agroclimatic_zone = \"Central Plateau & Hills Region\"\n","# agroclimatic_zone = 'Lower Gangetic Plain Region'\n","# agroclimatic_zone = 'Middle Gangetic Plain Region'\n","# agroclimatic_zone = 'Eastern Himalayan Region'\n","# agroclimatic_zone = 'Western Himalayan Region'\n","# agroclimatic_zone = 'Trans Gangetic Plain Region'\n","# agroclimatic_zone = \"Upper Gangetic Plain Region\"\n","# agroclimatic_zone = \"West Coast Plains & Ghat Region\" # Model not available\n","# agroclimatic_zone = 'Gujarat Plains & Hills Region' # Model not available\n","# agroclimatic_zone = 'Western Dry Region' # Model not available"]},{"cell_type":"code","execution_count":6,"metadata":{"executionInfo":{"elapsed":3,"status":"ok","timestamp":1775402369694,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"LNPicKBZyQfD"},"outputs":[],"source":["# Set the list of years for which to rename folders\n","years = ['2017']# ['2016', '2017', '2018','2019','2020','2021','2022','2023','2024']"]},{"cell_type":"markdown","metadata":{"id":"P_Php5yrbPm8"},"source":["# CHM"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":13,"status":"ok","timestamp":1775402390205,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"-m5EML9zyVX4","outputId":"444a0e1c-35ef-4049-9799-7eba9924b2f6"},"outputs":[{"output_type":"stream","name":"stdout","text":["['Anantapur']\n"]}],"source":["df = pd.read_csv(f'/content/drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = list(df['Name'])\n","dist_list = ['Anantapur']\n","print(dist_list)"]},{"cell_type":"code","execution_count":9,"metadata":{"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775402392754,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"tMOnWtVNygfj"},"outputs":[],"source":["# Set the root directory to the specified folder\n","root_dir = '/content/drive/MyDrive/'\n","os.chdir(root_dir)"]},{"cell_type":"code","execution_count":10,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":108,"status":"ok","timestamp":1775402395585,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ybIqOkpbykty","outputId":"76b07e97-7cca-4407-a346-174ea2639b37"},"outputs":[{"output_type":"stream","name":"stdout","text":["/content/drive/MyDrive\n"]}],"source":["! pwd"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":28,"status":"ok","timestamp":1775402397122,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"iiwNIME5yo3A","outputId":"9d48c10e-ba93-4a7b-eb20-3633974cfd7f"},"outputs":[{"output_type":"stream","name":"stdout","text":["core-stack-docs\n","kapil_test\n","Colab Notebooks\n","Pan_india_maps_screenshots\n","ATCF_ch_5jun25\n","Google Earth\n","Datasets\n","WBC_data\n","Apache\n","tiff_export\n","Pan_india\n","Aquifer_vector\n","Aquifer_export\n","TreeHealth\n","MGPR\n","pennar_lulc_23_24\n","LGPR1\n","lcw\n","Untitled form (File responses)\n","change_test\n","ET_downloads\n","core_stack_manual\n","WHR_2024\n","WHR_2023\n","WHR_2022\n","WHR_2021\n","WHR_2020\n","WHR_2019\n","WHR_2018\n","WHR_2017\n","WHR_2016\n","EPAHR_2016\n","EPAHR_2017\n","EPAHR_2018\n","EPAHR_2019\n","EPAHR_2020\n","EPAHR_2021\n","EPAHR_2022\n","EPAHR_2023\n","SPAHR_2016\n","SPAHR_2017\n","SPAHR_2018\n","SPAHR_2019\n","SPAHR_2020\n","SPAHR_2021\n","SPAHR_2022\n","SPAHR_2023\n","ECPHR_2016\n","ECPHR_2017\n","ECPHR_2018\n","ECPHR_2019\n","ECPHR_2020\n","ECPHR_2021\n","ECPHR_2022\n","ECPHR_2023\n","WPAHR_2016\n","WPAHR_2017\n","WPAHR_2018\n","WPAHR_2019\n","WPAHR_2020\n","WPAHR_2021\n","WPAHR_2022\n","WPAHR_2023\n","CPAHR_2016\n","CPAHR_2017\n","CPAHR_2018\n","CPAHR_2019\n","CPAHR_2020\n","CPAHR_2021\n","CPAHR_2022\n","CPAHR_2023\n","LGPR_2016\n","MGPR_2016\n","EHR_2016\n","EHR_2017\n","EHR_2018\n","EHR_2019\n","EHR_2020\n","EHR_2021\n","EHR_2022\n","EHR_2023\n","UGPR_2016\n","UGPR_2017\n","UGPR_2018\n","UGPR_2019\n","UGPR_2020\n","UGPR_2021\n","UGPR_2022\n","UGPR_2023\n","TGPR_2016\n","TGPR_2017\n","TGPR_2018\n","TGPR_2019\n","TGPR_2020\n","TGPR_2021\n","TGPR_2022\n","TGPR_2023\n","WCPGR_2016\n","WCPGR_2017\n","WCPGR_2018\n","WCPGR_2019\n","WCPGR_2020\n","WCPGR_2021\n","WCPGR_2022\n","WCPGR_2023\n","GPHR_2016\n","GPHR_2017\n","GPHR_2018\n","GPHR_2019\n","GPHR_2020\n","GPHR_2021\n","GPHR_2022\n","GPHR_2023\n","WDR_2016\n","WDR_2017\n","WDR_2018\n","WDR_2019\n","WDR_2020\n","WDR_2021\n","WDR_2022\n","WDR_2023\n","LGPR_2017\n","LGPR_2018\n","LGPR_2019\n","LGPR_2020\n","LGPR_2021\n","LGPR_2022\n","LGPR_2023\n","EHR_2024\n","WDR_2024\n","UGPR_2024\n","hydrological_boundaries\n","TGPR_2024\n","MGPR_2024\n","LGPR_2024\n","EPAHR_2024\n","SPAHR_2024\n","ECPHR_2024\n","WPAHR_2024\n","CPAHR_2024\n","MGPR_2017\n","MGPR_2018\n","MGPR_2019\n","MGPR_2020\n","MGPR_2021\n","MGPR_2022\n","MGPR_2023\n","mws_tmp\n","FOSS - CoRE Stack Material\n","wassan_boundaries\n","demand validtor\n"]}],"source":["# Get the current directory\n","current_directory = os.getcwd()\n","\n","# List all folders in the current directory\n","folders = [f for f in os.listdir(current_directory) if os.path.isdir(os.path.join(current_directory, f))]\n","\n","# Print the list of folders\n","# print(\"Folders in the current directory:\")\n","\n","for folder in folders:\n"," print(folder)"]},{"cell_type":"code","execution_count":12,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775402400124,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"VeRQDIsyysC-","outputId":"c9b23c9f-78c3-4baa-9bae-44dde8cb023c"},"outputs":[{"output_type":"stream","name":"stdout","text":["Folders starting with 'SPAHR':\n","SPAHR_2016\n","SPAHR_2017\n","SPAHR_2018\n","SPAHR_2019\n","SPAHR_2020\n","SPAHR_2021\n","SPAHR_2022\n","SPAHR_2023\n","SPAHR_2024\n"]}],"source":["# Regular expression pattern to match folder names starting with a particular pattern\n","pattern = re.compile(r'^' + f'{agroclimaticZone_acronym_dict[agroclimatic_zone]}')\n","\n","# Filter folders based on the pattern\n","matching_folders = [folder for folder in folders if pattern.match(folder)]\n","\n","# Print the matching folders\n","print(f\"Folders starting with '{agroclimaticZone_acronym_dict[agroclimatic_zone]}':\")\n","for folder in matching_folders:\n"," print(folder)"]},{"cell_type":"code","execution_count":13,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":11,"status":"ok","timestamp":1775402403714,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"KxX7a9qMywMW","outputId":"a0873f64-5add-4fde-9974-66029b019278"},"outputs":[{"output_type":"stream","name":"stdout","text":["2017\n","district_year_folders--> ['SPAHR_2017']\n"]}],"source":["# Leh (Ladkh) is the only district encountered having special character '(' and ')' in it. That's why its handled in a special way as seen here\n","\n","# Transfer files from duplicate folders to original folder\n","\n","dist_num = 0\n","# for district in dist_list:\n","# print(dist_num)\n","for year in years:\n"," print(year)\n","\n"," # orig_district = district\n"," # if district == 'Leh (Ladakh)':\n"," # district = 'Leh'\n","\n"," # pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + district + '_' + year)\n"," pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + year)\n"," district_year_folders = [folder for folder in matching_folders if pattern.match(folder)]\n"," print(\"district_year_folders-->\", district_year_folders)\n","\n"," # district = orig_district\n","\n"," while len(district_year_folders) > 1:\n"," source_folder = district_year_folders[0]\n"," destination_folder = district_year_folders[1]\n","\n"," files_to_move = os.listdir(source_folder)\n"," for file_name in files_to_move:\n"," source_path = os.path.join(source_folder, file_name)\n"," destination_path = os.path.join(destination_folder, file_name)\n"," os.rename(source_path, destination_path)\n","\n"," del district_year_folders[0]\n"," if len(district_year_folders) > 0:\n"," current_folder_name = district_year_folders[0]\n"," # new_folder_name = f'{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{district}_{year}'\n"," new_folder_name = f'{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{year}'\n"," os.rename(current_folder_name, new_folder_name)\n","\n"," # dist_num += 1"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775402407191,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"VjlSG5iPQkyP","outputId":"c10595ee-e3dd-47b0-dae2-9ec79f181cfd"},"outputs":[{"output_type":"stream","name":"stdout","text":["SPAHR_2017\n"]}],"source":["# Check all folders with more than 0 files\n","dist_num = 0\n","# for district in dist_list:\n","# print(dist_num)\n","for year in years:\n"," # orig_district = district\n"," # if district == 'Leh (Ladakh)':\n"," # district = 'Leh'\n","\n"," # Re-scan the current directory for folders after renaming\n"," current_directory = os.getcwd()\n"," folders = [f for f in os.listdir(current_directory) if os.path.isdir(os.path.join(current_directory, f))]\n"," # pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + district + '_' + year)\n"," pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + year)\n"," district_year_folders = [folder for folder in folders if pattern.match(folder)]\n","\n"," # district = orig_district\n","\n"," for folder in district_year_folders:\n"," if len(os.listdir(folder)) > 0:\n"," print(folder)\n","\n"," # dist_num += 1"]},{"cell_type":"code","execution_count":15,"metadata":{"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775402410054,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"aFKnyEbHRJku"},"outputs":[],"source":["# Change back to parent directory\n","os.chdir(os.path.dirname(os.getcwd()))"]},{"cell_type":"code","execution_count":16,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":15,"status":"ok","timestamp":1775402411295,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"dnodhGcxRbTn","outputId":"718cbfc3-4389-437a-dd2c-cdbf4d80c238"},"outputs":[{"output_type":"stream","name":"stdout","text":["/content/drive\n"]}],"source":["! pwd"]},{"cell_type":"code","execution_count":17,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":409,"status":"ok","timestamp":1775402413224,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"FPdn0lZ5bYDc","outputId":"c2b874d4-4a66-4752-a84d-570a5f6817ed"},"outputs":[{"output_type":"stream","name":"stdout","text":["666\n"]}],"source":["df = pd.read_csv('/content/drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n","print(df.shape[0])"]},{"cell_type":"code","execution_count":18,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775402414562,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"LvDi-4okc42F","outputId":"5b51c09f-7082-40a8-d336-bb9db8948a24"},"outputs":[{"output_type":"stream","name":"stdout","text":[" District \\\n","0 Nicobar Islands \n","1 North and Middle Andaman \n","2 South Andaman \n","3 Anantapur \n","4 Chittoor \n",".. ... \n","661 Pashchim Medinipur \n","662 Purba Medinipur \n","663 Puruliya \n","664 South 24 Parganas \n","665 Uttar Dinajpur \n","\n"," IntersectingZones \\\n","0 [] \n","1 [] \n","2 [] \n","3 [Southern Plateau and Hills Region] \n","4 [Southern Plateau and Hills Region, East Coast... \n",".. ... \n","661 [East Coast Plains & Hills Region, Eastern Pla... \n","662 [East Coast Plains & Hills Region, Lower Gange... \n","663 [Eastern Plateau & Hills Region, Lower Gangeti... \n","664 [Lower Gangetic Plain Region] \n","665 [Middle Gangetic Plain Region, Lower Gangetic ... \n","\n"," IntersectingAreas \\\n","0 [] \n","1 [] \n","2 [] \n","3 [19296696352.156364] \n","4 [11507580448.526262, 3718273353.5141] \n",".. ... \n","661 [27781135.047970004, 88721717.34314527, 930766... \n","662 [10359493.555347942, 3981478759.978645] \n","663 [6266123538.885962, 5557396.356690076] \n","664 [5541282840.37764] \n","665 [73899445.66614598, 3263671615.4575567, 414168... \n","\n"," AgroclimaticZone \n","0 NaN \n","1 NaN \n","2 NaN \n","3 Southern Plateau and Hills Region \n","4 Southern Plateau and Hills Region \n",".. ... \n","661 Lower Gangetic Plain Region \n","662 Lower Gangetic Plain Region \n","663 Eastern Plateau & Hills Region \n","664 Lower Gangetic Plain Region \n","665 Lower Gangetic Plain Region \n","\n","[666 rows x 4 columns]\n"]}],"source":["# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)\n","print(df)"]},{"cell_type":"code","execution_count":19,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":16,"status":"ok","timestamp":1775402418359,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ghNTHMVrc7bV","outputId":"0e6921a1-078b-458b-986b-9878e5c93da8"},"outputs":[{"output_type":"stream","name":"stdout","text":[" District IntersectingZones\n","3 Anantapur [Southern Plateau and Hills Region]\n"]}],"source":["district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n","district_mapping_df= district_mapping_df[district_mapping_df['District'].isin(dist_list)] ## Added extra\n","print(district_mapping_df)"]},{"cell_type":"code","execution_count":20,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":18,"status":"ok","timestamp":1775402420291,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9LAuCvPWdAmC","outputId":"defbc02c-e8a6-4a3b-d209-3e585b601b6c"},"outputs":[{"output_type":"stream","name":"stdout","text":["0 Anantapur ['Southern Plateau and Hills Region']\n"]}],"source":["\n","# print(district_mapping_df['IntersectingZones'][165])\n","# district_mapping_df['IntersectingZones']\n","i = 0\n","for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," print(i, district, zones)\n"," i += 1\n"," # for zone in zones:\n"," # if zone not in agroclimaticZone_acronym_dict:\n"," # print(district, zone)"]},{"cell_type":"code","execution_count":21,"metadata":{"executionInfo":{"elapsed":20,"status":"ok","timestamp":1775402421855,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"bE9YcsyVdMGK"},"outputs":[],"source":["agroclimatic_zone_model_path_mapping_rh98 = {'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Plateau_Hills_Region_correct_toa_rh98_24.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/East_Coast_Plains_Hills_Region_correct_toa_rh98_23.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Himalayan_Region_correct_toa_rh98_30.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Himalayan_Region_correct_toa_rh98_25.joblib',\n"," 'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Central_Plateau_Hills_Region_correct_toa_rh98_23.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Southern_Plateau_and_Hills_Region_correct_toa_rh98_23.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Plateau_and_Hills_Region_correct_toa_rh98_24.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Upper_Gangetic_Plain_Region_correct_toa_rh98_29.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Middle_Gangetic_Plain_Region_correct_toa_rh98_24.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Trans_Gangetic_Plain_Region_correct_toa_rh98_21.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Lower_Gangetic_Plain_Region_correct_toa_rh98_17.joblib'}\n","\n","agroclimatic_zone_model_path_mapping_rh75 = {'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Plateau_Hills_Region_correct_toa_rh75_17.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/East_Coast_Plains_Hills_Region_correct_toa_rh75_16.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Himalayan_Region_correct_toa_rh75_20.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Himalayan_Region_correct_toa_rh75_18.joblib',\n"," 'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Central_Plateau_Hills_Region_correct_toa_rh75_16.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Southern_Plateau_and_Hills_Region_correct_toa_rh75_16.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Plateau_and_Hills_Region_correct_toa_rh75_17.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Upper_Gangetic_Plain_Region_correct_toa_rh75_22.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Middle_Gangetic_Plain_Region_correct_toa_rh75_17.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Trans_Gangetic_Plain_Region_correct_toa_rh75_15.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Lower_Gangetic_Plain_Region_correct_toa_rh75_12.joblib'}\n","\n","agroclimatic_zone_model_path_mapping_rh50 = {'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Plateau_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/East_Coast_Plains_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Himalayan_Region_correct_toa_rh50_14.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Himalayan_Region_correct_toa_rh50_13.joblib',\n"," 'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Central_Plateau_Hills_Region_correct_toa_rh50_11.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Southern_Plateau_and_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Plateau_and_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Upper_Gangetic_Plain_Region_correct_toa_rh50_17.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Middle_Gangetic_Plain_Region_correct_toa_rh50_12.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Trans_Gangetic_Plain_Region_correct_toa_rh50_11.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Lower_Gangetic_Plain_Region_correct_toa_rh50_9.joblib'}"]},{"cell_type":"code","execution_count":22,"metadata":{"collapsed":true,"executionInfo":{"elapsed":4349,"status":"ok","timestamp":1775402428454,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"NFPKdyt3dQao","colab":{"base_uri":"https://localhost:8080/"},"outputId":"2e35db01-1b8c-4c2a-a44d-c4b6abaa79ce"},"outputs":[{"output_type":"stream","name":"stderr","text":["/usr/lib/python3.12/pickle.py:1760: UserWarning: [15:20:26] WARNING: /__w/xgboost/xgboost/src/collective/../data/../common/error_msg.h:83: If you are loading a serialized model (like pickle in Python, RDS in R) or\n","configuration generated by an older version of XGBoost, please export the model by calling\n","`Booster.save_model` from that version first, then load it back in current version. See:\n","\n"," https://xgboost.readthedocs.io/en/stable/tutorials/saving_model.html\n","\n","for more details about differences between saving model and serializing.\n","\n"," setstate(state)\n"]}],"source":["MODEL_PATH_rh98 = agroclimatic_zone_model_path_mapping_rh98[agroclimatic_zone]\n","model_rh98 = joblib.load(MODEL_PATH_rh98)\n","\n","MODEL_PATH_rh75 = agroclimatic_zone_model_path_mapping_rh75[agroclimatic_zone]\n","model_rh75 = joblib.load(MODEL_PATH_rh75)\n","\n","MODEL_PATH_rh50 = agroclimatic_zone_model_path_mapping_rh50[agroclimatic_zone]\n","model_rh50 = joblib.load(MODEL_PATH_rh50)"]},{"cell_type":"code","execution_count":23,"metadata":{"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775402428461,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"43wGQ6lvfkRR"},"outputs":[],"source":["if hasattr(model_rh98, 'feature_names_in_'):\n"," features_rh98 = model_rh98.feature_names_in_\n","\n","if hasattr(model_rh75, 'feature_names_in_'):\n"," features_rh75 = model_rh75.feature_names_in_\n","\n","if hasattr(model_rh50, 'feature_names_in_'):\n"," features_rh50 = model_rh50.feature_names_in_\n"]},{"cell_type":"code","execution_count":24,"metadata":{"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775402429003,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"YzC0_-8hfnB4"},"outputs":[],"source":["seasons = ['kharif', 'rabi', 'zaid']"]},{"cell_type":"code","execution_count":25,"metadata":{"executionInfo":{"elapsed":3,"status":"ok","timestamp":1775402430860,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"C2X1usS0fqgK"},"outputs":[],"source":["def add_s1_indices(df):\n"," for season in seasons:\n"," # SAR Indices\n"," df[f'VV_VH_Ratio_{season}'] = df[f'VV_{season}'] / df[f'VH_{season}']\n"," df[f'VH_VV_Ratio_{season}'] = df[f'VH_{season}'] / df[f'VV_{season}']\n"," df[f'SAR_NDVI_{season}'] = (df[f'VH_{season}'] - df[f'VV_{season}']) / (df[f'VH_{season}'] + df[f'VV_{season}'])\n"," df[f'SAR_DVI_{season}'] = df[f'VH_{season}'] - df[f'VV_{season}']\n"," df[f'SAR_SVI_{season}'] = df[f'VH_{season}'] + df[f'VV_{season}']\n"," df[f'SAR_RDVI_{season}'] = (df[f'VH_{season}'] / df[f'VV_{season}']) - (df[f'VV_{season}'] / df[f'VH_{season}'])\n"," df[f'SAR_NRDVI_{season}'] = ((df[f'VH_{season}']/df[f'VV_{season}'] - df[f'VV_{season}']/df[f'VH_{season}']) / (df[f'VH_{season}']/df[f'VV_{season}'] + df[f'VV_{season}']/df[f'VH_{season}']))\n"," df[f'SAR_SSDVI_{season}'] = df[f'VH_{season}']**2 - df[f'VV_{season}']**2\n","\n","def add_s2_indices(df):\n"," for season in seasons:\n"," # Optical Indices\n"," df[f'NDVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'])\n"," df[f'NDWI_{season}'] = (df[f'B8_{season}'] - df[f'B12_{season}']) / (df[f'B8_{season}'] + df[f'B12_{season}'])\n"," df[f'EVI_{season}'] = (2.5 * ((df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + 6*df[f'B4_{season}'] - 7.5*df[f'B2_{season}'] + 1)))\n"," df[f'OSAVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'] + 0.16)\n"," df[f'ARVI_{season}'] = (df[f'B8_{season}'] - 2*df[f'B4_{season}'] + df[f'B2_{season}']) / (df[f'B8_{season}'] + 2*df[f'B4_{season}'] + df[f'B2_{season}'])\n"," df[f'VARI_{season}'] = (df[f'B3_{season}'] - df[f'B4_{season}']) / (df[f'B3_{season}'] + df[f'B4_{season}'] - df[f'B2_{season}'])\n"]},{"cell_type":"code","execution_count":26,"metadata":{"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775402433241,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"iI0adElOftju"},"outputs":[],"source":["# def get_csv_data(fileName):\n","# data = pd.DataFrame()\n","# try:\n","# data = pd.read_csv(fileName)\n","# except Exception as exp:\n","# print(\"Error reading file \", fileName, \" - \", exp)\n","# return data\n","\n","def get_csv_chunks(fileName, chunksize=100_000):\n"," \"\"\"Yield DataFrame chunks from a CSV file.\"\"\"\n"," try:\n"," for chunk in pd.read_csv(fileName, chunksize=chunksize):\n"," yield chunk\n"," except Exception as exp:\n"," print(\"Error reading file \", fileName, \" - \", exp)\n"," return []\n"]},{"cell_type":"code","execution_count":27,"metadata":{"executionInfo":{"elapsed":2,"status":"ok","timestamp":1775402436472,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"BSIdWoUzfw7S"},"outputs":[],"source":["# For Canopy Height\n","def pipeline(fileName, chunksize=100000):\n"," res_chunks = [] # collect processed results\n","\n"," for df in get_csv_chunks(fileName, chunksize):\n"," if len(df) == 0:\n"," continue\n","\n"," # Add indices\n"," add_s1_indices(df)\n"," add_s2_indices(df)\n","\n"," # Geo column\n"," res_df = pd.DataFrame()\n"," res_df['.geo'] = df['.geo']\n","\n"," # Predictions\n"," pred_y_98 = model_rh98.predict(df[features_rh98])\n"," pred_y_75 = model_rh75.predict(df[features_rh75])\n"," pred_y_50 = model_rh50.predict(df[features_rh50])\n","\n"," res_df['rh98_class'] = pred_y_98\n"," res_df['rh75_class'] = pred_y_75\n"," res_df['rh50_class'] = pred_y_50\n","\n"," res_chunks.append(res_df)\n","\n"," if res_chunks:\n"," return pd.concat(res_chunks, ignore_index=True)\n"," else:\n"," return pd.DataFrame(columns=['.geo', 'rh98_class', 'rh75_class', 'rh50_class'])\n","\n","# def pipeline(fileName):\n","# # print(fileName)\n","\n","# df = get_csv_data(fileName)\n","\n","# if (len(df) == 0):\n","# return df\n","\n","# add_s1_indices(df)\n","# add_s2_indices(df)\n","\n","# geoList = list(df['.geo'])\n","# res_df = pd.DataFrame()\n","# res_df['.geo'] = geoList\n","\n","# test_df = df[features_rh98]\n","# pred_y_98 = list(model_rh98.predict(test_df))\n","# test_df = df[features_rh75]\n","# pred_y_75 = list(model_rh75.predict(test_df))\n","# test_df = df[features_rh50]\n","# pred_y_50 = list(model_rh50.predict(test_df))\n","\n","# res_df['rh98_class'] = pred_y_98\n","# res_df['rh75_class'] = pred_y_75\n","# res_df['rh50_class'] = pred_y_50\n","\n","# return res_df"]},{"cell_type":"code","execution_count":30,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"R7GDTVjcf1Vw","outputId":"c0389988-dd39-4d7e-b22a-43b92fc67fae","executionInfo":{"status":"ok","timestamp":1775402686426,"user_tz":-330,"elapsed":53554,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["Anantapur\n","['Southern Plateau and Hills Region']\n","\n","0 District: Anantapur, Zone: Southern Plateau and Hills Region, Year: 2017\n","no. of files: 1 \n","\n"]}],"source":["for year in years:\n"," dist_num = 0\n","\n"," for ind in district_mapping_df.index:\n"," # if dist_num < 14:\n"," # dist_num += 1\n"," # continue\n"," district = district_mapping_df.loc[ind, 'District']\n"," print(district)\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," print(zones)\n"," merged_df = pd.DataFrame()\n"," for zone in zones:\n"," print(f'\\n{dist_num} District: {district}, Zone: {zone}, Year: {year}')\n"," # dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[zone]}_{district}_{year}/'\n"," dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[zone]}_{year}'\n"," files = glob(f\"{dist_data_path}/{district}_{year}_all_grids.csv\")\n"," print(\"no. of files:\", len(files), '\\n')\n"," for fileName in files:\n"," df = pipeline(fileName,chunksize=100000)\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n","\n"," merged_df.to_csv(f'/content/drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm.csv', index=False)\n"," dist_num += 1"]},{"cell_type":"markdown","metadata":{"id":"vkgtoxewcscO"},"source":["# CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"651ANiZ4ctgL"},"outputs":[],"source":["df = pd.read_csv(f'/content/drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = list(df['Name'])\n","# dist_list = ['Yamunanagar'] ## Added extra"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":55,"status":"ok","timestamp":1768374034034,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"UvH9HrjK4w1E","outputId":"576ff54b-1b7a-4122-e215-f128724e1e1b"},"outputs":[{"name":"stdout","output_type":"stream","text":["86\n","['Araria', 'Arwal', 'AurangabadB', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar', 'Darbhanga', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur', 'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger', 'Muzaffarpur', 'Nalanda', 'Nawada', 'Pashchim Champaran', 'Patna', 'Purba Champaran', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur', 'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali', 'BalrampurC', 'Chatra', 'Deoghar', 'Dumka', 'Garhwa', 'Giridih', 'Godda', 'Hazaribagh', 'Kodarma', 'Pakur', 'Palamu', 'Sahibganj', 'Rewa', 'Singrauli', 'Allahabad', 'Ambedkar Nagar', 'Amethi', 'Azamgarh', 'Bahraich', 'Ballia', 'Balrampur', 'Barabanki', 'Basti', 'Chandauli', 'Deoria', 'Faizabad', 'Ghazipur', 'Gonda', 'Gorakhpur', 'Jaunpur', 'Kushinagar', 'Lakhimpur Kheri', 'Maharajganj', 'Mau', 'Mirzapur', 'Pratapgarhup', 'Sant Kabir Nagar', 'Sant Ravi Das Nagar', 'Shravasti', 'Siddharth Nagar', 'Sitapur', 'Sonbhadra', 'Sultanpur', 'Varanasi', 'Darjiling', 'Maldah', 'Murshidabad', 'Uttar Dinajpur']\n"]}],"source":["print(len(dist_list))\n","print(dist_list)"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"bMLuCQdz482a"},"outputs":[],"source":["agroclimatic_zone_model_path_mapping = {'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Central_Plateau_Hills_Region_toa_monthly_cover_51.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Lower_Gangetic_Plain_Region_toa_monthly_cover_48.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Middle_Gangetic_Plain_Region_toa_monthly_cover_50.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Eastern_Himalayan_Region_toa_monthly_cover_86.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Western_Himalayan_Region_toa_monthly_cover_78.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Upper_Gangetic_Plain_Region_toa_monthly_cover_67.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Trans_Gangetic_Plain_Region_toa_monthly_cover_55.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/East_Coast_Plains_Hills_Region_toa_monthly_cover_67.joblib',\n"," 'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Eastern_Plateau_Hills_Region_toa_monthly_cover_60.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Western_Plateau_and_Hills_Region_toa_monthly_cover_57.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Southern_Plateau_and_Hills_Region_toa_monthly_cover_62.joblib'}\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":2483,"status":"ok","timestamp":1768375137014,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"df76932e5Hzw","outputId":"a802cb3b-506c-4404-ce7c-08fc46161e67"},"outputs":[{"name":"stderr","output_type":"stream","text":["/usr/lib/python3.12/pickle.py:1760: UserWarning: [07:18:56] WARNING: /workspace/src/collective/../data/../common/error_msg.h:83: If you are loading a serialized model (like pickle in Python, RDS in R) or\n","configuration generated by an older version of XGBoost, please export the model by calling\n","`Booster.save_model` from that version first, then load it back in current version. See:\n","\n"," https://xgboost.readthedocs.io/en/stable/tutorials/saving_model.html\n","\n","for more details about differences between saving model and serializing.\n","\n"," setstate(state)\n"]}],"source":["MODEL_PATH_cc = agroclimatic_zone_model_path_mapping[agroclimatic_zone]\n","model_cc = joblib.load(MODEL_PATH_cc)"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"tlVIdXqx5Lcr"},"outputs":[],"source":["if hasattr(model_cc, 'feature_names_in_'):\n"," features_cc = model_cc.feature_names_in_"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"r1sgggp16NBx"},"outputs":[],"source":["seasons = ['kharif', 'rabi', 'zaid']\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"_gp52vxf6P5X"},"outputs":[],"source":["def add_s1_indices(df):\n"," for season in seasons:\n"," # SAR Indices\n"," df[f'VV_VH_Ratio_{season}'] = df[f'VV_{season}'] / df[f'VH_{season}']\n"," df[f'VH_VV_Ratio_{season}'] = df[f'VH_{season}'] / df[f'VV_{season}']\n"," df[f'SAR_NDVI_{season}'] = (df[f'VH_{season}'] - df[f'VV_{season}']) / (df[f'VH_{season}'] + df[f'VV_{season}'])\n"," df[f'SAR_DVI_{season}'] = df[f'VH_{season}'] - df[f'VV_{season}']\n"," df[f'SAR_SVI_{season}'] = df[f'VH_{season}'] + df[f'VV_{season}']\n"," df[f'SAR_RDVI_{season}'] = (df[f'VH_{season}'] / df[f'VV_{season}']) - (df[f'VV_{season}'] / df[f'VH_{season}'])\n"," df[f'SAR_NRDVI_{season}'] = ((df[f'VH_{season}']/df[f'VV_{season}'] - df[f'VV_{season}']/df[f'VH_{season}']) / (df[f'VH_{season}']/df[f'VV_{season}'] + df[f'VV_{season}']/df[f'VH_{season}']))\n"," df[f'SAR_SSDVI_{season}'] = df[f'VH_{season}']**2 - df[f'VV_{season}']**2\n","\n","def add_s2_indices(df):\n"," for season in seasons:\n"," # Optical Indices\n"," df[f'NDVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'])\n"," df[f'NDWI_{season}'] = (df[f'B8_{season}'] - df[f'B12_{season}']) / (df[f'B8_{season}'] + df[f'B12_{season}'])\n"," df[f'EVI_{season}'] = (2.5 * ((df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + 6*df[f'B4_{season}'] - 7.5*df[f'B2_{season}'] + 1)))\n"," df[f'OSAVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'] + 0.16)\n"," df[f'ARVI_{season}'] = (df[f'B8_{season}'] - 2*df[f'B4_{season}'] + df[f'B2_{season}']) / (df[f'B8_{season}'] + 2*df[f'B4_{season}'] + df[f'B2_{season}'])\n"," df[f'VARI_{season}'] = (df[f'B3_{season}'] - df[f'B4_{season}']) / (df[f'B3_{season}'] + df[f'B4_{season}'] - df[f'B2_{season}'])\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"Vg9Yf9nC6Sug"},"outputs":[],"source":["# def get_csv_data(fileName):\n","# data = pd.DataFrame()\n","# try:\n","# data = pd.read_csv(fileName)\n","# except Exception as exp:\n","# print(\"Error reading file \", fileName, \" - \", exp)\n","# return data\n","\n","def get_csv_chunks(fileName, chunksize=100_000):\n"," \"\"\"Yield DataFrame chunks from a CSV file.\"\"\"\n"," try:\n"," for chunk in pd.read_csv(fileName, chunksize=chunksize):\n"," yield chunk\n"," except Exception as exp:\n"," print(\"Error reading file \", fileName, \" - \", exp)\n"," return []"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"FYuZQqc36WKZ"},"outputs":[],"source":["# For Canopy Cover\n","def pipeline(fileName, chunksize=100000):\n"," res_chunks = [] # collect processed results\n","\n"," for df in get_csv_chunks(fileName, chunksize):\n"," if len(df) == 0:\n"," continue\n","\n"," # Add indices\n"," add_s1_indices(df)\n"," add_s2_indices(df)\n","\n"," # Geo column\n"," res_df = pd.DataFrame()\n"," res_df['.geo'] = list(df['.geo'])\n","\n"," for month in range(1,13):\n"," df['month_sin'] = [np.sin(2 * np.pi * month / 12)] * len(df)\n"," df['month_cos'] = [np.cos(2 * np.pi * month / 12)] * len(df)\n","\n"," test_df = df[features_cc]\n"," pred_y_cc = list(model_cc.predict(test_df))\n"," res_df[f'cc_{month}'] = pred_y_cc\n","\n"," res_chunks.append(res_df)\n","\n"," if res_chunks:\n"," return pd.concat(res_chunks, ignore_index=True)\n"," else:\n"," return pd.DataFrame(columns=['.geo'])\n","\n","# def pipeline(fileName):\n","\n","# print(fileName)\n","\n","# df = get_csv_data(fileName)\n","\n","# if (len(df) == 0):\n","# return df\n","\n","# add_s1_indices(df)\n","# add_s2_indices(df)\n","\n","# geoList = list(df['.geo'])\n","# res_df = pd.DataFrame()\n","# res_df['.geo'] = geoList\n","\n","# for month in range(1,13):\n","# df['month_sin'] = [np.sin(2 * np.pi * month / 12)] * len(df)\n","# df['month_cos'] = [np.cos(2 * np.pi * month / 12)] * len(df)\n","\n","# test_df = df[features_cc]\n","# pred_y_cc = list(model_cc.predict(test_df))\n","# res_df[f'cc_{month}'] = pred_y_cc\n","\n","# return res_df\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":22162462,"status":"ok","timestamp":1768405622097,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9bXlL4RT6Yzo","outputId":"e89c99f5-0fb8-4a44-9c4b-651d13b8d8ea"},"outputs":[{"name":"stdout","output_type":"stream","text":["\n"," 0 Araria 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1976372\n","\n"," 1 Arwal 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 192050\n","\n"," 2 AurangabadB 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 931520\n","\n"," 3 Banka 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1794108\n","\n"," 4 Begusarai 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 925926\n","\n"," 5 Bhagalpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1017500\n","\n"," 6 Bhojpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 528965\n","\n"," 7 Buxar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 319698\n","\n"," 8 Darbhanga 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1482059\n","\n"," 9 Gaya 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2347878\n","\n"," 10 Gopalganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 960264\n","\n"," 11 Jamui 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1946203\n","\n"," 12 Jehanabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 328318\n","\n"," 13 Kaimur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2050428\n","\n"," 14 Katihar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1742730\n","\n"," 15 Khagaria 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 763589\n","\n"," 16 Kishanganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1080963\n","\n"," 17 Lakhisarai 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 574227\n","\n"," 18 Madhepura 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1485046\n","\n"," 19 Madhubani 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2275458\n","\n"," 20 Munger 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 788345\n","\n"," 21 Muzaffarpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2058580\n","\n"," 22 Nalanda 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 828451\n","\n"," 23 Nawada 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1456076\n","\n"," 24 Pashchim Champaran 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 5057819\n","\n"," 25 Patna 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 988946\n","\n"," 26 Purba Champaran 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2114183\n","\n"," 27 Purnia 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2585720\n","\n"," 28 Rohtas 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1802430\n","\n"," 29 Saharsa 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1403136\n","\n"," 30 Samastipur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1863560\n","\n"," 31 Saran 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1191174\n","\n"," 32 Sheikhpura 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 164235\n","\n"," 33 Sheohar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 382768\n","\n"," 34 Sitamarhi 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1455242\n","\n"," 35 Siwan 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 942499\n","\n"," 36 Supaul 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1771433\n","\n"," 37 Vaishali 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1229291\n","\n"," 38 BalrampurC 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 28983\n","\n"," 39 Chatra 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 25378\n","\n"," 40 Deoghar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 11084\n","\n"," 41 Dumka 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 7096\n","\n"," 42 Garhwa 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 36756\n","\n"," 43 Giridih 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 7006\n","\n"," 44 Godda 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1900116\n","\n"," 45 Hazaribagh 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 19337\n","\n"," 46 Kodarma 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 41050\n","\n"," 47 Pakur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 30727\n","\n"," 48 Palamu 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 11864\n","\n"," 49 Sahibganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2025865\n","\n"," 50 Rewa 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 18058\n","\n"," 51 Singrauli 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 12480\n","\n"," 52 Allahabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 4270\n","\n"," 53 Ambedkar Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 923541\n","\n"," 54 Amethi 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 13299\n","\n"," 55 Azamgarh 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1182006\n","\n"," 56 Bahraich 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2344420\n","\n"," 57 Ballia 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 670018\n","\n"," 58 Balrampur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2339692\n","\n"," 59 Barabanki 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 423163\n","\n"," 60 Basti 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1061160\n","\n"," 61 Chandauli 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1213119\n","\n"," 62 Deoria 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 682125\n","\n"," 63 Faizabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1241511\n","\n"," 64 Ghazipur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 796377\n","\n"," 65 Gonda 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2413423\n","\n"," 66 Gorakhpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 772243\n","\n"," 67 Jaunpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1447752\n","\n"," 68 Kushinagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2092368\n","\n"," 69 Lakhimpur Kheri 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 411\n","\n"," 70 Maharajganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1562109\n","\n"," 71 Mau 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 601321\n","\n"," 72 Mirzapur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1617620\n","\n"," 73 Pratapgarhup 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 596\n","\n"," 74 Sant Kabir Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 365259\n","\n"," 75 Sant Ravi Das Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 312896\n","\n"," 76 Shravasti 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1762200\n","\n"," 77 Siddharth Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 843547\n","\n"," 78 Sitapur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 42\n","\n"," 79 Sonbhadra 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 5833979\n","\n"," 80 Sultanpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 16803\n","\n"," 81 Varanasi 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 552786\n","\n"," 82 Darjiling 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 16059\n","\n"," 83 Maldah 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 19259\n","\n"," 84 Murshidabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 4328\n","\n"," 85 Uttar Dinajpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 47370\n","\n"," 0 Araria 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2190491\n","\n"," 1 Arwal 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 161470\n","\n"," 2 AurangabadB 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 835319\n","\n"," 3 Banka 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1584408\n","\n"," 4 Begusarai 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 824518\n","\n"," 5 Bhagalpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 905853\n","\n"," 6 Bhojpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 488248\n","\n"," 7 Buxar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 305113\n","\n"," 8 Darbhanga 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1331421\n","\n"," 9 Gaya 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2161066\n","\n"," 10 Gopalganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 861413\n","\n"," 11 Jamui 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1876656\n","\n"," 12 Jehanabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 299666\n","\n"," 13 Kaimur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1941287\n","\n"," 14 Katihar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1475151\n","\n"," 15 Khagaria 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 588876\n","\n"," 16 Kishanganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1179706\n","\n"," 17 Lakhisarai 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 539734\n","\n"," 18 Madhepura 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1432748\n","\n"," 19 Madhubani 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2590746\n","\n"," 20 Munger 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 757651\n","\n"," 21 Muzaffarpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1891473\n","\n"," 22 Nalanda 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 758015\n","\n"," 23 Nawada 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1309715\n","\n"," 24 Pashchim Champaran 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 4314974\n","\n"," 25 Patna 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 908794\n","\n"," 26 Purba Champaran 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1949197\n","\n"," 27 Purnia 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2201352\n","\n"," 28 Rohtas 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1735872\n","\n"," 29 Saharsa 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1163729\n","\n"," 30 Samastipur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1492263\n","\n"," 31 Saran 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 952250\n","\n"," 32 Sheikhpura 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 127904\n","\n"," 33 Sheohar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 373246\n","\n"," 34 Sitamarhi 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1418026\n","\n"," 35 Siwan 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 735087\n","\n"," 36 Supaul 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2279975\n","\n"," 37 Vaishali 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1067125\n","\n"," 38 BalrampurC 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 29894\n","\n"," 39 Chatra 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 23811\n","\n"," 40 Deoghar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 10025\n","\n"," 41 Dumka 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 5602\n","\n"," 42 Garhwa 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 35540\n","\n"," 43 Giridih 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 7097\n","\n"," 44 Godda 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1689963\n","\n"," 45 Hazaribagh 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 18602\n","\n"," 46 Kodarma 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 40475\n","\n"," 47 Pakur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 30062\n","\n"," 48 Palamu 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 10438\n","\n"," 49 Sahibganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1858377\n","\n"," 50 Rewa 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 18511\n","\n"," 51 Singrauli 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 12442\n","\n"," 52 Allahabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 4125\n","\n"," 53 Ambedkar Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 878644\n","\n"," 54 Amethi 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 12882\n","\n"," 55 Azamgarh 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1144098\n","\n"," 56 Bahraich 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2281433\n","\n"," 57 Ballia 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 626531\n","\n"," 58 Balrampur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2413530\n","\n"," 59 Barabanki 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 397978\n","\n"," 60 Basti 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1032498\n","\n"," 61 Chandauli 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1129638\n","\n"," 62 Deoria 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 669973\n","\n"," 63 Faizabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1181990\n","\n"," 64 Ghazipur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 731143\n","\n"," 65 Gonda 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2484101\n","\n"," 66 Gorakhpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 745270\n","\n"," 67 Jaunpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1444477\n","\n"," 68 Kushinagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1983578\n","\n"," 69 Lakhimpur Kheri 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 405\n","\n"," 70 Maharajganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1535072\n","\n"," 71 Mau 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 547318\n","\n"," 72 Mirzapur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1555020\n","\n"," 73 Pratapgarhup 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 569\n","\n"," 74 Sant Kabir Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 347484\n","\n"," 75 Sant Ravi Das Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 308932\n","\n"," 76 Shravasti 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1696175\n","\n"," 77 Siddharth Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 815397\n","\n"," 78 Sitapur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 49\n","\n"," 79 Sonbhadra 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 5575006\n","\n"," 80 Sultanpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 17301\n","\n"," 81 Varanasi 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 530860\n","\n"," 82 Darjiling 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 15889\n","\n"," 83 Maldah 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 15725\n","\n"," 84 Murshidabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 3520\n","\n"," 85 Uttar Dinajpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 49835\n","\n"," 0 Araria 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1969950\n","\n"," 1 Arwal 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 172959\n","\n"," 2 AurangabadB 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 919333\n","\n"," 3 Banka 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1719181\n","\n"," 4 Begusarai 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 826079\n","\n"," 5 Bhagalpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 947866\n","\n"," 6 Bhojpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 493242\n","\n"," 7 Buxar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 315131\n","\n"," 8 Darbhanga 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1446552\n","\n"," 9 Gaya 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2250105\n","\n"," 10 Gopalganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 884977\n","\n"," 11 Jamui 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2049016\n","\n"," 12 Jehanabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 295487\n","\n"," 13 Kaimur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1891094\n","\n"," 14 Katihar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1527107\n","\n"," 15 Khagaria 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 552961\n","\n"," 16 Kishanganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1069558\n","\n"," 17 Lakhisarai 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 574275\n","\n"," 18 Madhepura 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1326965\n","\n"," 19 Madhubani 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2572389\n","\n"," 20 Munger 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 793397\n","\n"," 21 Muzaffarpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1974039\n","\n"," 22 Nalanda 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 760888\n","\n"," 23 Nawada 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1415526\n","\n"," 24 Pashchim Champaran 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 4607789\n","\n"," 25 Patna 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 878887\n","\n"," 26 Purba Champaran 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1975397\n","\n"," 27 Purnia 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2036293\n","\n"," 28 Rohtas 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1740932\n","\n"," 29 Saharsa 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1172223\n","\n"," 30 Samastipur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1579828\n","\n"," 31 Saran 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 967225\n","\n"," 32 Sheikhpura 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 139694\n","\n"," 33 Sheohar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 350852\n","\n"," 34 Sitamarhi 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1253511\n","\n"," 35 Siwan 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 748663\n","\n"," 36 Supaul 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1911496\n","\n"," 37 Vaishali 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1108101\n","\n"," 38 BalrampurC 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 29650\n","\n"," 39 Chatra 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 25041\n","\n"," 40 Deoghar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 10766\n","\n"," 41 Dumka 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 6322\n","\n"," 42 Garhwa 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 38526\n","\n"," 43 Giridih 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 7574\n","\n"," 44 Godda 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1777243\n","\n"," 45 Hazaribagh 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 18805\n","\n"," 46 Kodarma 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 40709\n","\n"," 47 Pakur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 29874\n","\n"," 48 Palamu 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 12084\n","\n"," 49 Sahibganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1923750\n","\n"," 50 Rewa 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 18630\n","\n"," 51 Singrauli 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 10951\n","\n"," 52 Allahabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 3656\n","\n"," 53 Ambedkar Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 820179\n","\n"," 54 Amethi 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 12372\n","\n"," 55 Azamgarh 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1082775\n","\n"," 56 Bahraich 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2313869\n","\n"," 57 Ballia 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 644920\n","\n"," 58 Balrampur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2341159\n","\n"," 59 Barabanki 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 379511\n","\n"," 60 Basti 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1041620\n","\n"," 61 Chandauli 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1108648\n","\n"," 62 Deoria 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 686270\n","\n"," 63 Faizabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1132587\n","\n"," 64 Ghazipur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 708669\n","\n"," 65 Gonda 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2413059\n","\n"," 66 Gorakhpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 713471\n","\n"," 67 Jaunpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1373721\n","\n"," 68 Kushinagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1938508\n","\n"," 69 Lakhimpur Kheri 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 417\n","\n"," 70 Maharajganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1461027\n","\n"," 71 Mau 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 535649\n","\n"," 72 Mirzapur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1452187\n","\n"," 73 Pratapgarhup 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 560\n","\n"," 74 Sant Kabir Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 333709\n","\n"," 75 Sant Ravi Das Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 300748\n","\n"," 76 Shravasti 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1667482\n","\n"," 77 Siddharth Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 780608\n","\n"," 78 Sitapur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 40\n","\n"," 79 Sonbhadra 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 5561742\n","\n"," 80 Sultanpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 15874\n","\n"," 81 Varanasi 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 464081\n","\n"," 82 Darjiling 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 15156\n","\n"," 83 Maldah 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 18893\n","\n"," 84 Murshidabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 3990\n","\n"," 85 Uttar Dinajpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 43326\n"]}],"source":["for year in years:\n"," dist_num = 0\n"," for district in dist_list:\n"," # if dist_num < 53:\n"," # dist_num += 1\n"," # continue\n"," print('\\n', dist_num, district, year)\n"," # dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{district}_{year}/'\n"," dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{year}'\n"," print(dist_data_path)\n"," # files = glob(dist_data_path + \"*.csv\")\n"," files = glob(f\"{dist_data_path}/{district}_{year}_all_grids.csv\")\n"," print(\"no. of files:\", len(files), '\\n')\n"," merged_df = pd.DataFrame()\n"," for fileName in files:\n"," df = pipeline(fileName, chunksize=100000)\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n"," print(\"merged_df\", len(merged_df))\n"," merged_df.to_csv(f'/content/drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc.csv', index=False)\n"," dist_num += 1"]}],"metadata":{"colab":{"collapsed_sections":["vkgtoxewcscO"],"provenance":[{"file_id":"1EZVgV-JFERHxOKGgBNKweDaXnmAGoo83","timestamp":1758129019932}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb b/utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb new file mode 100644 index 00000000..ef71a618 --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":5831,"status":"ok","timestamp":1774795294300,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"IYRi4f4mL668"},"outputs":[],"source":["import pandas as pd\n","import joblib\n","import time\n","from glob import glob\n","import numpy as np\n","import statistics as st\n","import matplotlib.pyplot as plt\n","import ee\n","import ast"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":27519,"status":"ok","timestamp":1774795330023,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qvEjcCWgLuTg","outputId":"4a74a17d-e4cc-4cc5-ebc0-286d81d6035e"},"outputs":[{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":3,"metadata":{"executionInfo":{"elapsed":15,"status":"ok","timestamp":1774795330024,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"fORWmS6aL-gR"},"outputs":[],"source":["acz_list = [\n"," 'Western Himalayan Region',\n"," # 'Eastern Himalayan Region',\n"," # 'Lower Gangetic Plain Region',\n"," # 'Middle Gangetic Plain Region',\n"," # 'Upper Gangetic Plain Region',\n"," # 'Trans Gangetic Plain Region',\n"," # 'Eastern Plateau & Hills Region',\n"," # 'Central Plateau & Hills Region',\n"," # 'Western Plateau and Hills Region',\n"," # 'Southern Plateau and Hills Region',\n"," # 'East Coast Plains & Hills Region'\n"," ]"]},{"cell_type":"code","execution_count":4,"metadata":{"executionInfo":{"elapsed":6,"status":"ok","timestamp":1774795342043,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"6Fiwos-UMDhQ"},"outputs":[],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}"]},{"cell_type":"code","execution_count":18,"metadata":{"executionInfo":{"elapsed":47,"status":"ok","timestamp":1774807519586,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"5T_YvOd-MK1m"},"outputs":[],"source":["# if AY 2023's data is being added, set year = '2023'\n","# No correction for 2016, starts from 2017\n","# (For correcting 2017, set to year to 2019)\n","year = '2024'\n","\n","# We need to correct data from year_2 and year_1\n","year_1 = int(year)-1\n","year_2 = int(year)-2\n","year_3 = int(year)-3\n","year_4 = int(year)-4"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":1284,"status":"ok","timestamp":1774715945110,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"3qECVxwWVRAP","outputId":"20eaa902-dbb3-4630-fc49-85b41ca7d54b"},"outputs":[{"output_type":"stream","name":"stdout","text":["2\n","dist_list: ['Katihar', 'Kishanganj']\n"]}],"source":["# df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{acz_list[0]}.csv')\n","# dist_list = list(df['Name'])\n","# dist_list = ['Katihar', 'Kishanganj']\n","# print(len(dist_list))\n","# print(f'dist_list: {dist_list}')"]},{"cell_type":"markdown","metadata":{"id":"OpBWXM1TMVAs"},"source":["# Data Correction - CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"pNuZ_qZtMWEj"},"outputs":[],"source":["def ccd_corrections_2017(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # In case the no. of columns are too less for any corrections to be performed\n"," if n < 5:\n"," print(f\"Number of columns are {n}, which is too less to perform any corrections..\")\n"," correction_df = pd.DataFrame(columns=columns)\n"," return correction_df\n","\n"," # Correcting the second (2017) year\n"," correction_df = df[(df[columns[n-1]] == df[columns[n-2]]) & (df[columns[n-2]] == df[columns[n-4]]) & (df[columns[n-3]] !=df[columns[n-1]])]\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n"," correction_df.loc[(correction_df[columns[n-1]] == correction_df[columns[n-2]]) &\n"," (correction_df[columns[n-2]] == correction_df[columns[n-4]]) &\n"," (correction_df[columns[n-3]] != correction_df[columns[n-1]]), columns[n-3]] = correction_df[columns[n-1]]\n","\n"," return correction_df\n","\n","\n","def corrections(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # In case the no. of columns are too less for any corrections to be performed\n"," if n < 5:\n"," correction_df = pd.DataFrame(columns=columns)\n"," return correction_df\n","\n"," # Correcting the second last year\n"," correction_df = df[(df[columns[n-1]] == df[columns[n-3]]) & (df[columns[n-3]] == df[columns[n-4]]) & (df[columns[n-2]] != df[columns[n-1]])]\n"," correction_df.drop_duplicates(inplace=True)\n"," print(\"correction_df 1\", correction_df)\n"," # Correcting the middle year\n"," new_df = df[(df[columns[n-5]] == df[columns[n-4]]) & (df[columns[n-4]] == df[columns[n-2]]) & (df[columns[n-2]] == df[columns[n-1]]) & (df[columns[n-3]] != df[columns[n-5]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n"," print(\"correction_df 2\", correction_df)\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n"," correction_df.loc[(correction_df[columns[n-1]] == correction_df[columns[n-3]]) & (correction_df[columns[n-3]] == correction_df[columns[n-4]]) &\n"," (correction_df[columns[n-2]] != correction_df[columns[n-1]]), columns[n-2]] = correction_df[columns[n-1]]\n"," correction_df.loc[(correction_df[columns[n-5]] == correction_df[columns[n-4]]) & (correction_df[columns[n-4]] == correction_df[columns[n-2]]) &\n"," (correction_df[columns[n-2]] == correction_df[columns[n-1]]) & (correction_df[columns[n-3]] != correction_df[columns[n-5]]), columns[n-3]] = correction_df[columns[n-5]]\n","\n"," return correction_df"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":517},"executionInfo":{"elapsed":20422,"status":"error","timestamp":1771511098161,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"kZsJ0sCEMcQo","outputId":"e3b8da73-1c52-49c8-ea41-db204f58acc8"},"outputs":[{"name":"stdout","output_type":"stream","text":["East Coast Plains & Hills Region\n","len(dist_list): 68\n","0 Chittoor\n"]},{"ename":"KeyboardInterrupt","evalue":"","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-50941476.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 36\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 37\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 38\u001b[0;31m \u001b[0mdf_3\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 39\u001b[0m \u001b[0mdf_3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdrop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcolumnList\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minplace\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0mdf_3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrename\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0mband\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'cover_class'\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minplace\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36mread_csv\u001b[0;34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)\u001b[0m\n\u001b[1;32m 1024\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwds_defaults\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1025\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1026\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_read\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1027\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1028\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36m_read\u001b[0;34m(filepath_or_buffer, kwds)\u001b[0m\n\u001b[1;32m 624\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 625\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mparser\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 626\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mparser\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnrows\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 627\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 628\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self, nrows)\u001b[0m\n\u001b[1;32m 1921\u001b[0m \u001b[0mcolumns\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1922\u001b[0m \u001b[0mcol_dict\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1923\u001b[0;31m \u001b[0;34m)\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m \u001b[0;31m# type: ignore[attr-defined]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1924\u001b[0m \u001b[0mnrows\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1925\u001b[0m )\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/c_parser_wrapper.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self, nrows)\u001b[0m\n\u001b[1;32m 232\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 233\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlow_memory\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 234\u001b[0;31m \u001b[0mchunks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_reader\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_low_memory\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnrows\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 235\u001b[0m \u001b[0;31m# destructive to chunks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 236\u001b[0m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_concatenate_chunks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchunks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader.read_low_memory\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._read_rows\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._tokenize_rows\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._check_tokenize_status\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.raise_parser_error\u001b[0;34m()\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/codecs.py\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(self, input, final)\u001b[0m\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "]}],"source":["for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n"," dist_list = list(df['Name'])\n"," print(f'len(dist_list): {len(dist_list)}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/'\n","\n"," total_corrections = 0\n"," total_length = 0\n","\n"," for i in range(len(dist_list)):\n","\n"," # if i != 11:\n"," # continue\n","\n"," print(i, dist_list[i])\n"," file_4 = path + dist_list[i] + f\"/{year_4}/result_monthly_cc.csv\"\n"," file_3 = path + dist_list[i] + f\"/{year_3}/result_monthly_cc.csv\"\n"," file_2 = path + dist_list[i] + f\"/{year_2}/result_monthly_cc.csv\"\n"," file_1 = path + dist_list[i] + f\"/{year_1}/result_monthly_cc.csv\"\n"," file_0 = path + dist_list[i] + f\"/{year}/result_monthly_cc.csv\"\n","\n"," band = best_month_dict[agroclimatic_zone]\n"," columnList = ['cc_1', 'cc_2', 'cc_3', 'cc_4', 'cc_5', 'cc_6', 'cc_7', 'cc_8', 'cc_9', 'cc_10', 'cc_11', 'cc_12']\n"," columnList.remove(band)\n","\n"," try:\n"," df_4 = pd.read_csv(file_4)\n"," df_4.drop(columns=columnList, inplace=True)\n"," df_4.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_4 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," try:\n"," df_3 = pd.read_csv(file_3)\n"," df_3.drop(columns=columnList, inplace=True)\n"," df_3.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_3 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_4.rename(columns={'cover_class': f'cc_{year_4}'}, inplace=True)\n"," df_3.rename(columns={'cover_class': f'cc_{year_3}'}, inplace=True)\n"," merged_df = pd.merge(df_4, df_3, on='.geo', how='outer')\n"," del(df_4)\n"," del(df_3)\n","\n"," try:\n"," df_2 = pd.read_csv(file_2)\n"," df_2.drop(columns=columnList, inplace=True)\n"," df_2.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_2 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_2.rename(columns={'cover_class': f'cc_{year_2}'}, inplace=True)\n"," merged_df = pd.merge(merged_df, df_2, on='.geo', how='outer')\n"," del(df_2)\n","\n"," try:\n"," df_1 = pd.read_csv(file_1)\n"," df_1.drop(columns=columnList, inplace=True)\n"," df_1.rename(columns={band: 'cover_class'}, inplace=True)\n"," except Exception as e:\n"," print(e)\n"," df_1 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_1.rename(columns={'cover_class': f'cc_{year_1}'}, inplace=True)\n"," merged_df = pd.merge(merged_df, df_1, on='.geo', how='outer')\n"," del(df_1)\n","\n"," try:\n"," df_0 = pd.read_csv(file_0)\n"," df_0.drop(columns=columnList, inplace=True)\n"," df_0.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_0 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_0.rename(columns={'cover_class': f'cc_{year}'}, inplace=True)\n"," merged_df = pd.merge(merged_df, df_0, on='.geo', how='outer')\n"," del(df_0)\n","\n","\n"," merged_df = merged_df[['.geo', f'cc_{year_4}', f'cc_{year_3}', f'cc_{year_2}', f'cc_{year_1}', f'cc_{year}']]\n"," print(merged_df)\n"," total_length += len(merged_df)\n"," print(\"Length of merged_df:\", len(merged_df))\n"," print(\"Total Length:\", total_length)\n"," if year == '2019':\n"," print(\"Correcting 2017\")\n"," correction_df = ccd_corrections_2017(merged_df)\n"," else:\n"," correction_df = corrections(merged_df)\n","\n"," del(merged_df)\n"," total_corrections += len(correction_df)\n"," print(f'Correction_df length: {len(correction_df)}')\n"," print(f'Total Corrections: {total_corrections}')\n"," if len(correction_df) > 0:\n"," # if year != last_year:\n"," correction_year_2 = correction_df[['.geo', f'cc_{year_2}']]\n"," correction_year_2.to_csv(f'{path}{dist_list[i]}/{year_2}/result_monthly_cc_corrections.csv', index=False)\n"," if year != '2019':\n"," correction_year_1 = correction_df[['.geo', f'cc_{year_1}']]\n"," correction_year_1.to_csv(f'{path}{dist_list[i]}/{year_1}/result_monthly_cc_corrections.csv', index=False) # Comment when correcting 2017\n","\n"," del(correction_df)"]},{"cell_type":"markdown","metadata":{"id":"Tw7xfx5BO1eH"},"source":[" # Data Correction - CH"]},{"cell_type":"code","execution_count":6,"metadata":{"executionInfo":{"elapsed":20,"status":"ok","timestamp":1774795365410,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qqU0JIe4O40i"},"outputs":[],"source":["# Column Order\n","# ['.geo',\n","# 'rh98_{year_4}', 'rh98_{year_3}', 'rh98_{year_2}', 'rh98_{year_1}', 'rh98_{year}',\n","# 'rh75_{year_4}', 'rh75_{year_3}', 'rh75_{year_2}', 'rh75_{year_1}', 'rh75_{year}',\n","# 'rh50_{year_4}', 'rh50_{year_3}', 'rh50_{year_2}', 'rh50_{year_1}', 'rh50_{year}']\n","\n","\n","def ch_corrections(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # Correcting the second last year - rh98\n"," correction_df = df[(df[columns[5]] == df[columns[3]]) & (df[columns[3]] == df[columns[2]]) & (df[columns[4]] != df[columns[5]])]\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the middle year - rh98\n"," new_df = df[(df[columns[1]] == df[columns[2]]) & (df[columns[2]] == df[columns[4]]) & (df[columns[4]] == df[columns[5]]) & (df[columns[3]] != df[columns[1]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n","\n","\n"," # Correcting the second last year - rh75\n"," new_df = df[(df[columns[10]] == df[columns[8]]) & (df[columns[8]] == df[columns[7]]) & (df[columns[9]] != df[columns[10]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the middle year - rh75\n"," new_df = df[(df[columns[6]] == df[columns[7]]) & (df[columns[7]] == df[columns[9]]) & (df[columns[9]] == df[columns[10]]) & (df[columns[8]] != df[columns[6]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n","\n","\n"," # Correcting the second last year - rh50\n"," new_df = df[(df[columns[15]] == df[columns[13]]) & (df[columns[13]] == df[columns[12]]) & (df[columns[14]] != df[columns[15]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the middle year - rh50\n"," new_df = df[(df[columns[11]] == df[columns[12]]) & (df[columns[12]] == df[columns[14]]) & (df[columns[14]] == df[columns[15]]) & (df[columns[13]] != df[columns[11]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n","\n","\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n","\n"," # rh98\n"," correction_df.loc[(correction_df[columns[5]] == correction_df[columns[3]]) & (correction_df[columns[3]] == correction_df[columns[2]]) &\n"," (correction_df[columns[4]] != correction_df[columns[5]]), columns[4]] = correction_df[columns[5]]\n"," correction_df.loc[(correction_df[columns[1]] == correction_df[columns[2]]) & (correction_df[columns[2]] == correction_df[columns[4]]) &\n"," (correction_df[columns[4]] == correction_df[columns[5]]) & (correction_df[columns[3]] != correction_df[columns[1]]), columns[3]] = correction_df[columns[1]]\n","\n","\n"," # rh75\n"," correction_df.loc[(correction_df[columns[10]] == correction_df[columns[8]]) & (correction_df[columns[8]] == correction_df[columns[7]]) &\n"," (correction_df[columns[9]] != correction_df[columns[10]]), columns[9]] = correction_df[columns[10]]\n"," correction_df.loc[(correction_df[columns[6]] == correction_df[columns[7]]) & (correction_df[columns[7]] == correction_df[columns[9]]) &\n"," (correction_df[columns[9]] == correction_df[columns[10]]) & (correction_df[columns[8]] != correction_df[columns[6]]), columns[8]] = correction_df[columns[6]]\n","\n","\n"," # rh50\n"," correction_df.loc[(correction_df[columns[15]] == correction_df[columns[13]]) & (correction_df[columns[13]] == correction_df[columns[12]]) &\n"," (correction_df[columns[14]] != correction_df[columns[15]]), columns[14]] = correction_df[columns[15]]\n"," correction_df.loc[(correction_df[columns[11]] == correction_df[columns[12]]) & (correction_df[columns[12]] == correction_df[columns[14]]) &\n"," (correction_df[columns[14]] == correction_df[columns[15]]) & (correction_df[columns[13]] != correction_df[columns[11]]), columns[13]] = correction_df[columns[11]]\n","\n"," return correction_df\n","\n","def ch_corrections_2017(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # Correcting the second last year - rh98\n"," correction_df = df[(df[columns[5]] == df[columns[4]]) & (df[columns[4]] == df[columns[2]]) & (df[columns[3]] != df[columns[5]])]\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the second last year - rh75\n"," new_df = df[(df[columns[10]] == df[columns[9]]) & (df[columns[9]] == df[columns[7]]) & (df[columns[8]] != df[columns[10]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the second last year - rh50\n"," new_df = df[(df[columns[15]] == df[columns[14]]) & (df[columns[14]] == df[columns[12]]) & (df[columns[13]] != df[columns[15]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n","\n"," # rh98\n"," correction_df.loc[(correction_df[columns[5]] == correction_df[columns[4]]) & (correction_df[columns[4]] == correction_df[columns[2]]) &\n"," (correction_df[columns[3]] != correction_df[columns[5]]), columns[3]] = correction_df[columns[5]]\n","\n"," # rh75\n"," correction_df.loc[(correction_df[columns[10]] == correction_df[columns[9]]) & (correction_df[columns[9]] == correction_df[columns[7]]) &\n"," (correction_df[columns[8]] != correction_df[columns[10]]), columns[8]] = correction_df[columns[10]]\n","\n"," # rh50\n"," correction_df.loc[(correction_df[columns[15]] == correction_df[columns[14]]) & (correction_df[columns[14]] == correction_df[columns[12]]) &\n"," (correction_df[columns[13]] != correction_df[columns[15]]), columns[13]] = correction_df[columns[15]]\n","\n"," return correction_df"]},{"cell_type":"markdown","metadata":{"id":"2aOG9olEVlV-"},"source":["CH corrections without Chunking"]},{"cell_type":"code","execution_count":19,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"exmkYL5zPGs-","outputId":"3c535796-d3f1-468c-de0e-a1ba907ce127","executionInfo":{"status":"ok","timestamp":1774808836553,"user_tz":-330,"elapsed":1310782,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["Western Himalayan Region\n","len(dist_list): 4\n","dist_list=: ['Dehradun', 'Garhwal', 'Nainital', 'Champawat']\n","0 Dehradun\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","... ... ... \n","5126057 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126058 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126059 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126060 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126061 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 1.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","5126057 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126058 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126059 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126060 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126061 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 1.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","5126057 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126058 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126059 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126060 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126061 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2024 rh50_2024 \n","0 0.0 0.0 \n","1 0.0 0.0 \n","2 0.0 0.0 \n","3 0.0 0.0 \n","4 0.0 0.0 \n","... ... ... \n","5126057 0.0 0.0 \n","5126058 1.0 0.0 \n","5126059 0.0 1.0 \n","5126060 0.0 1.0 \n","5126061 0.0 0.0 \n","\n","[5126062 rows x 16 columns]\n","Length of merged_df: 5126062\n","Total Length: 5126062\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 1278553\n","Total Corrections: 1278553\n","1 Garhwal\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","... ... ... \n","9596162 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596163 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596164 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596165 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596166 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","9596162 0.0 0.0 0.0 0.0 0.0 NaN \n","9596163 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596164 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596165 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596166 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","9596162 NaN NaN NaN NaN NaN 0.0 \n","9596163 0.0 0.0 NaN NaN NaN 0.0 \n","9596164 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596165 0.0 0.0 NaN NaN NaN 0.0 \n","9596166 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2024 rh50_2024 \n","0 0.0 0.0 \n","1 0.0 0.0 \n","2 0.0 0.0 \n","3 0.0 0.0 \n","4 0.0 0.0 \n","... ... ... \n","9596162 0.0 0.0 \n","9596163 0.0 0.0 \n","9596164 0.0 0.0 \n","9596165 0.0 0.0 \n","9596166 0.0 0.0 \n","\n","[9596167 rows x 16 columns]\n","Length of merged_df: 9596167\n","Total Length: 14722229\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 1725432\n","Total Corrections: 3003985\n","2 Nainital\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","... ... ... \n","6912865 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912866 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912867 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912868 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912869 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 1.0 1.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","6912865 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912866 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912867 1.0 1.0 1.0 1.0 1.0 1.0 \n","6912868 1.0 1.0 1.0 1.0 1.0 1.0 \n","6912869 1.0 1.0 0.0 1.0 1.0 0.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","6912865 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912866 1.0 0.0 1.0 1.0 1.0 0.0 \n","6912867 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912868 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912869 1.0 1.0 1.0 1.0 1.0 0.0 \n","\n"," rh75_2024 rh50_2024 \n","0 0.0 0.0 \n","1 0.0 0.0 \n","2 0.0 0.0 \n","3 0.0 0.0 \n","4 0.0 0.0 \n","... ... ... \n","6912865 0.0 1.0 \n","6912866 0.0 1.0 \n","6912867 0.0 1.0 \n","6912868 0.0 1.0 \n","6912869 0.0 1.0 \n","\n","[6912870 rows x 16 columns]\n","Length of merged_df: 6912870\n","Total Length: 21635099\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 1325096\n","Total Corrections: 4329081\n","3 Champawat\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","... ... ... \n","2951210 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951211 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951212 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951213 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951214 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 1.0 1.0 0.0 0.0 1.0 0.0 \n","1 1.0 1.0 0.0 0.0 0.0 0.0 \n","2 1.0 1.0 1.0 0.0 0.0 0.0 \n","3 0.0 1.0 0.0 1.0 1.0 0.0 \n","4 1.0 1.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","2951210 1.0 0.0 1.0 1.0 1.0 1.0 \n","2951211 0.0 0.0 1.0 1.0 1.0 1.0 \n","2951212 0.0 0.0 1.0 1.0 1.0 1.0 \n","2951213 0.0 0.0 1.0 1.0 1.0 1.0 \n","2951214 0.0 0.0 1.0 1.0 1.0 1.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 1.0 0.0 1.0 1.0 1.0 0.0 \n","1 1.0 1.0 1.0 1.0 1.0 1.0 \n","2 1.0 1.0 1.0 1.0 1.0 1.0 \n","3 1.0 1.0 1.0 1.0 1.0 0.0 \n","4 0.0 1.0 1.0 1.0 1.0 0.0 \n","... ... ... ... ... ... ... \n","2951210 1.0 1.0 1.0 1.0 1.0 0.0 \n","2951211 1.0 1.0 1.0 1.0 1.0 1.0 \n","2951212 1.0 1.0 1.0 1.0 1.0 1.0 \n","2951213 1.0 1.0 1.0 1.0 1.0 1.0 \n","2951214 1.0 1.0 1.0 1.0 1.0 1.0 \n","\n"," rh75_2024 rh50_2024 \n","0 1.0 1.0 \n","1 1.0 1.0 \n","2 0.0 0.0 \n","3 1.0 1.0 \n","4 0.0 1.0 \n","... ... ... \n","2951210 0.0 0.0 \n","2951211 0.0 0.0 \n","2951212 1.0 0.0 \n","2951213 1.0 1.0 \n","2951214 1.0 1.0 \n","\n","[2951215 rows x 16 columns]\n","Length of merged_df: 2951215\n","Total Length: 24586314\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 762567\n","Total Corrections: 5091648\n"]}],"source":["# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n","\n"," df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n"," df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)\n"," district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n"," dist_list = []\n"," for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," dist_list.append(district)\n","\n"," dist_list = ['Dehradun', 'Garhwal', 'Nainital', 'Champawat']\n"," print(f'len(dist_list): {len(dist_list)}')\n"," print(f'dist_list=: {dist_list}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/'\n","\n"," total_corrections = 0\n"," total_length = 0\n","\n"," for i in range(len(dist_list)):\n","\n"," if i == 4 or i==6:\n"," continue\n","\n"," print(i, dist_list[i])\n"," file_4 = path + dist_list[i] + f\"/{year_4}/result_chm.csv\"\n"," file_3 = path + dist_list[i] + f\"/{year_3}/result_chm.csv\"\n"," file_2 = path + dist_list[i] + f\"/{year_2}/result_chm.csv\"\n"," file_1 = path + dist_list[i] + f\"/{year_1}/result_chm.csv\"\n"," file_0 = path + dist_list[i] + f\"/{year}/result_chm.csv\"\n","\n"," try:\n"," df_4 = pd.read_csv(file_4)\n"," df_4.rename(columns={'rh98_class': f'rh98_{year_4}', 'rh75_class': f'rh75_{year_4}', 'rh50_class': f'rh50_{year_4}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_4 = pd.DataFrame(columns=['.geo', f'rh98_{year_4}', f'rh75_{year_4}', f'rh50_{year_4}', f'ch_{year_4}'])\n","\n"," try:\n"," df_3 = pd.read_csv(file_3)\n"," df_3.rename(columns={'rh98_class': f'rh98_{year_3}', 'rh75_class': f'rh75_{year_3}', 'rh50_class': f'rh50_{year_3}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_3 = pd.DataFrame(columns=['.geo', f'rh98_{year_3}', f'rh75_{year_3}', f'rh50_{year_3}', f'ch_{year_3}'])\n","\n"," merged_df = pd.merge(df_4, df_3, on='.geo', how='outer')\n"," del(df_4)\n"," del(df_3)\n","\n"," try:\n"," df_2 = pd.read_csv(file_2)\n"," df_2.rename(columns={'rh98_class': f'rh98_{year_2}', 'rh75_class': f'rh75_{year_2}', 'rh50_class': f'rh50_{year_2}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_2 = pd.DataFrame(columns=['.geo', f'rh98_{year_2}', f'rh75_{year_2}', f'rh50_{year_2}', f'ch_{year_2}'])\n","\n"," merged_df = pd.merge(merged_df, df_2, on='.geo', how='outer')\n"," del(df_2)\n","\n"," try:\n"," df_1 = pd.read_csv(file_1)\n"," df_1.rename(columns={'rh98_class': f'rh98_{year_1}', 'rh75_class': f'rh75_{year_1}', 'rh50_class': f'rh50_{year_1}'}, inplace=True)\n"," except Exception as e:\n"," print(e)\n"," df_1 = pd.DataFrame(columns=['.geo', f'rh98_{year_1}', f'rh75_{year_1}', f'rh50_{year_1}', f'ch_{year_1}'])\n","\n"," merged_df = pd.merge(merged_df, df_1, on='.geo', how='outer')\n"," del(df_1)\n","\n"," try:\n"," df_0 = pd.read_csv(file_0)\n"," df_0.rename(columns={'rh98_class': f'rh98_{year}', 'rh75_class': f'rh75_{year}', 'rh50_class': f'rh50_{year}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_0 = pd.DataFrame(columns=['.geo', f'rh98_{year}', f'rh75_{year}', f'rh50_{year}', f'ch_{year}'])\n","\n"," merged_df = pd.merge(merged_df, df_0, on='.geo', how='outer')\n"," del(df_0)\n"," print(\"merged_df>>>\",merged_df)\n","\n"," try:\n"," merged_df = merged_df[['.geo', f'rh98_{year_4}', f'rh98_{year_3}', f'rh98_{year_2}', f'rh98_{year_1}', f'rh98_{year}',\n"," f'rh75_{year_4}', f'rh75_{year_3}', f'rh75_{year_2}', f'rh75_{year_1}', f'rh75_{year}', f'rh50_{year_4}',\n"," f'rh50_{year_3}', f'rh50_{year_2}', f'rh50_{year_1}', f'rh50_{year}']]\n"," except Exception as e:\n"," print(e)\n","\n"," total_length += len(merged_df)\n"," print(\"Length of merged_df:\", len(merged_df))\n"," print(\"Total Length:\", total_length)\n"," if year == '2019':\n"," correction_df = ch_corrections_2017(merged_df) # Uncomment when correcting 2017\n"," else:\n"," correction_df = ch_corrections(merged_df) # Comment when correcting 2017\n","\n"," del(merged_df)\n","\n"," total_corrections += len(correction_df)\n"," print(f'Correction_df length: {len(correction_df)}')\n"," print(f'Total Corrections: {total_corrections}')\n"," if len(correction_df) > 0:\n","\n"," choices = [0, 0, 1, 2, 1, 2]\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_4}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_3}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_2}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_1}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1)\n"," ]\n"," correction_df[f'ch_{year}'] = np.select(conditions, choices, default=3)\n","\n"," cols = correction_df.columns\n"," for j in range(1, len(cols)):\n"," correction_df[cols[j]] = correction_df[cols[j]].astype('Int64')\n"," # if year != last_year: # TODO remove this\n"," correction_year_2 = correction_df[['.geo', f'rh50_{year_2}', f'rh75_{year_2}', f'rh98_{year_2}', f'ch_{year_2}']]\n"," correction_year_2.to_csv(f'{path}{dist_list[i]}/{year_2}/result_chm_corrections.csv', index=False)\n"," if year != '2019':\n"," correction_year_1 = correction_df[['.geo', f'rh50_{year_1}', f'rh75_{year_1}', f'rh98_{year_1}', f'ch_{year_1}']] # Comment when correcting 2017\n"," correction_year_1.to_csv(f'{path}{dist_list[i]}/{year_1}/result_chm_corrections.csv', index=False) # Comment when correcting 2017\n","\n"," del(correction_df)\n"]},{"cell_type":"markdown","metadata":{"id":"ZwMptI_mVQ4a"},"source":["CH - correction Chunking (Run this only for those locations which are causing RAM issue)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"eNlS8jKW3tom","executionInfo":{"status":"ok","timestamp":1774375877955,"user_tz":-330,"elapsed":6162811,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"ca3318b0-6fe1-42e9-c59f-03e707e8339c"},"outputs":[{"output_type":"stream","name":"stdout","text":["2024\n","Eastern Plateau & Hills Region\n","len(dist_list): 1\n","dist_list: ['Garhchiroli']\n","0 District Garhchiroli\n","File 4\n","File 3\n","File 2\n","File 1\n","File 0\n","Total unique .geo keys (union): 20257293\n","Processing 6 merge-chunks of size up to 4000000...\n","Processing SQL chunk 1/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 4000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 251390\n","Total Corrections so far: 251390\n","Processing SQL chunk 2/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 8000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 365821\n","Total Corrections so far: 617211\n","Processing SQL chunk 3/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 12000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 530155\n","Total Corrections so far: 1147366\n","Processing SQL chunk 4/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 16000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 626236\n","Total Corrections so far: 1773602\n","Processing SQL chunk 5/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 20000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 824080\n","Total Corrections so far: 2597682\n","Processing SQL chunk 6/6 with 257293 rows\n","Length of merged_df (this chunk): 257293\n","Total Length so far: 20257293\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 54985\n","Total Corrections so far: 2652667\n"]}],"source":["# Add imports at top of your file (if not already imported)\n","import sqlite3\n","import os\n","import math\n","from pathlib import Path\n","\n","CHUNKSIZE = 4000_000\n","print(year)\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n","\n"," df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n"," df['IntersectingZones'] = df['IntersectingZones'].apply(ast.literal_eval)\n"," district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n","\n"," dist_list = district_mapping_df['District'].tolist()\n"," dist_list = ['Garhchiroli']\n"," print(f'len(dist_list): {len(dist_list)}')\n"," print(f'dist_list: {dist_list}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/'\n"," total_corrections = 0\n"," total_length = 0\n","\n","\n"," for i in range(len(dist_list)):\n"," print(i, \"District\",dist_list[i])\n"," file_4 = f\"{path}{dist_list[i]}/{year_4}/result_chm.csv\"\n"," file_3 = f\"{path}{dist_list[i]}/{year_3}/result_chm.csv\"\n"," file_2 = f\"{path}{dist_list[i]}/{year_2}/result_chm.csv\"\n"," file_1 = f\"{path}{dist_list[i]}/{year_1}/result_chm.csv\"\n"," file_0 = f\"{path}{dist_list[i]}/{year}/result_chm.csv\"\n","\n"," # --- Begin drop-in chunked-on-disk merge logic ---\n"," # Create temp sqlite DB per district to avoid collisions and keep things local to each district\n"," db_path = f'{path}{dist_list[i]}/temp_merge.db'\n"," # ensure directory exists\n"," Path(os.path.dirname(db_path)).mkdir(parents=True, exist_ok=True)\n"," # remove previous DB if exists (so headers won't collide when appending)\n"," if os.path.exists(db_path):\n"," try:\n"," os.remove(db_path)\n"," except Exception:\n"," pass\n","\n"," conn = sqlite3.connect(db_path)\n"," # Use pragmas to speed inserts (safe for temp file)\n"," conn.execute('PRAGMA synchronous=OFF;')\n"," conn.execute('PRAGMA journal_mode=MEMORY;')\n"," conn.commit()\n","\n"," # helper to stream a CSV into an sqlite table; normalizes column names: .geo -> geo, rh*_class -> rh*_\n"," def stream_csv_to_table(csv_file, table_name, year_tag):\n"," # if file doesn't exist, create empty table with expected schema (so joins work)\n"," if not os.path.exists(csv_file) or os.path.getsize(csv_file) <= 1:\n"," # create empty table with schema\n"," conn.execute(f'''\n"," CREATE TABLE IF NOT EXISTS {table_name} (\n"," geo TEXT PRIMARY KEY,\n"," rh98_{year_tag} INTEGER,\n"," rh75_{year_tag} INTEGER,\n"," rh50_{year_tag} INTEGER\n"," )\n"," ''')\n"," conn.commit()\n"," return\n","\n"," cols = ['.geo', 'rh98_class', 'rh75_class', 'rh50_class']\n"," try:\n"," reader = pd.read_csv(csv_file, usecols=cols, chunksize=CHUNKSIZE, iterator=True)\n"," except Exception as e:\n"," # if reading with those columns fails, fall back to reading only .geo\n"," reader = pd.read_csv(csv_file, chunksize=CHUNKSIZE, iterator=True)\n","\n"," any_rows = False\n","\n"," for chunk in reader:\n"," any_rows = True\n"," # normalize column names and keep only what we need\n"," if '.geo' in chunk.columns:\n"," chunk = chunk.rename(columns={\n"," '.geo': 'geo',\n"," 'rh98_class': f'rh98_{year_tag}',\n"," 'rh75_class': f'rh75_{year_tag}',\n"," 'rh50_class': f'rh50_{year_tag}'\n"," })\n"," # ensure columns exist even if file lacked them\n"," for c in [f'rh98_{year_tag}', f'rh75_{year_tag}', f'rh50_{year_tag}']:\n"," if c not in chunk.columns:\n"," chunk[c] = pd.NA\n","\n"," chunk[['geo', f'rh98_{year_tag}', f'rh75_{year_tag}', f'rh50_{year_tag}']].to_sql(\n"," table_name, conn, if_exists='append', index=False\n"," )\n","\n"," # If the file had headers but no data rows,\n"," # make sure the table still exists with the right schema\n"," if not any_rows:\n"," conn.execute(f'''\n"," CREATE TABLE IF NOT EXISTS {table_name} (\n"," geo TEXT PRIMARY KEY,\n"," rh98_{year_tag} INTEGER,\n"," rh75_{year_tag} INTEGER,\n"," rh50_{year_tag} INTEGER\n"," )\n"," ''')\n"," conn.commit()\n","\n"," # Stream each file into its own sqlite table\n"," print(\"File 4\")\n"," stream_csv_to_table(file_4, 't4', year_4)\n"," print(\"File 3\")\n"," stream_csv_to_table(file_3, 't3', year_3)\n"," print(\"File 2\")\n"," stream_csv_to_table(file_2, 't2', year_2)\n"," print(\"File 1\")\n"," stream_csv_to_table(file_1, 't1', year_1)\n"," print(\"File 0\")\n"," stream_csv_to_table(file_0, 't0', year)\n","\n"," # Build union of all geo keys (on-disk), then count total rows\n"," union_sql = '''\n"," SELECT geo FROM t4\n"," UNION\n"," SELECT geo FROM t3\n"," UNION\n"," SELECT geo FROM t2\n"," UNION\n"," SELECT geo FROM t1\n"," UNION\n"," SELECT geo FROM t0\n"," '''\n"," count_sql = f\"SELECT COUNT(*) FROM ({union_sql}) AS u\"\n"," cursor = conn.execute(count_sql)\n"," total_rows = cursor.fetchone()[0]\n"," print(\"Total unique .geo keys (union):\", total_rows)\n","\n"," # process in CHUNKSIZE slices from the union\n"," num_chunks = math.ceil(total_rows / CHUNKSIZE)\n"," print(f\"Processing {num_chunks} merge-chunks of size up to {CHUNKSIZE}...\")\n","\n"," # build a parameterized select that left-joins each year's table to the union subquery\n"," chunk_select_template = f'''\n"," SELECT u.geo AS \".geo\",\n"," t4.rh98_{year_4} as rh98_{year_4}, t4.rh75_{year_4} as rh75_{year_4}, t4.rh50_{year_4} as rh50_{year_4},\n"," t3.rh98_{year_3} as rh98_{year_3}, t3.rh75_{year_3} as rh75_{year_3}, t3.rh50_{year_3} as rh50_{year_3},\n"," t2.rh98_{year_2} as rh98_{year_2}, t2.rh75_{year_2} as rh75_{year_2}, t2.rh50_{year_2} as rh50_{year_2},\n"," t1.rh98_{year_1} as rh98_{year_1}, t1.rh75_{year_1} as rh75_{year_1}, t1.rh50_{year_1} as rh50_{year_1},\n"," t0.rh98_{year} as rh98_{year}, t0.rh75_{year} as rh75_{year}, t0.rh50_{year} as rh50_{year}\n"," FROM (\n"," {union_sql}\n"," ORDER BY geo\n"," LIMIT ? OFFSET ?\n"," ) u\n"," LEFT JOIN t4 ON u.geo = t4.geo\n"," LEFT JOIN t3 ON u.geo = t3.geo\n"," LEFT JOIN t2 ON u.geo = t2.geo\n"," LEFT JOIN t1 ON u.geo = t1.geo\n"," LEFT JOIN t0 ON u.geo = t0.geo\n"," '''\n","\n"," # remove any existing output correction files for this district/year so we can append anew\n"," out_file_y2 = f'{path}{dist_list[i]}/{year_2}/result_chm_corrections.csv'\n"," out_file_y1 = f'{path}{dist_list[i]}/{year_1}/result_chm_corrections.csv'\n"," # We'll append chunk outputs; delete existing so headers are added correctly (mimic original behaviour)\n"," if os.path.exists(out_file_y2):\n"," os.remove(out_file_y2)\n"," if os.path.exists(out_file_y1) and year != '2019':\n"," os.remove(out_file_y1)\n","\n"," for chunk_idx in range(num_chunks):\n"," offset = chunk_idx * CHUNKSIZE\n"," params = (CHUNKSIZE, offset)\n"," # read chunk into pandas\n"," df_chunk = pd.read_sql_query(chunk_select_template, conn, params=params)\n","\n"," # rename the 'year' columns to include the actual year variable names used in the template above\n"," # (they already are named properly except the generic 'rh98_{year}' placeholders which the SQL aliased as rh98_{year})\n"," # ensure types and presence of columns, then proceed exactly as your old code expects (select columns etc.)\n"," print(f\"Processing SQL chunk {chunk_idx+1}/{num_chunks} with {len(df_chunk)} rows\")\n"," total_length += len(df_chunk)\n"," print(\"Length of merged_df (this chunk):\", len(df_chunk))\n"," print(\"Total Length so far:\", total_length)\n","\n"," # Recreate expected column selection (same as original)\n"," try:\n"," merged_df = df_chunk[['.geo',\n"," f'rh98_{year_4}', f'rh98_{year_3}', f'rh98_{year_2}', f'rh98_{year_1}', f'rh98_{year}',\n"," f'rh75_{year_4}', f'rh75_{year_3}', f'rh75_{year_2}', f'rh75_{year_1}', f'rh75_{year}',\n"," f'rh50_{year_4}', f'rh50_{year_3}', f'rh50_{year_2}', f'rh50_{year_1}', f'rh50_{year}']]\n"," except Exception as e:\n"," print(\"Column selection error on chunk:\", e)\n"," # Continue with whatever columns are present\n"," merged_df = df_chunk.copy()\n","\n"," # run correction on this chunk exactly like before\n"," if year == '2019':\n"," correction_df = ch_corrections_2017(merged_df)\n"," else:\n"," correction_df = ch_corrections(merged_df)\n","\n"," del(merged_df)\n","\n"," total_corrections += len(correction_df)\n"," print(f'Correction_df length (chunk): {len(correction_df)}')\n"," print(f'Total Corrections so far: {total_corrections}')\n","\n"," if len(correction_df) > 0:\n"," # same CH assignment logic as before - keep exactly the same code\n"," choices = [0, 0, 1, 2, 1, 2]\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_4}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_3}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_2}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_1}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1)\n"," ]\n"," correction_df[f'ch_{year}'] = np.select(conditions, choices, default=3)\n","\n"," cols = correction_df.columns\n"," for j in range(1, len(cols)):\n"," correction_df[cols[j]] = correction_df[cols[j]].astype('Int64')\n","\n"," # Write outputs appending chunk results (same filenames as before)\n"," # if year != last_year:\n"," # correction_year_2 = correction_df[['.geo', f'rh50_{year_2}', f'rh75_{year_2}', f'rh98_{year_2}', f'ch_{year_2}']]\n"," correction_year_2 = correction_df[correction_df[f'ch_{year_2}'].notna()][['.geo', f'rh50_{year_2}', f'rh75_{year_2}', f'rh98_{year_2}', f'ch_{year_2}']]\n"," correction_year_2.to_csv(out_file_y2, mode='a', index=False, header=not os.path.exists(out_file_y2))\n","\n"," if year != '2019':\n"," # correction_year_1 = correction_df[['.geo', f'rh50_{year_1}', f'rh75_{year_1}', f'rh98_{year_1}', f'ch_{year_1}']]\n"," correction_year_1 = correction_df[correction_df[f'ch_{year_1}'].notna()][['.geo', f'rh50_{year_1}', f'rh75_{year_1}', f'rh98_{year_1}', f'ch_{year_1}']]\n"," correction_year_1.to_csv(out_file_y1, mode='a', index=False, header=not os.path.exists(out_file_y1))\n","\n"," del(correction_df)\n","\n"," # cleanup sqlite DB\n"," conn.close()\n"," try:\n"," os.remove(db_path)\n"," except Exception:\n"," pass\n","\n"," # --- End drop-in chunked-on-disk merge logic ---"]}],"metadata":{"colab":{"collapsed_sections":["OpBWXM1TMVAs"],"provenance":[{"file_id":"1mrIZUaD4wZbGsDhalhghUuHK-uYOTTew","timestamp":1758914446736}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb b/utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb new file mode 100644 index 00000000..a7b0281d --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":13357,"status":"ok","timestamp":1775284491086,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"yewisZzO_XBA"},"outputs":[],"source":["import ee\n","import pandas as pd\n","import json\n","from glob import glob\n","import geemap\n","import pyproj\n","from geopandas import geopandas as gpd\n","from shapely.geometry import Point\n","from copy import deepcopy\n","import numpy as np\n","import os\n","import ast\n","from google.cloud import storage"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":22,"status":"ok","timestamp":1774410186498,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"iIy2d0N33YVM","outputId":"d30444eb-aa3c-47f4-a361-2793d2fe07a2"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# from google.colab import drive\n","# drive.flush_and_unmount()"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":50444,"status":"ok","timestamp":1775284560071,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"XOwz3mnBYzzf","outputId":"da646a7e-387e-4fd0-ea21-11a6859e69e4"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":3,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":44694,"status":"ok","timestamp":1775284604766,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"EmZdWlXYBv-I","outputId":"b6bfe92a-5c89-470a-ee5b-add2fbf5b974"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["ee.Authenticate()\n","ee_project = \"corestack1-dev-alpha\"\n","ee.Initialize(project=\"core-stack-dev-2\")#\"corestack1-dev-alpha\")"]},{"cell_type":"code","execution_count":4,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":1407,"status":"ok","timestamp":1775284606174,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"SnpGWiMmDvM3","outputId":"36fbf7ff-49be-4ed0-bd4c-ff348b3a593c"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["client = storage.Client()\n","bucket = client.get_bucket('core_stack')"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":4,"status":"ok","timestamp":1775284606179,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"RT2Qqbs-Y4Qv","outputId":"b9727627-6f15-48e8-d405-a42d433c899e"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775284609820,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qoDgejJ4Y7uk","outputId":"54668df5-bb0a-4fc2-9e77-a9de785ea269"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# agroclimatic_zone = 'Eastern Plateau & Hills Region'\n","\n","# agroclimatic_zone = 'Lower Gangetic Plain Region'\n","\n","agroclimatic_zone = 'Middle Gangetic Plain Region'\n","\n","# agroclimatic_zone = 'Western Himalayan Region'\n","\n","# agroclimatic_zone = 'Eastern Himalayan Region'\n","\n","# agroclimatic_zone = 'Upper Gangetic Plain Region'\n","\n","# agroclimatic_zone = 'Trans Gangetic Plain Region'\n","\n","# agroclimatic_zone = 'Central Plateau & Hills Region'\n","\n","# agroclimatic_zone = 'Western Plateau and Hills Region'\n","\n","# agroclimatic_zone = 'Southern Plateau and Hills Region'\n","\n","# agroclimatic_zone = 'East Coast Plains & Hills Region'"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775284614550,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"EpHcGapNX0cq","outputId":"cd3efa6e-0415-40bb-be10-31803077cad4"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":4,"status":"ok","timestamp":1774369649861,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qlIS9q3xZAzR","outputId":"e02201ba-b02f-4f7b-83df-53508912e2a1"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# acz_list = ['Western Himalayan Region', 'Eastern Himalayan Region', 'Lower Gangetic Plain Region',\n","# 'Middle Gangetic Plain Region', 'Upper Gangetic Plain Region', 'Trans Gangetic Plain Region',\n","# 'Eastern Plateau & Hills Region', 'Central Plateau & Hills Region', 'Western Plateau and Hills Region',\n","# 'Southern Plateau and Hills Region', 'East Coast Plains & Hills Region']\n","\n","# acz_list = ['Eastern Plateau & Hills Region']"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775284625250,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"j6ZwUG7Q-7WS","outputId":"989135a3-bb1b-477a-99c7-3a5916d759bb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def upload_file_to_gcs(local_file_path, file_name):\n"," blob = bucket.blob(f'nrm_tree_health/compiled_results/{file_name}.csv') # GCS path\n"," blob.upload_from_filename(local_file_path)\n","\n"," print(\"Upload complete.\")\n","\n","def export_to_gee(file_name):\n"," # CSV GCS path\n"," gcs_path = f'gs://core_stack/nrm_tree_health/compiled_results/{file_name}.csv'\n","\n"," # Create task ID and run table ingestion\n"," task_id = ee.data.newTaskId()[0]\n"," asset_id = f'projects/{ee_project}/assets/tree_characteristics/{file_name}'\n","\n"," manifest = {\n"," 'id': asset_id,\n"," 'sources': [\n"," {\n"," 'primaryPath': gcs_path,\n"," 'additionalPaths': []\n"," }\n"," ]\n"," }\n","\n"," ee.data.startTableIngestion(task_id, manifest)\n"," print(\"Ingestion task started:\", task_id)"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"collapsed":true,"executionInfo":{"elapsed":40,"status":"ok","timestamp":1775284627970,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"T7qHDbSCcy2H","outputId":"a8394995-d17d-44e1-c095-3add8ad846c5"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Set the years for which results need to be combined\n","years = ['2016','2017', '2018','2019','2020','2021','2022','2023','2024']"]},{"cell_type":"markdown","metadata":{"id":"_e3eXwraczpB"},"source":["# Combine CSVs CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":73},"executionInfo":{"elapsed":3235,"status":"ok","timestamp":1774369664051,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"cH7MCucjc4P8","outputId":"9ae54cac-8bc9-4727-edc0-224ef31a9a88"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["119\n","['East Godavari', 'Srikakulam', 'Visakhapatnam', 'Vizianagaram', 'AurangabadB', 'Banka', 'Gaya', 'Jamui', 'Nawada', 'Rohtas', 'Baloda Bazar', 'Balod', 'BalrampurC', 'Bastar', 'Bemetara', 'BijapurC', 'BilaspurC', 'Dantewada', 'Dhamtari', 'Durg', 'Gariaband', 'Janjgir-Champa', 'Jashpur', 'Kabeerdham', 'Kondagaon', 'Korba', 'Koriya', 'Mahasamund', 'Mungeli', 'Narayanpur', 'Raigarh', 'Raipur', 'Rajnandgaon', 'Sukma', 'Surajpur', 'Surguja', 'Uttar Bastar Kanker', 'Bokaro', 'Chatra', 'Deoghar', 'Dhanbad', 'Dumka', 'Garhwa', 'Giridih', 'Godda', 'Gumla', 'Hazaribagh', 'Jamtara', 'Khunti', 'Kodarma', 'Latehar', 'Lohardaga', 'Pakur', 'Palamu', 'Pashchimi Singhbhum', 'Purbi Singhbhum', 'Ramgarh', 'Ranchi', 'Sahibganj', 'Saraikela-kharsawan', 'Simdega', 'Anuppur', 'Balaghat', 'Dindori', 'Jabalpur', 'Katni', 'Mandla', 'Rewa', 'Satna', 'Seoni', 'Shahdol', 'Sidhi', 'Singrauli', 'Umaria', 'Bhandara', 'Chandrapur', 'Garhchiroli', 'Gondiya', 'Nagpur', 'Wardha', 'Yavatmal', 'Anugul', 'Balangir', 'Baleshwar', 'Bargarh', 'Bauda', 'Bhadrak', 'Cuttack', 'Debagarh', 'Dhenkanal', 'Gajapati', 'Ganjam', 'Jajapur', 'Jharsuguda', 'Kalahandi', 'Kandhamal', 'Kendujhar', 'Koraput', 'Malkangiri', 'Mayurbhanj', 'Nabarangapur', 'Nayagarh', 'Nuapada', 'Rayagada', 'Sambalpur', 'Subarnapur', 'Sundargarh', 'Adilabad', 'Karimnagar', 'Khammam', 'Warangal', 'Mirzapur', 'Sonbhadra', 'Bankura', 'Barddhaman', 'Birbhum', 'Murshidabad', 'Pashchim Medinipur', 'Puruliya']\n"]}],"source":["df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = list(df['Name'])\n","print(len(dist_list))\n","print(dist_list)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":24132732,"status":"error","timestamp":1774393796785,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9hFjUp4QmLL3","outputId":"401dc704-cf94-41f9-a38c-41c4c2b11d2a"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["2023\n","0 East Godavari\n","1 Srikakulam\n","2 Visakhapatnam\n","3 Vizianagaram\n","4 AurangabadB\n","5 Banka\n","6 Gaya\n","7 Jamui\n","8 Nawada\n","9 Rohtas\n","10 Baloda Bazar\n","11 Balod\n","12 BalrampurC\n","13 Bastar\n","14 Bemetara\n","15 BijapurC\n","16 BilaspurC\n","17 Dantewada\n","18 Dhamtari\n","19 Durg\n","20 Gariaband\n","21 Janjgir-Champa\n","22 Jashpur\n","23 Kabeerdham\n","24 Kondagaon\n","25 Korba\n","26 Koriya\n","Saving result_0.csv with 80,497,091 rows\n","Upload complete.\n","Ingestion task started: c73487a3-3340-4ce0-afe7-141323f3c52a\n","27 Mahasamund\n","28 Mungeli\n","29 Narayanpur\n","30 Raigarh\n","31 Raipur\n","32 Rajnandgaon\n","33 Sukma\n","34 Surajpur\n","35 Surguja\n","36 Uttar Bastar Kanker\n","37 Bokaro\n","38 Chatra\n","39 Deoghar\n","40 Dhanbad\n","41 Dumka\n","42 Garhwa\n","43 Giridih\n","Saving result_1.csv with 80,298,124 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: 1469ba67-3aff-4e72-997c-39bbba391b60\n","44 Godda\n","45 Gumla\n","46 Hazaribagh\n","47 Jamtara\n","48 Khunti\n","49 Kodarma\n","50 Latehar\n","51 Lohardaga\n","52 Pakur\n","53 Palamu\n","54 Pashchimi Singhbhum\n","55 Purbi Singhbhum\n","56 Ramgarh\n","57 Ranchi\n","58 Sahibganj\n","59 Saraikela-kharsawan\n","60 Simdega\n","61 Anuppur\n","62 Balaghat\n","63 Dindori\n","64 Jabalpur\n","65 Katni\n","66 Mandla\n","67 Rewa\n","68 Satna\n","69 Seoni\n","70 Shahdol\n","71 Sidhi\n","Saving result_2.csv with 80,399,602 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: 17b547b5-7742-4840-806d-2fb7800bc9ce\n","72 Singrauli\n","73 Umaria\n","74 Bhandara\n","75 Chandrapur\n","76 Garhchiroli\n","77 Gondiya\n","78 Nagpur\n","79 Wardha\n","80 Yavatmal\n","81 Anugul\n","82 Balangir\n","83 Baleshwar\n","84 Bargarh\n","85 Bauda\n","86 Bhadrak\n","87 Cuttack\n","88 Debagarh\n","89 Dhenkanal\n","Saving result_3.csv with 80,196,991 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: ed0f5f3d-2f04-43b1-9f20-0b012f85218a\n","90 Gajapati\n","91 Ganjam\n","92 Jajapur\n","93 Jharsuguda\n","94 Kalahandi\n","95 Kandhamal\n","96 Kendujhar\n","97 Koraput\n","98 Malkangiri\n","99 Mayurbhanj\n","100 Nabarangapur\n","101 Nayagarh\n","102 Nuapada\n","103 Rayagada\n","Saving result_4.csv with 80,452,883 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: ae7633ed-a43e-4b3c-9074-3b3f223d2342\n","104 Sambalpur\n","105 Subarnapur\n","106 Sundargarh\n","107 Adilabad\n","108 Karimnagar\n","109 Khammam\n","110 Warangal\n","111 Mirzapur\n","112 Sonbhadra\n","113 Bankura\n","114 Barddhaman\n","115 Birbhum\n","116 Murshidabad\n","117 Pashchim Medinipur\n","118 Puruliya\n","Saving result_5.csv with 30,673,393 rows\n","Upload complete.\n","Ingestion task started: 1869d43e-71d7-49cc-9c1d-b1bb4a7f8467\n","2024\n","0 East Godavari\n","1 Srikakulam\n","2 Visakhapatnam\n","3 Vizianagaram\n","4 AurangabadB\n","5 Banka\n","6 Gaya\n","7 Jamui\n","8 Nawada\n","9 Rohtas\n","10 Baloda Bazar\n","11 Balod\n","12 BalrampurC\n","13 Bastar\n","14 Bemetara\n","15 BijapurC\n","16 BilaspurC\n","17 Dantewada\n","18 Dhamtari\n","19 Durg\n","20 Gariaband\n","21 Janjgir-Champa\n","22 Jashpur\n","23 Kabeerdham\n","24 Kondagaon\n","25 Korba\n","26 Koriya\n","Saving result_0.csv with 80,265,488 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: dafeda5b-16c0-423d-a117-240a643c4431\n","27 Mahasamund\n","28 Mungeli\n","29 Narayanpur\n","30 Raigarh\n","31 Raipur\n","32 Rajnandgaon\n","33 Sukma\n","34 Surajpur\n","35 Surguja\n","36 Uttar Bastar Kanker\n","37 Bokaro\n","38 Chatra\n","39 Deoghar\n","40 Dhanbad\n","41 Dumka\n","42 Garhwa\n","43 Giridih\n","Saving result_1.csv with 80,493,525 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: 248f4208-704a-4ba9-85d1-9bead9490c53\n","44 Godda\n","45 Gumla\n","46 Hazaribagh\n","47 Jamtara\n","48 Khunti\n","49 Kodarma\n","50 Latehar\n","51 Lohardaga\n","52 Pakur\n","53 Palamu\n","54 Pashchimi Singhbhum\n","55 Purbi Singhbhum\n","56 Ramgarh\n","57 Ranchi\n","58 Sahibganj\n","59 Saraikela-kharsawan\n","60 Simdega\n","61 Anuppur\n","62 Balaghat\n","63 Dindori\n","64 Jabalpur\n","65 Katni\n","66 Mandla\n","67 Rewa\n","68 Satna\n","69 Seoni\n","70 Shahdol\n","71 Sidhi\n","Saving result_2.csv with 80,264,095 rows\n","Upload complete.\n","Ingestion task started: 3cde4a32-1589-462e-b073-f84f97e6cd27\n"]},{"output_type":"error","ename":"OSError","evalue":"[Errno 5] Input/output error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36msave\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 269\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 270\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 271\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36m_save\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 274\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save_header\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 275\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save_body\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 276\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36m_save_body\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 313\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save_chunk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstart_i\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mend_i\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 314\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36m_save_chunk\u001b[0;34m(self, start_i, end_i)\u001b[0m\n\u001b[1;32m 323\u001b[0m \u001b[0mix\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata_index\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mslicer\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_values_for_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_number_format\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 324\u001b[0;31m libwriters.write_csv_rows(\n\u001b[0m\u001b[1;32m 325\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32mwriters.pyx\u001b[0m in \u001b[0;36mpandas._libs.writers.write_csv_rows\u001b[0;34m()\u001b[0m\n","\u001b[0;31mOSError\u001b[0m: [Errno 5] Input/output error","\nDuring handling of the above exception, another exception occurred:\n","\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)","\u001b[0;31mOSError\u001b[0m: [Errno 5] Input/output error","\nDuring handling of the above exception, another exception occurred:\n","\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_2765/2038472130.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[0;31m# Append to current output file\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 34\u001b[0;31m df_chunk.to_csv(drive_path, mode='a',\n\u001b[0m\u001b[1;32m 35\u001b[0m header=first_write, index=False)\n\u001b[1;32m 36\u001b[0m \u001b[0mfirst_write\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/util/_decorators.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 331\u001b[0m \u001b[0mstacklevel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfind_stack_level\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 332\u001b[0m )\n\u001b[0;32m--> 333\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 334\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 335\u001b[0m \u001b[0;31m# error: \"Callable[[VarArg(Any), KwArg(Any)], Any]\" has no\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/generic.py\u001b[0m in \u001b[0;36mto_csv\u001b[0;34m(self, path_or_buf, sep, na_rep, float_format, columns, header, index, index_label, mode, encoding, compression, quoting, quotechar, lineterminator, chunksize, date_format, doublequote, escapechar, decimal, errors, storage_options)\u001b[0m\n\u001b[1;32m 3965\u001b[0m )\n\u001b[1;32m 3966\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3967\u001b[0;31m return DataFrameRenderer(formatter).to_csv(\n\u001b[0m\u001b[1;32m 3968\u001b[0m \u001b[0mpath_or_buf\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3969\u001b[0m \u001b[0mlineterminator\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mlineterminator\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/format.py\u001b[0m in \u001b[0;36mto_csv\u001b[0;34m(self, path_or_buf, encoding, sep, columns, index_label, mode, compression, quoting, quotechar, lineterminator, chunksize, date_format, doublequote, escapechar, errors, storage_options)\u001b[0m\n\u001b[1;32m 1012\u001b[0m \u001b[0mformatter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfmt\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1013\u001b[0m )\n\u001b[0;32m-> 1014\u001b[0;31m \u001b[0mcsv_formatter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msave\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1015\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1016\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcreated_buffer\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36msave\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 249\u001b[0m \"\"\"\n\u001b[1;32m 250\u001b[0m \u001b[0;31m# apply compression and byte/text conversion\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 251\u001b[0;31m with get_handle(\n\u001b[0m\u001b[1;32m 252\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 253\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmode\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/common.py\u001b[0m in \u001b[0;36m__exit__\u001b[0;34m(self, exc_type, exc_value, traceback)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mTracebackType\u001b[0m \u001b[0;34m|\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m ) -> None:\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/common.py\u001b[0m in \u001b[0;36mclose\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 142\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreated_handles\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhandle\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 143\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mhandle\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreated_handles\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 144\u001b[0;31m \u001b[0mhandle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 145\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreated_handles\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_wrapped\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mOSError\u001b[0m: [Errno 5] Input/output error"]}],"source":["best_month = best_month_dict[agroclimatic_zone]\n","chunk_size=500_000\n","max_rows=80_000_000\n","\n","for year in years:\n"," print(year)\n"," result_num = 0\n"," dst_num = 0\n"," row_counter = 0\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n"," os.makedirs(path, exist_ok=True)\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # controls header\n","\n"," for district in dist_list:\n"," print(dst_num, district)\n"," dst_num += 1\n"," try:\n"," reader = pd.read_csv(\n"," f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc.csv',\n"," chunksize=chunk_size\n"," )\n"," except:\n"," continue\n","\n"," for df_chunk in reader:\n"," if df_chunk.shape[0] > 0:\n"," # print(df_chunk)\n"," # Add \"cc\" column from best month\n"," df_chunk['cc'] = df_chunk[best_month]\n","\n"," # Append to current output file\n"," df_chunk.to_csv(drive_path, mode='a',\n"," header=first_write, index=False)\n"," first_write = False\n","\n"," row_counter += len(df_chunk)\n","\n"," # Split file if max_rows exceeded\n"," if row_counter >= max_rows:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n","\n"," file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n","\n"," # reset counters\n"," result_num += 1\n"," row_counter = 0\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # new file needs header\n","\n"," # flush leftovers\n"," if row_counter > 0:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n"," file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"RYYNSioqd0T9"},"outputs":[],"source":["# best_month = best_month_dict[agroclimatic_zone]\n","# for year in years:\n","# print(year)\n","# df_con = pd.DataFrame()\n","# df_len = 0\n","# result_num = 0\n","# dst_num = 0\n","# for district in dist_list:\n","# print(dst_num, district)\n","# dst_num += 1\n","# try:\n","# df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc.csv')\n","# except:\n","# continue\n","# df_len += len(df)\n","# df_con = pd.concat([df_con, df])\n","# del(df)\n","# print(df_len)\n","\n","\n","# if df_len > 80000000:\n","# print(f'Saving result_{result_num}.csv')\n","# df_con['cc'] = df_con[best_month]\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/result_{result_num}.csv', index=False)\n","# file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# upload_file_to_gcs(drive_path, file_name)\n","# export_to_gee(file_name)\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()\n","\n","# if (len(df_con) > 0):\n","# print(f'Saving result_{result_num}.csv')\n","# df_con['cc'] = df_con[best_month]\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(drive_path, index=False)\n","\n","# file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# upload_file_to_gcs(drive_path, file_name)\n","# export_to_gee(file_name)\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":327},"executionInfo":{"elapsed":168191,"status":"ok","timestamp":1762767949388,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"7nY3jBPsNG6_","outputId":"23307a31-72c0-48fe-8962-7cce2c8cf161"},"outputs":[{"data":{"text/html":["\n"," \n"," "],"text/plain":[""]},"metadata":{},"output_type":"display_data"},{"name":"stdout","output_type":"stream","text":[".geo object\n","cc_1 int64\n","cc_2 int64\n","cc_3 int64\n","cc_4 int64\n","cc_5 int64\n","cc_6 int64\n","cc_7 int64\n","cc_8 int64\n","cc_9 int64\n","cc_10 int64\n","cc_11 int64\n","cc_12 int64\n","cc int64\n","dtype: object\n","Upload complete.\n","Ingestion task started: 2a4f86c7-03b8-4605-916d-fb63feae926e\n"]}],"source":["# year = '2016'\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n","# for result_num in range(1):\n","# drive_path = f\"{path}/result_{result_num}.csv\"\n","# reader = pd.read_csv(drive_path, chunksize=100)\n","# for df_chunk in reader:\n","# print(df_chunk.dtypes)\n","# break\n","# # upload + export\n","# upload_file_to_gcs(\n","# drive_path,\n","# f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )\n","# export_to_gee(\n","# f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )"]},{"cell_type":"markdown","metadata":{"id":"sfozGeDdiQcC"},"source":["# Combine CSVs CH"]},{"cell_type":"code","execution_count":10,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":2344,"status":"ok","timestamp":1775284632488,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"bk7terDQiQ_y","outputId":"8ab1da5e-de1e-4284-e3a6-a81a26266423"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":3,"status":"ok","timestamp":1775284632493,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"eQgVTZCiiYa0","outputId":"83e3c7a2-8256-4c9b-b076-9f3ce2cd84f6"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)"]},{"cell_type":"code","execution_count":12,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":23,"status":"ok","timestamp":1775284633301,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"QxHpT4hhibUG","outputId":"03c65e28-8dfa-4dfc-eec2-b1eb3268f2b2"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]"]},{"cell_type":"code","execution_count":13,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":55},"executionInfo":{"elapsed":15,"status":"ok","timestamp":1775284634739,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"PpR3DJEbifWe","outputId":"9a95daed-af5c-4426-c099-363db40b89a3"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["['Araria', 'Arwal', 'AurangabadB', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar', 'Darbhanga', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur', 'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger', 'Muzaffarpur', 'Nalanda', 'Nawada', 'Pashchim Champaran', 'Patna', 'Purba Champaran', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur', 'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali', 'Godda', 'Sahibganj', 'Ambedkar Nagar', 'Azamgarh', 'Bahraich', 'Ballia', 'Balrampur', 'Basti', 'Chandauli', 'Deoria', 'Faizabad', 'Ghazipur', 'Gonda', 'Gorakhpur', 'Jaunpur', 'Kushinagar', 'Maharajganj', 'Mau', 'Mirzapur', 'Sant Kabir Nagar', 'Sant Ravi Das Nagar', 'Shravasti', 'Siddharth Nagar', 'Sonbhadra', 'Varanasi']\n"]}],"source":["dist_list = []\n","i = 0\n","for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," # print(i, district, zones)\n"," dist_list.append(district)\n"," i += 1\n","# dist_list = ['Darjiling']\n","print(dist_list)"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775284642451,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"orS9dnFnmVEe","outputId":"9237665f-2f26-4e37-e4af-185bbc58fbbb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def classify_chunk(df_chunk):\n"," \"\"\"Add ch_class column based on rh50/rh75/rh98 logic.\"\"\"\n"," conditions = [\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 0),\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 1),\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 1) & (df_chunk['rh98_class'] == 0),\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 1) & (df_chunk['rh98_class'] == 1),\n"," (df_chunk['rh50_class'] == 1) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 0),\n"," (df_chunk['rh50_class'] == 1) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 1)\n"," ]\n"," choices = [0, 0, 1, 2, 1, 2]\n","\n"," df_chunk['ch_class'] = np.select(conditions, choices, default=3)\n"," return df_chunk"]},{"cell_type":"code","execution_count":15,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"PbwYc1iLmXA5","executionInfo":{"status":"error","timestamp":1775286506270,"user_tz":-330,"elapsed":1861463,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"70821778-37d8-4b6a-c59a-304c8f89d767"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["2016\n","0 Araria\n","1 Arwal\n","2 AurangabadB\n","3 Banka\n","4 Begusarai\n","5 Bhagalpur\n","6 Bhojpur\n","7 Buxar\n","8 Darbhanga\n","9 Gaya\n","10 Gopalganj\n","11 Jamui\n","12 Jehanabad\n","13 Kaimur\n","14 Katihar\n","15 Khagaria\n","16 Kishanganj\n","17 Lakhisarai\n","18 Madhepura\n","19 Madhubani\n","20 Munger\n","21 Muzaffarpur\n","22 Nalanda\n","23 Nawada\n","24 Pashchim Champaran\n","25 Patna\n","26 Purba Champaran\n","27 Purnia\n","28 Rohtas\n","29 Saharsa\n","30 Samastipur\n","31 Saran\n","32 Sheikhpura\n","33 Sheohar\n","34 Sitamarhi\n","35 Siwan\n","36 Supaul\n","37 Vaishali\n","38 Godda\n","39 Sahibganj\n","40 Ambedkar Nagar\n","41 Azamgarh\n","42 Bahraich\n","43 Ballia\n","44 Balrampur\n","45 Basti\n","46 Chandauli\n","47 Deoria\n","48 Faizabad\n","49 Ghazipur\n","50 Gonda\n","51 Gorakhpur\n","52 Jaunpur\n","53 Kushinagar\n","54 Maharajganj\n","55 Mau\n","56 Mirzapur\n","57 Sant Kabir Nagar\n","58 Sant Ravi Das Nagar\n","59 Shravasti\n","60 Siddharth Nagar\n","61 Sonbhadra\n","62 Varanasi\n","Saving result_0.csv with 68,660,369 rows\n"]},{"output_type":"error","ename":"KeyboardInterrupt","evalue":"","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_4886/2152513440.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 56\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mrow_counter\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 57\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'Saving result_{result_num}.csv with {row_counter:,} rows'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 58\u001b[0;31m upload_file_to_gcs(\n\u001b[0m\u001b[1;32m 59\u001b[0m \u001b[0mdrive_path\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 60\u001b[0m \u001b[0;34mf\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipykernel_4886/1786300172.py\u001b[0m in \u001b[0;36mupload_file_to_gcs\u001b[0;34m(local_file_path, file_name)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mupload_file_to_gcs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlocal_file_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfile_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mblob\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mbucket\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mblob\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'nrm_tree_health/compiled_results/{file_name}.csv'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# GCS path\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mblob\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupload_from_filename\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlocal_file_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Upload complete.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36mupload_from_filename\u001b[0;34m(self, filename, content_type, client, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 3196\u001b[0m \"\"\"\n\u001b[1;32m 3197\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mcreate_trace_span\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Storage.Blob.uploadFromFilename\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3198\u001b[0;31m self._handle_filename_and_upload(\n\u001b[0m\u001b[1;32m 3199\u001b[0m \u001b[0mfilename\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3200\u001b[0m \u001b[0mcontent_type\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcontent_type\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_handle_filename_and_upload\u001b[0;34m(self, filename, content_type, *args, **kwargs)\u001b[0m\n\u001b[1;32m 3046\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"rb\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mfile_obj\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3047\u001b[0m \u001b[0mtotal_bytes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfstat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_obj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfileno\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mst_size\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3048\u001b[0;31m self._prep_and_do_upload(\n\u001b[0m\u001b[1;32m 3049\u001b[0m \u001b[0mfile_obj\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3050\u001b[0m \u001b[0mcontent_type\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcontent_type\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_prep_and_do_upload\u001b[0;34m(self, file_obj, rewind, size, content_type, client, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, command, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 2833\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2834\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2835\u001b[0;31m created_json = self._do_upload(\n\u001b[0m\u001b[1;32m 2836\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2837\u001b[0m \u001b[0mfile_obj\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_do_upload\u001b[0;34m(self, client, stream, content_type, size, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, command, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 2644\u001b[0m )\n\u001b[1;32m 2645\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2646\u001b[0;31m response = self._do_resumable_upload(\n\u001b[0m\u001b[1;32m 2647\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2648\u001b[0m \u001b[0mstream\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_do_resumable_upload\u001b[0;34m(self, client, stream, content_type, size, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, command, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 2461\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mupload\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfinished\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2462\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2463\u001b[0;31m \u001b[0mresponse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mupload\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransmit_next_chunk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransport\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2464\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mDataCorruption\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2465\u001b[0m \u001b[0;31m# Attempt to delete the corrupted object.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/_media/requests/upload.py\u001b[0m in \u001b[0;36mtransmit_next_chunk\u001b[0;34m(self, transport, timeout)\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 528\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 529\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_request_helpers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_and_retry\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mretriable_request\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_retry_strategy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 530\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 531\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrecover\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtransport\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/_media/requests/_request_helpers.py\u001b[0m in \u001b[0;36mwait_and_retry\u001b[0;34m(func, retry_strategy)\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mretry_strategy\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0mfunc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mretry_strategy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 107\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/api_core/retry/retry_unary.py\u001b[0m in \u001b[0;36mretry_wrapped_func\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 292\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_initial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_maximum\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmultiplier\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_multiplier\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 293\u001b[0m )\n\u001b[0;32m--> 294\u001b[0;31m return retry_target(\n\u001b[0m\u001b[1;32m 295\u001b[0m \u001b[0mtarget\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 296\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_predicate\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/api_core/retry/retry_unary.py\u001b[0m in \u001b[0;36mretry_target\u001b[0;34m(target, predicate, sleep_generator, timeout, on_error, exception_factory, **kwargs)\u001b[0m\n\u001b[1;32m 145\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 147\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtarget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 148\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0minspect\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misawaitable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 149\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwarn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_ASYNC_RETRY_WARNING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/_media/requests/upload.py\u001b[0m in \u001b[0;36mretriable_request\u001b[0;34m()\u001b[0m\n\u001b[1;32m 519\u001b[0m \u001b[0;31m# Wrap the request business logic in a function to be retried.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 520\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mretriable_request\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 521\u001b[0;31m result = transport.request(\n\u001b[0m\u001b[1;32m 522\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mpayload\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 523\u001b[0m )\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/auth/transport/requests.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, data, headers, max_allowed_time, timeout, **kwargs)\u001b[0m\n\u001b[1;32m 541\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mTimeoutGuard\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mremaining_time\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mguard\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 542\u001b[0m \u001b[0m_helpers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrequest_log\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_LOGGER\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 543\u001b[0;31m response = super(AuthorizedSession, self).request(\n\u001b[0m\u001b[1;32m 544\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 545\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/requests/sessions.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[1;32m 587\u001b[0m }\n\u001b[1;32m 588\u001b[0m \u001b[0msend_kwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msettings\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 589\u001b[0;31m \u001b[0mresp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprep\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0msend_kwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 590\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 591\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/requests/sessions.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, **kwargs)\u001b[0m\n\u001b[1;32m 701\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 702\u001b[0m \u001b[0;31m# Send the request\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 703\u001b[0;31m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0madapter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 704\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 705\u001b[0m \u001b[0;31m# Total elapsed time of the request (approximately)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/requests/adapters.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 665\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 666\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 667\u001b[0;31m resp = conn.urlopen(\n\u001b[0m\u001b[1;32m 668\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 669\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36murlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[0m\n\u001b[1;32m 785\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 786\u001b[0m \u001b[0;31m# Make the request on the HTTPConnection object\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 787\u001b[0;31m response = self._make_request(\n\u001b[0m\u001b[1;32m 788\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 789\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36m_make_request\u001b[0;34m(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)\u001b[0m\n\u001b[1;32m 491\u001b[0m \u001b[0;31m# urllib3.request. It also calls makefile (recv) on the socket.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 492\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 493\u001b[0;31m conn.request(\n\u001b[0m\u001b[1;32m 494\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/urllib3/connection.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)\u001b[0m\n\u001b[1;32m 506\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mb\"%x\\r\\n%b\\r\\n\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchunk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mchunk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 507\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 508\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchunk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 509\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[0;31m# Regardless of whether we have a body or not, if we're in\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/http/client.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 1075\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maudit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"http.client.send\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1076\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1077\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msendall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1078\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1079\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcollections\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mabc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mIterable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/ssl.py\u001b[0m in \u001b[0;36msendall\u001b[0;34m(self, data, flags)\u001b[0m\n\u001b[1;32m 1208\u001b[0m \u001b[0mamount\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbyte_view\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1209\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0mamount\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1210\u001b[0;31m \u001b[0mv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbyte_view\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mcount\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1211\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1212\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/ssl.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, data, flags)\u001b[0m\n\u001b[1;32m 1177\u001b[0m \u001b[0;34m\"non-zero flags not allowed in calls to send() on %s\"\u001b[0m \u001b[0;34m%\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1178\u001b[0m self.__class__)\n\u001b[0;32m-> 1179\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sslobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1180\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1181\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "]}],"source":["chunk_size=500_000\n","max_rows=80_000_000\n","for year in years:\n"," print(year)\n"," result_num = 0\n"," dst_num = 0\n"," row_counter = 0\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n"," os.makedirs(path, exist_ok=True)\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # controls header writing\n","\n"," for district in dist_list:\n"," print(dst_num, district)\n"," dst_num += 1\n"," try:\n"," reader = pd.read_csv(\n"," f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm.csv',\n"," chunksize=chunk_size\n"," )\n"," except:\n"," print(\"Errorrrrrrrr\")\n"," continue\n","\n"," for df_chunk in reader:\n"," df_chunk = classify_chunk(df_chunk)\n","\n"," # Append to current result file\n"," df_chunk.to_csv(drive_path, mode='a',\n"," header=first_write, index=False)\n"," first_write = False\n","\n"," row_counter += len(df_chunk)\n","\n"," # If exceeds limit → finalize file and start new one\n"," if row_counter >= max_rows:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n","\n"," # upload + export\n"," upload_file_to_gcs(\n"," drive_path,\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n"," export_to_gee(\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n","\n"," # reset counters\n"," result_num += 1\n"," row_counter = 0\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # new file needs header\n","\n"," # Flush leftover rows at end of loop\n"," if row_counter > 0:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n"," upload_file_to_gcs(\n"," drive_path,\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n"," export_to_gee(\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"SKueMXZDj1NP"},"outputs":[],"source":["# for year in years:\n","# print(year)\n","# df_con = pd.DataFrame()\n","# df_len = 0\n","# result_num = 0\n","# dst_num = 0\n","# for district in dist_list:\n","# print(dst_num, district)\n","# dst_num += 1\n","# try:\n","# df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm.csv')\n","# except:\n","# continue\n","# df_len += len(df)\n","# df_con = pd.concat([df_con, df])\n","# del(df)\n","# print(df_len)\n","\n","# if df_len > 80000000:\n","# print(f'Saving result_{result_num}.csv')\n","\n","# conditions = [\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1)\n","# ]\n","\n","# choices = [0, 0, 1, 2, 1, 2]\n","\n","# df_con['ch_class'] = np.select(conditions, choices, default=3)\n","\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(drive_path, index=False)\n","\n","# upload_file_to_gcs(drive_path, f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","# export_to_gee(f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()\n","\n","# if (len(df_con) > 0):\n","# print(f'Saving result_{result_num}.csv')\n","\n","# conditions = [\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1)\n","# ]\n","\n","# choices = [0, 0, 1, 2, 1, 2]\n","\n","# df_con['ch_class'] = np.select(conditions, choices, default=3)\n","\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(drive_path, index=False)\n","\n","# upload_file_to_gcs(drive_path, f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","# export_to_gee(f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"xl-Lt3vZRfsq"},"outputs":[],"source":["\n","# year = '2023'\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n","# for result_num in range(6):\n","# drive_path = f\"{path}/result_{result_num}.csv\"\n","# # upload + export\n","# upload_file_to_gcs(\n","# drive_path,\n","# f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )\n","# export_to_gee(\n","# f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )"]}],"metadata":{"colab":{"collapsed_sections":["_e3eXwraczpB"],"provenance":[{"file_id":"11PyrzNVnNfHa7YM1M37Eg-Ov3L91sSrM","timestamp":1758134547309}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb b/utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb new file mode 100644 index 00000000..b7a02243 --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":10466,"status":"ok","timestamp":1775209278781,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"yewisZzO_XBA"},"outputs":[],"source":["import ee\n","import pandas as pd\n","import json\n","from glob import glob\n","import geemap\n","import pyproj\n","from geopandas import geopandas as gpd\n","from shapely.geometry import Point\n","from copy import deepcopy\n","import numpy as np\n","import os\n","import ast\n","from google.cloud import storage"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":21238,"status":"ok","timestamp":1775209302013,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"XOwz3mnBYzzf","outputId":"1557f773-62e2-4e8e-a6be-a6c619c3f2b6"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":583,"status":"ok","timestamp":1775209354321,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"BYyrrpjo0be9","outputId":"161c88fb-cbea-40cd-d20b-09930cf31c6d"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["ee.Authenticate()\n","ee_project = \"corestack1-dev-alpha\"\n","ee.Initialize(project=\"core-stack-dev-2\")#\"corestack1-dev-alpha\")"]},{"cell_type":"code","execution_count":4,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":197,"status":"ok","timestamp":1775209320541,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"xSSojnkB0Twj","outputId":"d81f8d77-f546-4e6e-c895-c55513dc4822"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["client = storage.Client()\n","bucket = client.get_bucket('core_stack')"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775209320551,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"RT2Qqbs-Y4Qv","outputId":"cc559b70-3004-4507-a360-7dba8797c768"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775209321213,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"OySilp5oft-j","outputId":"ed9bffbe-3b93-4cdd-fb33-62526c03a36c"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":11,"status":"ok","timestamp":1775209323514,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"giCoCm_0fylu","outputId":"782b295f-85ce-497f-8cf7-5fa4bd02ae7d"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def upload_file_to_gcs(local_file_path, file_name):\n"," blob = bucket.blob(f'nrm_tree_health/correction_compiled_results/{file_name}.csv') # GCS path\n"," blob.upload_from_filename(local_file_path)\n","\n"," print(\"Upload complete.\")\n","\n","def export_to_gee(file_name):\n"," # CSV GCS path\n"," gcs_path = f'gs://core_stack/nrm_tree_health/correction_compiled_results/{file_name}.csv'\n","\n"," # Create task ID and run table ingestion\n"," task_id = ee.data.newTaskId()[0]\n"," asset_id = f'projects/{ee_project}/assets/tree_characteristics/{file_name}'\n","\n"," manifest = {\n"," 'id': asset_id,\n"," 'sources': [\n"," {\n"," 'primaryPath': gcs_path,\n"," 'additionalPaths': []\n"," }\n"," ]\n"," }\n","\n"," ee.data.startTableIngestion(task_id, manifest)\n"," print(\"Ingestion task started:\", task_id)"]},{"cell_type":"markdown","metadata":{"id":"YGk1NbQIY_9w"},"source":["Merge Correction CSVs"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":43,"status":"ok","timestamp":1775209358495,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qlIS9q3xZAzR","outputId":"724f3d95-d00a-4f55-c744-97b764af7bd2"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["acz_list = [\n"," # 'Western Himalayan Region',\n"," 'Eastern Himalayan Region',\n"," # 'Lower Gangetic Plain Region',\n"," # 'Middle Gangetic Plain Region',\n"," # 'Upper Gangetic Plain Region',\n"," # 'Trans Gangetic Plain Region',\n"," # 'Eastern Plateau & Hills Region',\n"," # 'Central Plateau & Hills Region',\n"," # 'Western Plateau and Hills Region',\n"," # 'Southern Plateau and Hills Region',\n"," # 'East Coast Plains & Hills Region'\n"," ]\n","\n"]},{"cell_type":"code","execution_count":10,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":10,"status":"ok","timestamp":1775209362961,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ae3Qg-PHZFWP","outputId":"2a0f4a86-0f2d-4fe3-f889-ed971bfdf4d4"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["years = ['2017', '2018', '2019', '2020', '2021', '2022', '2023']"]},{"cell_type":"markdown","metadata":{"id":"Ah69USIF_hSV"},"source":["# CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":363},"collapsed":true,"id":"Gi4Bw7-LZJHw","outputId":"3af2dd77-cd05-4aaa-a89c-0bd7ffb5568a"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Eastern Plateau & Hills Region\n","2020\n","length(dist_list): 119\n","0 East Godavari\n","1 Srikakulam\n","2 Visakhapatnam\n","3 Vizianagaram\n","4 AurangabadB\n","5 Banka\n","6 Gaya\n","7 Jamui\n","8 Nawada\n","9 Rohtas\n","10 Baloda Bazar\n","11 Balod\n","12 BalrampurC\n","13 Bastar\n","14 Bemetara\n","15 BijapurC\n"]}],"source":["# CCD\n","\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n"," for year in years:\n"," print(year)\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n"," dist_list = list(df['Name'])\n"," print(f'length(dist_list): {len(dist_list)}')\n","\n"," i = 0\n"," merged_df = pd.DataFrame()\n"," for district in dist_list:\n"," print(i, district)\n"," i += 1\n","\n"," try:\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc_corrections.csv')\n"," except:\n"," continue\n","\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n","\n"," print(f'length(merged_df): {len(merged_df)}')\n","\n"," cols = merged_df.columns\n"," for i in range(1, len(cols)):\n"," merged_df[cols[i]] = merged_df[cols[i]].astype('Int64')\n"," drive_path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/corrections_{year}.csv'\n"," merged_df.to_csv(drive_path, index=False)\n","\n"," file_name = f\"corrections_ccd_{year}_result_0_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)"]},{"cell_type":"markdown","metadata":{"id":"J7kg35BL_rWU"},"source":["# CH"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"umuwxquJLI1q","executionInfo":{"status":"ok","timestamp":1775217779330,"user_tz":-330,"elapsed":8414523,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"0605ef8a-3f71-4edc-a097-442968229a59"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Eastern Himalayan Region\n","2017\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 2979532\n","1 Changlang\n","length(merged_df): 3307447\n","2 Dibang Valley\n","length(merged_df): 4194324\n","3 East Kameng\n","length(merged_df): 4352964\n","4 East Siang\n","length(merged_df): 4458119\n","5 Kurung Kumey\n","6 Lohit\n","length(merged_df): 5274731\n","7 Longding\n","length(merged_df): 5300151\n","8 Lower Dibang Valley\n","length(merged_df): 6630346\n","9 Lower Subansiri\n","10 Namsai\n","length(merged_df): 6654526\n","11 Papum Pare\n","12 Tawang\n","13 Tirap\n","length(merged_df): 6808687\n","14 Upper Siang\n","15 Upper Subansiri\n","16 West Kameng\n","length(merged_df): 7216607\n","17 West Siang\n","18 Baksa\n","length(merged_df): 7300480\n","19 Barpeta\n","length(merged_df): 7319087\n","20 Bongaigaon\n","length(merged_df): 7335411\n","21 Cachar\n","length(merged_df): 7372150\n","22 Chirang\n","length(merged_df): 7420786\n","23 Darrang\n","length(merged_df): 7454804\n","24 Dhemaji\n","length(merged_df): 7457716\n","25 Dhubri\n","length(merged_df): 7488291\n","26 Dibrugarh\n","length(merged_df): 7520020\n","27 Dima Hasao\n","length(merged_df): 7968962\n","28 Goalpara\n","length(merged_df): 7993104\n","29 Golaghat\n","length(merged_df): 8007504\n","30 Hailakandi\n","length(merged_df): 8013767\n","31 Jorhat\n","length(merged_df): 8023171\n","32 Kamrup Metropolitan\n","length(merged_df): 8124708\n","33 Kamrup\n","length(merged_df): 8236975\n","34 Karbi Anglong\n","length(merged_df): 10643741\n","35 Karimganj\n","length(merged_df): 10760499\n","36 Kokrajhar\n","length(merged_df): 10925086\n","37 Lakhimpur\n","length(merged_df): 10933650\n","38 Morigaon\n","length(merged_df): 11075910\n","39 Nagaon\n","length(merged_df): 11178452\n","40 Nalbari\n","length(merged_df): 11196382\n","41 Sivasagar\n","length(merged_df): 11234982\n","42 Sonitpur\n","length(merged_df): 11367250\n","43 Tinsukia\n","length(merged_df): 11619699\n","44 Udalguri\n","length(merged_df): 11682623\n","45 Bishnupur\n","46 Chandel\n","47 Churachandpur\n","48 Imphal East\n","49 Imphal West\n","50 Senapati\n","length(merged_df): 12040830\n","51 Tamenglong\n","length(merged_df): 12293929\n","52 Thoubal\n","53 Ukhrul\n","length(merged_df): 12351297\n","54 East Garo Hills\n","length(merged_df): 12481308\n","55 East Khasi Hills\n","length(merged_df): 13191302\n","56 Jaintia Hills\n","length(merged_df): 14527081\n","57 North Garo Hills\n","length(merged_df): 14543576\n","58 Ri Bhoi\n","length(merged_df): 15479001\n","59 South Garo Hills\n","length(merged_df): 15711222\n","60 South West Garo Hills\n","length(merged_df): 15721187\n","61 South West Khasi Hills\n","length(merged_df): 15973372\n","62 West Garo Hills\n","length(merged_df): 16109333\n","63 West Khasi Hills\n","length(merged_df): 16882649\n","64 Aizawl\n","length(merged_df): 16883641\n","65 Champhai\n","length(merged_df): 16962300\n","66 Kolasib\n","67 Lawangtlai\n","length(merged_df): 17607846\n","68 Lunglei\n","length(merged_df): 17777978\n","69 Mamit\n","length(merged_df): 17816098\n","70 Saiha\n","length(merged_df): 18337927\n","71 Serchhip\n","length(merged_df): 18456898\n","72 Dimapur\n","length(merged_df): 18653296\n","73 Kiphire\n","length(merged_df): 18828710\n","74 Kohima\n","length(merged_df): 19026827\n","75 Longleng\n","76 Mokokchung\n","77 Mon\n","length(merged_df): 19026889\n","78 Peren\n","length(merged_df): 19635289\n","79 Phek\n","length(merged_df): 19663020\n","80 Tuensang\n","length(merged_df): 19889386\n","81 Wokha\n","length(merged_df): 20040494\n","82 Zunheboto\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 20042784\n","86 West Sikkim\n","length(merged_df): 20042906\n","87 Dhalai\n","length(merged_df): 20349925\n","88 Gomati\n","length(merged_df): 20559679\n","89 Khowai\n","length(merged_df): 20770547\n","90 North Tripura\n","length(merged_df): 20915052\n","91 Sipahijala\n","length(merged_df): 21005581\n","92 South Tripura\n","length(merged_df): 21023307\n","93 Unokoti\n","length(merged_df): 21069492\n","94 West Tripura\n","length(merged_df): 21162616\n","95 Alipurduar\n","length(merged_df): 21333417\n","96 Darjiling\n","length(merged_df): 21731155\n","97 Jalpaiguri\n","length(merged_df): 21848690\n","98 Koch Bihar\n","length(merged_df): 21905819\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: a50d3798-10da-4004-a2fb-6e0e6aa129c2\n","2018\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 478344\n","1 Changlang\n","length(merged_df): 2444511\n","2 Dibang Valley\n","length(merged_df): 3836445\n","3 East Kameng\n","length(merged_df): 5122433\n","4 East Siang\n","length(merged_df): 5540789\n","5 Kurung Kumey\n","length(merged_df): 6210350\n","6 Lohit\n","length(merged_df): 7154987\n","7 Longding\n","length(merged_df): 7518784\n","8 Lower Dibang Valley\n","length(merged_df): 9181092\n","9 Lower Subansiri\n","length(merged_df): 9544762\n","10 Namsai\n","length(merged_df): 9599147\n","11 Papum Pare\n","length(merged_df): 10071058\n","12 Tawang\n","13 Tirap\n","length(merged_df): 10651507\n","14 Upper Siang\n","15 Upper Subansiri\n","length(merged_df): 10996049\n","16 West Kameng\n","length(merged_df): 11911902\n","17 West Siang\n","length(merged_df): 12567368\n","18 Baksa\n","length(merged_df): 12741540\n","19 Barpeta\n","length(merged_df): 12814699\n","20 Bongaigaon\n","length(merged_df): 12865104\n","21 Cachar\n","length(merged_df): 13634666\n","22 Chirang\n","length(merged_df): 13737843\n","23 Darrang\n","length(merged_df): 13803079\n","24 Dhemaji\n","length(merged_df): 14005416\n","25 Dhubri\n","length(merged_df): 14086546\n","26 Dibrugarh\n","length(merged_df): 14327414\n","27 Dima Hasao\n","length(merged_df): 15403060\n","28 Goalpara\n","length(merged_df): 15456008\n","29 Golaghat\n","length(merged_df): 15577333\n","30 Hailakandi\n","length(merged_df): 15668824\n","31 Jorhat\n","length(merged_df): 15803737\n","32 Kamrup Metropolitan\n","length(merged_df): 15886696\n","33 Kamrup\n","length(merged_df): 16170410\n","34 Karbi Anglong\n","length(merged_df): 17867132\n","35 Karimganj\n","length(merged_df): 18041501\n","36 Kokrajhar\n","length(merged_df): 18451796\n","37 Lakhimpur\n","length(merged_df): 18617844\n","38 Morigaon\n","length(merged_df): 18681752\n","39 Nagaon\n","length(merged_df): 18889057\n","40 Nalbari\n","length(merged_df): 18936490\n","41 Sivasagar\n","length(merged_df): 19070646\n","42 Sonitpur\n","length(merged_df): 19487051\n","43 Tinsukia\n","length(merged_df): 20132264\n","44 Udalguri\n","length(merged_df): 20234159\n","45 Bishnupur\n","length(merged_df): 20256633\n","46 Chandel\n","length(merged_df): 21456762\n","47 Churachandpur\n","length(merged_df): 22912733\n","48 Imphal East\n","length(merged_df): 22977450\n","49 Imphal West\n","length(merged_df): 23010433\n","50 Senapati\n","length(merged_df): 24073080\n","51 Tamenglong\n","length(merged_df): 25665819\n","52 Thoubal\n","length(merged_df): 25718095\n","53 Ukhrul\n","length(merged_df): 27128826\n","54 East Garo Hills\n","length(merged_df): 27513780\n","55 East Khasi Hills\n","length(merged_df): 28529934\n","56 Jaintia Hills\n","length(merged_df): 29485284\n","57 North Garo Hills\n","length(merged_df): 29544664\n","58 Ri Bhoi\n","length(merged_df): 30143241\n","59 South Garo Hills\n","length(merged_df): 30579916\n","60 South West Garo Hills\n","length(merged_df): 30602928\n","61 South West Khasi Hills\n","length(merged_df): 31055221\n","62 West Garo Hills\n","length(merged_df): 31366797\n","63 West Khasi Hills\n","length(merged_df): 32639956\n","64 Aizawl\n","length(merged_df): 33683017\n","65 Champhai\n","length(merged_df): 34876756\n","66 Kolasib\n","length(merged_df): 35235014\n","67 Lawangtlai\n","length(merged_df): 36017069\n","68 Lunglei\n","length(merged_df): 37422177\n","69 Mamit\n","length(merged_df): 38303422\n","70 Saiha\n","length(merged_df): 39353050\n","71 Serchhip\n","length(merged_df): 39818787\n","72 Dimapur\n","length(merged_df): 39983662\n","73 Kiphire\n","length(merged_df): 40462734\n","74 Kohima\n","length(merged_df): 40960637\n","75 Longleng\n","length(merged_df): 41132686\n","76 Mokokchung\n","length(merged_df): 41676302\n","77 Mon\n","length(merged_df): 42316638\n","78 Peren\n","length(merged_df): 43022268\n","79 Phek\n","length(merged_df): 43711636\n","80 Tuensang\n","length(merged_df): 44510144\n","81 Wokha\n","length(merged_df): 45009024\n","82 Zunheboto\n","length(merged_df): 45589344\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 45594992\n","86 West Sikkim\n","length(merged_df): 45595116\n","87 Dhalai\n","length(merged_df): 46143129\n","88 Gomati\n","length(merged_df): 46453951\n","89 Khowai\n","length(merged_df): 46721002\n","90 North Tripura\n","length(merged_df): 47019475\n","91 Sipahijala\n","length(merged_df): 47148693\n","92 South Tripura\n","length(merged_df): 47417704\n","93 Unokoti\n","length(merged_df): 47531409\n","94 West Tripura\n","length(merged_df): 47655435\n","95 Alipurduar\n","length(merged_df): 48182964\n","96 Darjiling\n","length(merged_df): 48864414\n","97 Jalpaiguri\n","length(merged_df): 49084292\n","98 Koch Bihar\n","length(merged_df): 49297786\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: ba30440b-954b-43bb-814b-f9240309b01a\n","2019\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 478344\n","1 Changlang\n","length(merged_df): 2444511\n","2 Dibang Valley\n","length(merged_df): 3836445\n","3 East Kameng\n","length(merged_df): 5122433\n","4 East Siang\n","length(merged_df): 5540789\n","5 Kurung Kumey\n","length(merged_df): 6210350\n","6 Lohit\n","length(merged_df): 7154987\n","7 Longding\n","length(merged_df): 7518784\n","8 Lower Dibang Valley\n","length(merged_df): 9181092\n","9 Lower Subansiri\n","length(merged_df): 9544762\n","10 Namsai\n","length(merged_df): 9599147\n","11 Papum Pare\n","length(merged_df): 10071058\n","12 Tawang\n","13 Tirap\n","length(merged_df): 10651507\n","14 Upper Siang\n","15 Upper Subansiri\n","length(merged_df): 10996049\n","16 West Kameng\n","length(merged_df): 11911902\n","17 West Siang\n","length(merged_df): 12567368\n","18 Baksa\n","length(merged_df): 12741540\n","19 Barpeta\n","length(merged_df): 12814699\n","20 Bongaigaon\n","length(merged_df): 12865104\n","21 Cachar\n","length(merged_df): 13634666\n","22 Chirang\n","length(merged_df): 13737843\n","23 Darrang\n","length(merged_df): 13803079\n","24 Dhemaji\n","length(merged_df): 14005416\n","25 Dhubri\n","length(merged_df): 14086546\n","26 Dibrugarh\n","length(merged_df): 14327414\n","27 Dima Hasao\n","length(merged_df): 15403060\n","28 Goalpara\n","length(merged_df): 15456008\n","29 Golaghat\n","length(merged_df): 15577333\n","30 Hailakandi\n","length(merged_df): 15668824\n","31 Jorhat\n","length(merged_df): 15803737\n","32 Kamrup Metropolitan\n","length(merged_df): 15886696\n","33 Kamrup\n","length(merged_df): 16170410\n","34 Karbi Anglong\n","length(merged_df): 17867132\n","35 Karimganj\n","length(merged_df): 18041501\n","36 Kokrajhar\n","length(merged_df): 18451796\n","37 Lakhimpur\n","length(merged_df): 18617844\n","38 Morigaon\n","length(merged_df): 18681752\n","39 Nagaon\n","length(merged_df): 18889057\n","40 Nalbari\n","length(merged_df): 18936490\n","41 Sivasagar\n","length(merged_df): 19070646\n","42 Sonitpur\n","length(merged_df): 19487051\n","43 Tinsukia\n","length(merged_df): 20132264\n","44 Udalguri\n","length(merged_df): 20234159\n","45 Bishnupur\n","length(merged_df): 20256633\n","46 Chandel\n","length(merged_df): 21456762\n","47 Churachandpur\n","length(merged_df): 22912733\n","48 Imphal East\n","length(merged_df): 22977450\n","49 Imphal West\n","length(merged_df): 23010433\n","50 Senapati\n","length(merged_df): 24073080\n","51 Tamenglong\n","length(merged_df): 25665819\n","52 Thoubal\n","length(merged_df): 25718095\n","53 Ukhrul\n","length(merged_df): 27128826\n","54 East Garo Hills\n","length(merged_df): 27513780\n","55 East Khasi Hills\n","length(merged_df): 28529934\n","56 Jaintia Hills\n","length(merged_df): 29485284\n","57 North Garo Hills\n","length(merged_df): 29544664\n","58 Ri Bhoi\n","length(merged_df): 30143241\n","59 South Garo Hills\n","length(merged_df): 30579916\n","60 South West Garo Hills\n","length(merged_df): 30602928\n","61 South West Khasi Hills\n","length(merged_df): 31055221\n","62 West Garo Hills\n","length(merged_df): 31366797\n","63 West Khasi Hills\n","length(merged_df): 32639956\n","64 Aizawl\n","length(merged_df): 33683017\n","65 Champhai\n","length(merged_df): 34876756\n","66 Kolasib\n","length(merged_df): 35235014\n","67 Lawangtlai\n","length(merged_df): 36017069\n","68 Lunglei\n","length(merged_df): 37422177\n","69 Mamit\n","length(merged_df): 38303422\n","70 Saiha\n","length(merged_df): 39353050\n","71 Serchhip\n","length(merged_df): 39818787\n","72 Dimapur\n","length(merged_df): 39983662\n","73 Kiphire\n","length(merged_df): 40462734\n","74 Kohima\n","length(merged_df): 40960637\n","75 Longleng\n","length(merged_df): 41132686\n","76 Mokokchung\n","length(merged_df): 41676302\n","77 Mon\n","length(merged_df): 42316638\n","78 Peren\n","length(merged_df): 43022268\n","79 Phek\n","length(merged_df): 43711636\n","80 Tuensang\n","length(merged_df): 44510144\n","81 Wokha\n","length(merged_df): 45009024\n","82 Zunheboto\n","length(merged_df): 45589344\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 45594992\n","86 West Sikkim\n","length(merged_df): 45595116\n","87 Dhalai\n","length(merged_df): 46143129\n","88 Gomati\n","length(merged_df): 46453951\n","89 Khowai\n","length(merged_df): 46721002\n","90 North Tripura\n","length(merged_df): 47019475\n","91 Sipahijala\n","length(merged_df): 47148693\n","92 South Tripura\n","length(merged_df): 47417704\n","93 Unokoti\n","length(merged_df): 47531409\n","94 West Tripura\n","length(merged_df): 47655435\n","95 Alipurduar\n","length(merged_df): 48182964\n","96 Darjiling\n","length(merged_df): 49123864\n","97 Jalpaiguri\n","length(merged_df): 49343742\n","98 Koch Bihar\n","length(merged_df): 49557236\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: 5533b929-ee31-49af-88d3-d3604cacf2fc\n","2020\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1330356\n","1 Changlang\n","length(merged_df): 3381834\n","2 Dibang Valley\n","length(merged_df): 4485987\n","3 East Kameng\n","length(merged_df): 5946992\n","4 East Siang\n","length(merged_df): 6841619\n","5 Kurung Kumey\n","length(merged_df): 7374955\n","6 Lohit\n","length(merged_df): 8623493\n","7 Longding\n","length(merged_df): 9122254\n","8 Lower Dibang Valley\n","length(merged_df): 10832333\n","9 Lower Subansiri\n","length(merged_df): 11508961\n","10 Namsai\n","length(merged_df): 11572983\n","11 Papum Pare\n","length(merged_df): 11964446\n","12 Tawang\n","13 Tirap\n","length(merged_df): 12482533\n","14 Upper Siang\n","length(merged_df): 12986325\n","15 Upper Subansiri\n","length(merged_df): 13569984\n","16 West Kameng\n","length(merged_df): 14629622\n","17 West Siang\n","length(merged_df): 15473209\n","18 Baksa\n","length(merged_df): 15724686\n","19 Barpeta\n","length(merged_df): 15796660\n","20 Bongaigaon\n","length(merged_df): 15864834\n","21 Cachar\n","length(merged_df): 16954573\n","22 Chirang\n","length(merged_df): 17208861\n","23 Darrang\n","length(merged_df): 17261514\n","24 Dhemaji\n","length(merged_df): 17476816\n","25 Dhubri\n","length(merged_df): 17557170\n","26 Dibrugarh\n","length(merged_df): 17762589\n","27 Dima Hasao\n","length(merged_df): 19560412\n","28 Goalpara\n","length(merged_df): 19608366\n","29 Golaghat\n","length(merged_df): 19763513\n","30 Hailakandi\n","length(merged_df): 19944247\n","31 Jorhat\n","length(merged_df): 20226737\n","32 Kamrup Metropolitan\n","length(merged_df): 20346460\n","33 Kamrup\n","length(merged_df): 20894960\n","34 Karbi Anglong\n","length(merged_df): 24409800\n","35 Karimganj\n","length(merged_df): 24720227\n","36 Kokrajhar\n","length(merged_df): 25249349\n","37 Lakhimpur\n","length(merged_df): 25394037\n","38 Morigaon\n","length(merged_df): 25464656\n","39 Nagaon\n","length(merged_df): 25730146\n","40 Nalbari\n","length(merged_df): 25783561\n","41 Sivasagar\n","length(merged_df): 25989411\n","42 Sonitpur\n","length(merged_df): 26322156\n","43 Tinsukia\n","length(merged_df): 26849930\n","44 Udalguri\n","length(merged_df): 26974580\n","45 Bishnupur\n","length(merged_df): 27012133\n","46 Chandel\n","length(merged_df): 29576779\n","47 Churachandpur\n","length(merged_df): 34538630\n","48 Imphal East\n","length(merged_df): 34666561\n","49 Imphal West\n","length(merged_df): 34724393\n","50 Senapati\n","length(merged_df): 36660245\n","51 Tamenglong\n","length(merged_df): 39328598\n","52 Thoubal\n","length(merged_df): 39442977\n","53 Ukhrul\n","length(merged_df): 42235241\n","54 East Garo Hills\n","length(merged_df): 42628773\n","55 East Khasi Hills\n","length(merged_df): 43646393\n","56 Jaintia Hills\n","length(merged_df): 45028991\n","57 North Garo Hills\n","length(merged_df): 45074050\n","58 Ri Bhoi\n","length(merged_df): 45875336\n","59 South Garo Hills\n","length(merged_df): 46412583\n","60 South West Garo Hills\n","length(merged_df): 46429328\n","61 South West Khasi Hills\n","length(merged_df): 46933540\n","62 West Garo Hills\n","length(merged_df): 47220989\n","63 West Khasi Hills\n","length(merged_df): 48539439\n","64 Aizawl\n","length(merged_df): 50741733\n","Saving merged_corrections_0.csv\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n","WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n","Ingestion task started: 719ee0b0-b6f9-4f82-a567-e1de6a176ab3\n","65 Champhai\n","length(merged_df): 3634458\n","66 Kolasib\n","length(merged_df): 4210001\n","67 Lawangtlai\n","length(merged_df): 5163272\n","68 Lunglei\n","length(merged_df): 7603186\n","69 Mamit\n","length(merged_df): 8935328\n","70 Saiha\n","length(merged_df): 10161350\n","71 Serchhip\n","length(merged_df): 10989166\n","72 Dimapur\n","length(merged_df): 11231066\n","73 Kiphire\n","length(merged_df): 11796555\n","74 Kohima\n","length(merged_df): 12588621\n","75 Longleng\n","length(merged_df): 12837079\n","76 Mokokchung\n","length(merged_df): 13936156\n","77 Mon\n","length(merged_df): 14914721\n","78 Peren\n","length(merged_df): 15919623\n","79 Phek\n","length(merged_df): 17010418\n","80 Tuensang\n","length(merged_df): 18037322\n","81 Wokha\n","length(merged_df): 19164284\n","82 Zunheboto\n","length(merged_df): 20101345\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 20114100\n","86 West Sikkim\n","length(merged_df): 20114504\n","87 Dhalai\n","length(merged_df): 20852781\n","88 Gomati\n","length(merged_df): 21287454\n","89 Khowai\n","length(merged_df): 21705560\n","90 North Tripura\n","length(merged_df): 22057720\n","91 Sipahijala\n","length(merged_df): 22183921\n","92 South Tripura\n","length(merged_df): 22465453\n","93 Unokoti\n","length(merged_df): 22613908\n","94 West Tripura\n","length(merged_df): 22735736\n","95 Alipurduar\n","length(merged_df): 23223854\n","96 Darjiling\n","length(merged_df): 24393328\n","97 Jalpaiguri\n","length(merged_df): 24698003\n","98 Koch Bihar\n","length(merged_df): 24915683\n","Saving merged_corrections_1.csv\n","Upload complete.\n","Ingestion task started: b4ac2c6e-32aa-44a8-a4a6-dbd936a44ca4\n","2021\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1330356\n","1 Changlang\n","length(merged_df): 3381834\n","2 Dibang Valley\n","length(merged_df): 4485987\n","3 East Kameng\n","length(merged_df): 5946992\n","4 East Siang\n","length(merged_df): 6841619\n","5 Kurung Kumey\n","length(merged_df): 7374955\n","6 Lohit\n","length(merged_df): 8623493\n","7 Longding\n","length(merged_df): 9122254\n","8 Lower Dibang Valley\n","length(merged_df): 10832333\n","9 Lower Subansiri\n","length(merged_df): 11508961\n","10 Namsai\n","length(merged_df): 11572983\n","11 Papum Pare\n","length(merged_df): 11964446\n","12 Tawang\n","13 Tirap\n","length(merged_df): 12482533\n","14 Upper Siang\n","length(merged_df): 12986325\n","15 Upper Subansiri\n","length(merged_df): 13569984\n","16 West Kameng\n","length(merged_df): 14629622\n","17 West Siang\n","length(merged_df): 15473209\n","18 Baksa\n","length(merged_df): 15724686\n","19 Barpeta\n","length(merged_df): 15796660\n","20 Bongaigaon\n","length(merged_df): 15864834\n","21 Cachar\n","length(merged_df): 16954573\n","22 Chirang\n","length(merged_df): 17208861\n","23 Darrang\n","length(merged_df): 17261514\n","24 Dhemaji\n","length(merged_df): 17476816\n","25 Dhubri\n","length(merged_df): 17557170\n","26 Dibrugarh\n","length(merged_df): 17762589\n","27 Dima Hasao\n","length(merged_df): 19560412\n","28 Goalpara\n","length(merged_df): 19608366\n","29 Golaghat\n","length(merged_df): 19763513\n","30 Hailakandi\n","length(merged_df): 19944247\n","31 Jorhat\n","length(merged_df): 20226737\n","32 Kamrup Metropolitan\n","length(merged_df): 20346460\n","33 Kamrup\n","length(merged_df): 20894960\n","34 Karbi Anglong\n","35 Karimganj\n","length(merged_df): 21205387\n","36 Kokrajhar\n","length(merged_df): 21734509\n","37 Lakhimpur\n","length(merged_df): 21879197\n","38 Morigaon\n","length(merged_df): 21949816\n","39 Nagaon\n","length(merged_df): 22215306\n","40 Nalbari\n","length(merged_df): 22268721\n","41 Sivasagar\n","length(merged_df): 22474571\n","42 Sonitpur\n","length(merged_df): 22807316\n","43 Tinsukia\n","length(merged_df): 23335090\n","44 Udalguri\n","length(merged_df): 23459740\n","45 Bishnupur\n","length(merged_df): 23497293\n","46 Chandel\n","length(merged_df): 26061939\n","47 Churachandpur\n","length(merged_df): 31023790\n","48 Imphal East\n","length(merged_df): 31151721\n","49 Imphal West\n","length(merged_df): 31209553\n","50 Senapati\n","length(merged_df): 33145405\n","51 Tamenglong\n","length(merged_df): 35813758\n","52 Thoubal\n","length(merged_df): 35928137\n","53 Ukhrul\n","length(merged_df): 38720401\n","54 East Garo Hills\n","length(merged_df): 39113933\n","55 East Khasi Hills\n","length(merged_df): 40131553\n","56 Jaintia Hills\n","length(merged_df): 41514151\n","57 North Garo Hills\n","length(merged_df): 41559210\n","58 Ri Bhoi\n","length(merged_df): 42360496\n","59 South Garo Hills\n","length(merged_df): 42897743\n","60 South West Garo Hills\n","length(merged_df): 42914488\n","61 South West Khasi Hills\n","length(merged_df): 43418700\n","62 West Garo Hills\n","length(merged_df): 43706149\n","63 West Khasi Hills\n","length(merged_df): 45024599\n","64 Aizawl\n","length(merged_df): 47226893\n","65 Champhai\n","length(merged_df): 50861351\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: 6d28ee07-5504-47db-bef6-41b776609a75\n","66 Kolasib\n","length(merged_df): 575543\n","67 Lawangtlai\n","length(merged_df): 1528814\n","68 Lunglei\n","length(merged_df): 3968728\n","69 Mamit\n","length(merged_df): 5300870\n","70 Saiha\n","length(merged_df): 6526892\n","71 Serchhip\n","length(merged_df): 7354708\n","72 Dimapur\n","length(merged_df): 7596608\n","73 Kiphire\n","length(merged_df): 8162097\n","74 Kohima\n","length(merged_df): 8954163\n","75 Longleng\n","length(merged_df): 9202621\n","76 Mokokchung\n","length(merged_df): 10301698\n","77 Mon\n","length(merged_df): 11280263\n","78 Peren\n","length(merged_df): 12285165\n","79 Phek\n","length(merged_df): 13375960\n","80 Tuensang\n","length(merged_df): 14402864\n","81 Wokha\n","length(merged_df): 15529826\n","82 Zunheboto\n","length(merged_df): 16466887\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 16479642\n","86 West Sikkim\n","length(merged_df): 16480046\n","87 Dhalai\n","length(merged_df): 17218323\n","88 Gomati\n","length(merged_df): 17652996\n","89 Khowai\n","length(merged_df): 18071102\n","90 North Tripura\n","length(merged_df): 18423262\n","91 Sipahijala\n","length(merged_df): 18549463\n","92 South Tripura\n","length(merged_df): 18830995\n","93 Unokoti\n","length(merged_df): 18979450\n","94 West Tripura\n","length(merged_df): 19101278\n","95 Alipurduar\n","length(merged_df): 19589396\n","96 Darjiling\n","length(merged_df): 20711886\n","97 Jalpaiguri\n","length(merged_df): 21016561\n","98 Koch Bihar\n","length(merged_df): 21234241\n","Saving merged_corrections_1.csv\n","Upload complete.\n","Ingestion task started: 07473749-9783-4784-9b75-52ef5c1bd445\n","2022\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1848049\n","1 Changlang\n","length(merged_df): 4620455\n","2 Dibang Valley\n","length(merged_df): 7067896\n","3 East Kameng\n","length(merged_df): 9238528\n","4 East Siang\n","length(merged_df): 10273774\n","5 Kurung Kumey\n","length(merged_df): 12139221\n","6 Lohit\n","length(merged_df): 13411891\n","7 Longding\n","length(merged_df): 13957060\n","8 Lower Dibang Valley\n","length(merged_df): 15800235\n","9 Lower Subansiri\n","length(merged_df): 17537049\n","10 Namsai\n","length(merged_df): 17592906\n","11 Papum Pare\n","length(merged_df): 19454295\n","12 Tawang\n","length(merged_df): 20259605\n","13 Tirap\n","length(merged_df): 20849587\n","14 Upper Siang\n","length(merged_df): 25013318\n","15 Upper Subansiri\n","length(merged_df): 26200628\n","16 West Kameng\n","length(merged_df): 29142728\n","17 West Siang\n","length(merged_df): 31692865\n","18 Baksa\n","length(merged_df): 31844938\n","19 Barpeta\n","length(merged_df): 31948301\n","20 Bongaigaon\n","length(merged_df): 32020012\n","21 Cachar\n","length(merged_df): 33015996\n","22 Chirang\n","length(merged_df): 33110098\n","23 Darrang\n","length(merged_df): 33184191\n","24 Dhemaji\n","length(merged_df): 33459882\n","25 Dhubri\n","length(merged_df): 33584733\n","26 Dibrugarh\n","length(merged_df): 33854284\n","27 Dima Hasao\n","length(merged_df): 35107231\n","28 Goalpara\n","length(merged_df): 35181002\n","29 Golaghat\n","length(merged_df): 35345503\n","30 Hailakandi\n","length(merged_df): 35574708\n","31 Jorhat\n","length(merged_df): 35772826\n","32 Kamrup Metropolitan\n","length(merged_df): 35930324\n","33 Kamrup\n","length(merged_df): 36346509\n","34 Karbi Anglong\n","length(merged_df): 38769946\n","35 Karimganj\n","length(merged_df): 39093925\n","36 Kokrajhar\n","length(merged_df): 39620058\n","37 Lakhimpur\n","length(merged_df): 39918127\n","38 Morigaon\n","length(merged_df): 40003240\n","39 Nagaon\n","length(merged_df): 40244487\n","40 Nalbari\n","length(merged_df): 40300500\n","41 Sivasagar\n","length(merged_df): 40499034\n","42 Sonitpur\n","length(merged_df): 40866091\n","43 Tinsukia\n","length(merged_df): 41475492\n","44 Udalguri\n","length(merged_df): 41567105\n","45 Bishnupur\n","length(merged_df): 41604433\n","46 Chandel\n","length(merged_df): 42744913\n","47 Churachandpur\n","length(merged_df): 43872322\n","48 Imphal East\n","length(merged_df): 43964012\n","49 Imphal West\n","length(merged_df): 44013228\n","50 Senapati\n","length(merged_df): 45464578\n","51 Tamenglong\n","length(merged_df): 47644037\n","52 Thoubal\n","length(merged_df): 47707540\n","53 Ukhrul\n","length(merged_df): 50022798\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: b7a98fca-05eb-42b0-85c3-de59164d0658\n","54 East Garo Hills\n","length(merged_df): 357834\n","55 East Khasi Hills\n","length(merged_df): 1398969\n","56 Jaintia Hills\n","length(merged_df): 2608139\n","57 North Garo Hills\n","length(merged_df): 2680692\n","58 Ri Bhoi\n","length(merged_df): 3610989\n","59 South Garo Hills\n","length(merged_df): 4039361\n","60 South West Garo Hills\n","length(merged_df): 4071718\n","61 South West Khasi Hills\n","length(merged_df): 4490566\n","62 West Garo Hills\n","length(merged_df): 4804823\n","63 West Khasi Hills\n","length(merged_df): 6123187\n","64 Aizawl\n","length(merged_df): 7805567\n","65 Champhai\n","length(merged_df): 8604961\n","66 Kolasib\n","length(merged_df): 9126897\n","67 Lawangtlai\n","length(merged_df): 9927838\n","68 Lunglei\n","length(merged_df): 11885061\n","69 Mamit\n","length(merged_df): 13041068\n","70 Saiha\n","length(merged_df): 13994266\n","71 Serchhip\n","length(merged_df): 14664286\n","72 Dimapur\n","length(merged_df): 14839149\n","73 Kiphire\n","length(merged_df): 15397224\n","74 Kohima\n","length(merged_df): 16046210\n","75 Longleng\n","length(merged_df): 16286450\n","76 Mokokchung\n","length(merged_df): 17059850\n","77 Mon\n","length(merged_df): 18007009\n","78 Peren\n","length(merged_df): 18737431\n","79 Phek\n","length(merged_df): 19621866\n","80 Tuensang\n","length(merged_df): 20699273\n","81 Wokha\n","length(merged_df): 21418859\n","82 Zunheboto\n","length(merged_df): 22249456\n","83 East Sikkim\n","84 North Sikkim\n","length(merged_df): 22255108\n","85 South Sikkim\n","length(merged_df): 22263543\n","86 West Sikkim\n","length(merged_df): 22433494\n","87 Dhalai\n","length(merged_df): 23093845\n","88 Gomati\n","length(merged_df): 23380456\n","89 Khowai\n","length(merged_df): 23629337\n","90 North Tripura\n","length(merged_df): 24031752\n","91 Sipahijala\n","length(merged_df): 24177507\n","92 South Tripura\n","length(merged_df): 24426953\n","93 Unokoti\n","length(merged_df): 24600290\n","94 West Tripura\n","length(merged_df): 24730615\n","95 Alipurduar\n","length(merged_df): 25183066\n","96 Darjiling\n","length(merged_df): 26125332\n","97 Jalpaiguri\n","length(merged_df): 26444567\n","98 Koch Bihar\n","length(merged_df): 26737087\n","Saving merged_corrections_1.csv\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n","WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n","Ingestion task started: 5a592b45-4bd1-4fef-9018-cd77a083c6de\n","2023\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1848049\n","1 Changlang\n","length(merged_df): 4620455\n","2 Dibang Valley\n","length(merged_df): 7067896\n","3 East Kameng\n","length(merged_df): 9238528\n","4 East Siang\n","length(merged_df): 10273774\n","5 Kurung Kumey\n","length(merged_df): 12139221\n","6 Lohit\n","length(merged_df): 13411891\n","7 Longding\n","length(merged_df): 13957060\n","8 Lower Dibang Valley\n","length(merged_df): 15800235\n","9 Lower Subansiri\n","length(merged_df): 17537049\n","10 Namsai\n","length(merged_df): 17592906\n","11 Papum Pare\n","length(merged_df): 19454295\n","12 Tawang\n","length(merged_df): 20259605\n","13 Tirap\n","length(merged_df): 20849587\n","14 Upper Siang\n","length(merged_df): 25013318\n","15 Upper Subansiri\n","length(merged_df): 26200628\n","16 West Kameng\n","length(merged_df): 29142728\n","17 West Siang\n","length(merged_df): 31692865\n","18 Baksa\n","length(merged_df): 31844938\n","19 Barpeta\n","length(merged_df): 31948301\n","20 Bongaigaon\n","length(merged_df): 32020012\n","21 Cachar\n","length(merged_df): 33015996\n","22 Chirang\n","length(merged_df): 33110098\n","23 Darrang\n","length(merged_df): 33184191\n","24 Dhemaji\n","length(merged_df): 33459882\n","25 Dhubri\n","length(merged_df): 33584733\n","26 Dibrugarh\n","length(merged_df): 33854284\n","27 Dima Hasao\n","length(merged_df): 35107231\n","28 Goalpara\n","length(merged_df): 35181002\n","29 Golaghat\n","length(merged_df): 35345503\n","30 Hailakandi\n","length(merged_df): 35574708\n","31 Jorhat\n","length(merged_df): 35772826\n","32 Kamrup Metropolitan\n","length(merged_df): 35930324\n","33 Kamrup\n","length(merged_df): 36346509\n","34 Karbi Anglong\n","length(merged_df): 38769946\n","35 Karimganj\n","length(merged_df): 39093925\n","36 Kokrajhar\n","length(merged_df): 39620058\n","37 Lakhimpur\n","length(merged_df): 39918127\n","38 Morigaon\n","length(merged_df): 40003240\n","39 Nagaon\n","length(merged_df): 40244487\n","40 Nalbari\n","length(merged_df): 40300500\n","41 Sivasagar\n","length(merged_df): 40499034\n","42 Sonitpur\n","length(merged_df): 40866091\n","43 Tinsukia\n","length(merged_df): 41475492\n","44 Udalguri\n","length(merged_df): 41567105\n","45 Bishnupur\n","length(merged_df): 41604433\n","46 Chandel\n","length(merged_df): 42744913\n","47 Churachandpur\n","length(merged_df): 43872322\n","48 Imphal East\n","length(merged_df): 43964012\n","49 Imphal West\n","length(merged_df): 44013228\n","50 Senapati\n","length(merged_df): 45464578\n","51 Tamenglong\n","length(merged_df): 47644037\n","52 Thoubal\n","length(merged_df): 47707540\n","53 Ukhrul\n","length(merged_df): 50022798\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: bf24e7ae-59e0-47e6-9e17-ceda97654ae2\n","54 East Garo Hills\n","length(merged_df): 357834\n","55 East Khasi Hills\n","length(merged_df): 1398969\n","56 Jaintia Hills\n","length(merged_df): 2608139\n","57 North Garo Hills\n","length(merged_df): 2680692\n","58 Ri Bhoi\n","length(merged_df): 3610989\n","59 South Garo Hills\n","length(merged_df): 4039361\n","60 South West Garo Hills\n","length(merged_df): 4071718\n","61 South West Khasi Hills\n","length(merged_df): 4490566\n","62 West Garo Hills\n","length(merged_df): 4804823\n","63 West Khasi Hills\n","length(merged_df): 6123187\n","64 Aizawl\n","length(merged_df): 7805567\n","65 Champhai\n","length(merged_df): 8604961\n","66 Kolasib\n","length(merged_df): 9126897\n","67 Lawangtlai\n","length(merged_df): 9927838\n","68 Lunglei\n","length(merged_df): 11885061\n","69 Mamit\n","length(merged_df): 13041068\n","70 Saiha\n","length(merged_df): 13994266\n","71 Serchhip\n","length(merged_df): 14664286\n","72 Dimapur\n","length(merged_df): 14839149\n","73 Kiphire\n","length(merged_df): 15397224\n","74 Kohima\n","length(merged_df): 16046210\n","75 Longleng\n","length(merged_df): 16286450\n","76 Mokokchung\n","length(merged_df): 17059850\n","77 Mon\n","length(merged_df): 18007009\n","78 Peren\n","length(merged_df): 18737431\n","79 Phek\n","length(merged_df): 19621866\n","80 Tuensang\n","length(merged_df): 20699273\n","81 Wokha\n","length(merged_df): 21418859\n","82 Zunheboto\n","length(merged_df): 22249456\n","83 East Sikkim\n","84 North Sikkim\n","length(merged_df): 22255108\n","85 South Sikkim\n","length(merged_df): 22263543\n","86 West Sikkim\n","length(merged_df): 22433494\n","87 Dhalai\n","length(merged_df): 23093845\n","88 Gomati\n","length(merged_df): 23380456\n","89 Khowai\n","length(merged_df): 23629337\n","90 North Tripura\n","length(merged_df): 24031752\n","91 Sipahijala\n","length(merged_df): 24177507\n","92 South Tripura\n","length(merged_df): 24426953\n","93 Unokoti\n","length(merged_df): 24600290\n","94 West Tripura\n","length(merged_df): 24730615\n","95 Alipurduar\n","length(merged_df): 25183066\n","96 Darjiling\n","length(merged_df): 26125332\n","97 Jalpaiguri\n","length(merged_df): 26444567\n","98 Koch Bihar\n","length(merged_df): 26737087\n","Saving merged_corrections_1.csv\n","Upload complete.\n","Ingestion task started: 7643f5cd-7037-483d-97cd-4712f77c7924\n"]}],"source":["# CH\n","\n","# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n"," for year in years:\n"," print(year)\n"," df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n"," df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)\n"," district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n"," dist_list = []\n"," for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," dist_list.append(district)\n","\n"," print(f'length(dist_list): {len(dist_list)}')\n","\n"," correction_num = 0\n"," j = 0\n"," merged_df = pd.DataFrame()\n","\n","\n"," for district in dist_list:\n"," print(j, district)\n"," j += 1\n","\n"," try:\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm_corrections.csv')\n"," except:\n"," continue\n","\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n"," print(f'length(merged_df): {len(merged_df)}')\n"," drive_path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/corrections_chm_{year}_{correction_num}.csv'\n","\n"," if len(merged_df) > 50000000: # Adjust based on need, if script crashes because of RAM exhaustion, default 70000000\n"," cols = merged_df.columns\n"," for i in range(1, len(cols)):\n"," merged_df[cols[i]] = merged_df[cols[i]].astype('Int64')\n"," print(f'Saving merged_corrections_{correction_num}.csv')\n"," merged_df.to_csv(drive_path, index=False)\n","\n"," file_name = f\"corrections_ch_{year}_result_{correction_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n","\n"," del(merged_df)\n"," correction_num += 1\n"," merged_df = pd.DataFrame()\n","\n"," if len(merged_df) > 0:\n"," cols = merged_df.columns\n"," for i in range(1, len(cols)):\n"," merged_df[cols[i]] = merged_df[cols[i]].astype('Int64')\n"," print(f'Saving merged_corrections_{correction_num}.csv')\n"," merged_df.to_csv(drive_path, index=False)\n","\n"," file_name = f\"corrections_ch_{year}_result_{correction_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n","\n"," del(merged_df)"]}],"metadata":{"colab":{"collapsed_sections":["Ah69USIF_hSV"],"provenance":[{"file_id":"1m0Q6Ic94NeVThpU_ZU0OydAeqlp6zWoe","timestamp":1759050819148}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/gee_scripts/ccd_change.js b/utilities/scripts/tree_health/gee_scripts/ccd_change.js new file mode 100644 index 00000000..33e60087 --- /dev/null +++ b/utilities/scripts/tree_health/gee_scripts/ccd_change.js @@ -0,0 +1,76 @@ +// This script is for doing a modal analysis while analysing forest cover change over the years between any 2 given years. +// It also gives the change quantification + +// Set year_1 and year_2. These are the 2 years between which comparison has to be made +// Note that year_2 must be > year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2023; + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +var project_path = 'projects/corestack-trees/assets/tree_characteristics'; + +var initial_image = ee.Image(project_path+'/modal_ccd_' + year_1 + '/' + agroclimaticZoneAcronymDict[acz]); +var final_image = ee.Image(project_path+'/modal_ccd_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz]); + +var change = initial_image.addBands(final_image); +change = change.unmask(-9999); + +print(change); + +// 3 denotes missing data +change = change.expression( + "((b('cc')==0) and (b('cc_1')==0)) ? (0)"+ + ":((b('cc')==1) and (b('cc_1')==1)) ? (0)"+ + ":((b('cc')==0) and (b('cc_1')==1)) ? (1)"+ + ":((b('cc')==1) and (b('cc_1')==0)) ? (-1)"+ + ":((b('cc')==-9999) and (b('cc_1')!=-9999)) ? (2)"+ + ":((b('cc')!=-9999) and (b('cc_1')==-9999)) ? (-2)"+ + ":((b('cc')==2) or (b('cc_1')==2)) ? (3)"+ + ":-9999" + ) + .clip(aoi); +change = change.updateMask(change.neq(-9999)); +// var palette = ['red', 'orange', 'white', 'lightgreen', 'darkgreen', 'black']; +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer'); + +// Export CCD change image +Export.image.toAsset({ + image: change, + description: 'ccd_change_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/ccd_change_' + year_1 + '_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); \ No newline at end of file diff --git a/utilities/scripts/tree_health/gee_scripts/ch_change.js b/utilities/scripts/tree_health/gee_scripts/ch_change.js new file mode 100644 index 00000000..ec746fd3 --- /dev/null +++ b/utilities/scripts/tree_health/gee_scripts/ch_change.js @@ -0,0 +1,74 @@ +// This script is for doing a modal analysis while analysing forest cover change over the years between any 2 given years. +// It also gives the change quantification + +// Set year_1 and year_2. These are the 2 years between which comparison has to be made +// Note that year_2 must be > year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2023; + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +var project_path = 'projects/corestack1-dev-alpha/assets/tree_characteristics'; + +var initial_image = ee.Image(project_path+'/modal_ch_' + year_1 + '/' + agroclimaticZoneAcronymDict[acz]); +var final_image = ee.Image(project_path+'/modal_ch_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz]); + +var change = initial_image.addBands(final_image); +change = change.unmask(-9999); +// 3 denotes missing data +change = change.expression( + "((b('ch_class') == -9999) and (b('ch_class_1') != -9999)) ? (2)" + + ": ((b('ch_class') != -9999) and (b('ch_class_1') == -9999)) ? (-2)"+ + ": ((b('ch_class') == 8) or (b('ch_class_1') == 8)) ? (3)" + + ": ((b('ch_class') == b('ch_class_1')) and (b('ch_class') != -9999)) ? (0)" + + ": ((b('ch_class') < b('ch_class_1')) and (b('ch_class') != -9999) and ((b('ch_class') <= 1) or (b('ch_class') >= 4))) ? (1)" + + ": ((b('ch_class') > b('ch_class_1')) and (b('ch_class_1') != -9999)) ? (-1)" + + ": ((b('ch_class') == 2) and ((b('ch_class_1') == 3) or (b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": ((b('ch_class') == 3) and ((b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": (((b('ch_class') == 2) or (b('ch_class') == 3)) and ((b('ch_class_1') == 4) or (b('ch_class_1') == 5))) ? (-1)"+ + ":-9999" +).clip(aoi); +change = change.updateMask(change.neq(-9999)); +// var palette = ['red', 'orange', 'white', 'lightgreen', 'green', 'black']; +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer ch'); + +// Export CH change image +Export.image.toAsset({ + image: change, + description: 'ch_change_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/ch_change_' + year_1 + '_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); diff --git a/utilities/scripts/tree_health/gee_scripts/fc_to_image.js b/utilities/scripts/tree_health/gee_scripts/fc_to_image.js new file mode 100644 index 00000000..629ce8ec --- /dev/null +++ b/utilities/scripts/tree_health/gee_scripts/fc_to_image.js @@ -0,0 +1,157 @@ +// Using Compiled Result from upload asset script +// Both CCD and CH images can be generated using this script. Comment out CCD if you +// are generating CH image and vice versa + +// TODO: Set total_files according to the number of result files for a particular ACZ +// in a particular year +var total_files = 5; + +// TODO: Mention the year for which you want to run this script for. +var year = '2018'; + +// TODO: Uncomment the acz for which you want to run this script for. +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +var res_list = []; +for (var i=0; i modify these + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); +} + + + +// ch + +var dist_list_dict = {'EPAHR': ['Baloda Bazar', 'Balod', 'BalrampurC', 'Bastar', 'Bemetara', 'BijapurC', 'BilaspurC', 'Dantewada', 'Dhamtari', 'Durg', 'Gariaband', 'Janjgir-Champa', 'Jashpur', 'Kabeerdham', 'Kondagaon', 'Korba', 'Koriya', 'Mahasamund', 'Mungeli', 'Narayanpur', 'Raigarh', 'Raipur', 'Rajnandgaon', 'Sukma', 'Surajpur', 'Surguja', 'Uttar Bastar Kanker', 'Bokaro', 'Chatra', 'Deoghar', 'Dhanbad', 'Dumka', 'Garhwa', 'Giridih', 'Gumla', 'Hazaribagh', 'Jamtara', 'Khunti', 'Kodarma', 'Latehar', 'Lohardaga', 'Pakur', 'Palamu', 'Pashchimi Singhbhum', 'Purbi Singhbhum', 'Ramgarh', 'Ranchi', 'Saraikela-kharsawan', 'Simdega', 'Anuppur', 'Balaghat', 'Shahdol', 'Sidhi', 'Singrauli', 'Umaria', 'Bhandara', 'Chandrapur', 'Garhchiroli', 'Gondiya', 'Anugul', 'Balangir', 'Bargarh', 'Bauda', 'Debagarh', 'Dhenkanal', 'Jharsuguda', 'Kalahandi', 'Kandhamal', 'Kendujhar', 'Koraput', 'Malkangiri', 'Mayurbhanj', 'Nabarangapur', 'Nuapada', 'Rayagada', 'Sambalpur', 'Subarnapur', 'Sundargarh', 'Puruliya'], + 'CPAHR': ['Ashoknagar', 'Betul', 'Bhind', 'Bhopal', 'Chhatarpur', 'Chhindwara', 'Damoh', 'Datia', 'Dindori', 'Guna', 'Gwalior', 'Harda', 'Hoshangabad', 'Jabalpur', 'Katni', 'Mandla', 'Morena', 'Narsimhapur', 'Panna', 'Raisen', 'Rajgarh', 'Rewa', 'Sagar', 'Satna', 'Sehore', 'Seoni', 'Sheopur', 'Shivpuri', 'Tikamgarh', 'Vidisha', 'Ajmer', 'Alwar', 'Banswara', 'Baran', 'Bharatpur', 'Bhilwara', 'Bundi', 'Chittaurgarh', 'Dausa', 'Dhaulpur', 'Dungarpur', 'Jaipur', 'Karauli', 'Kota', 'Pali', 'Pratapgarhraj', 'Rajsamand', 'Sawai Madhopur', 'Sirohi', 'Tonk', 'Udaipur', 'Banda', 'Chitrakoot', 'Hamirpurup', 'Jalaun', 'Jhansi', 'Lalitpur', 'Mahoba'], + 'SPAHR': ['Anantapur', 'Chittoor', 'Kurnool', 'Y.S.R.', 'Bagalkot', 'Bangalore Rural', 'Bangalore', 'Belgaum', 'Bellary', 'Bidar', 'Bijapur', 'Chamrajnagar', 'Chikballapura', 'Chitradurga', 'Davanagere', 'Dharwad', 'Gadag', 'Gulbarga', 'Hassan', 'Haveri', 'Kolar', 'Koppal', 'Mandya', 'Mysore', 'Raichur', 'Ramanagara', 'Tumkur', 'Yadgir', 'Ariyalur', 'Coimbatore', 'Dharmapuri', 'Dindigul', 'Erode', 'Karur', 'Krishnagiri', 'Madurai', 'Namakkal', 'Perambalur', 'Salem', 'The Nilgiris', 'Theni', 'Tiruchirappalli', 'Tiruppur', 'Adilabad', 'Hyderabad', 'Karimnagar', 'Khammam', 'Mahbubnagar', 'Medak', 'Nalgonda', 'Nizamabad', 'Ranga Reddy', 'Warangal'], + 'WPAHR': ['Agar Malwa', 'Alirajpur', 'Barwani', 'Burhanpur', 'Dewas', 'Dhar', 'East Nimar', 'Indore', 'Jhabua', 'Mandsaur', 'Neemuch', 'Ratlam', 'Shajapur', 'Ujjain', 'West Nimar', 'Ahmadnagar', 'Akola', 'Amravati', 'Aurangabad', 'Bid', 'Buldana', 'Dhule', 'Hingoli', 'Jalgaon', 'Jalna', 'Kolhapur', 'Latur', 'Nagpur', 'Nanded', 'Nandurbar', 'Nashik', 'Osmanabad', 'Parbhani', 'Pune', 'Sangli', 'Satara', 'Solapur', 'Wardha', 'Washim', 'Yavatmal', 'Jhalawar'], + 'ECPHR': ['East Godavari', 'Guntur', 'Krishna', 'Nellore', 'Prakasam', 'Srikakulam', 'Visakhapatnam', 'Vizianagaram', 'West Godavari', 'Baleshwar', 'Bhadrak', 'Cuttack', 'Gajapati', 'Ganjam', 'Jagatsinghapur', 'Jajapur', 'Kendrapara', 'Khordha', 'Nayagarh', 'Puri', 'Karaikal', 'Puducherry', 'Yanam', 'Chennai', 'Cuddalore', 'Kancheepuram', 'Nagappattinam', 'Pudukkottai', 'Ramanathapuram', 'Sivaganga', 'Thanjavur', 'Thiruvallur', 'Thiruvarur', 'Thoothukkudi', 'Tirunelveli', 'Tiruvannamalai', 'Vellore', 'Viluppuram', 'Virudunagar'], + 'UGPR': ['Agra', 'Aligarh', 'Allahabad', 'Amethi', 'Amroha', 'Auraiya', 'Baghpat', 'Barabanki', 'Bareilly', 'Bijnor', 'Budaun', 'Bulandshahr', 'Etah', 'Etawah', 'Farrukhabad', 'Fatehpur', 'Firozabad', 'Gautam Buddha Nagar', 'Ghaziabad', 'Hapur', 'Hardoi', 'Hathras', 'Kannauj', 'Kanpur Dehat', 'Kanpur Nagar', 'Kasganj', 'Kaushambi', 'Lakhimpur Kheri', 'Lucknow', 'Mainpuri', 'Mathura', 'Meerut', 'Moradabad', 'Muzaffarnagar', 'Pilibhit', 'Pratapgarhup', 'Rae Bareli', 'Rampur', 'Saharanpur', 'Sambhal', 'Shahjahanpur', 'Shamli', 'Sitapur', 'Sultanpur', 'Unnao', 'Hardwar', 'Udham Singh Nagar'], + 'LGPR': ['Bankura', 'Barddhaman', 'Birbhum', 'Dakshin Dinajpur', 'Haora', 'Hugli', 'Kolkata', 'Maldah', 'Murshidabad', 'Nadia', 'North 24 Parganas', 'Pashchim Medinipur', 'Purba Medinipur', 'South 24 Parganas', 'Uttar Dinajpur'], + 'MGPR': ['Araria', 'Arwal', 'AurangabadB', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar', 'Darbhanga', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur', 'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger', 'Muzaffarpur', 'Nalanda', 'Nawada', 'Pashchim Champaran', 'Patna', 'Purba Champaran', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur', 'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali', 'Godda', 'Sahibganj', 'Ambedkar Nagar', 'Azamgarh', 'Bahraich', 'Ballia', 'Balrampur', 'Basti', 'Chandauli', 'Deoria', 'Faizabad', 'Ghazipur', 'Gonda', 'Gorakhpur', 'Jaunpur', 'Kushinagar', 'Maharajganj', 'Mau', 'Mirzapur', 'Sant Kabir Nagar', 'Sant Ravi Das Nagar', 'Shravasti', 'Siddharth Nagar', 'Sonbhadra', 'Varanasi'], + 'TGPR': ['Chandigarh', 'Ambala', 'Bhiwani', 'Faridabad', 'Fatehabad', 'Gurgaon', 'Hisar', 'Jhajjar', 'Jind', 'Kaithal', 'Karnal', 'Kurukshetra', 'Mahendragarh', 'Mewat', 'Palwal', 'Panchkula', 'Panipat', 'Rewari', 'Rohtak', 'Sirsa', 'Sonipat', 'Yamunanagar', 'Delhi', 'Amritsar', 'Barnala', 'Bathinda', 'Faridkot', 'Fatehgarh Sahib', 'Fazilka', 'Firozpur', 'Gurdaspur', 'Hoshiarpur', 'Jalandhar', 'Kapurthala', 'Ludhiana', 'Mansa', 'Moga', 'Muktsar', 'Patiala', 'Sahibzada Ajit Singh Nagar', 'Sangrur', 'Shahid Bhagat Singh Nagar', 'Tarn Taran', 'Ganganagar', 'Hanumangarh'], + 'WHR': ['Bilaspur', 'Chamba', 'Hamirpur', 'Kangra', 'Kinnaur', 'Kullu', 'Lahul & Spiti', 'Mandi', 'Shimla', 'Sirmaur', 'Solan', 'Una', 'Anantnag', 'Badgam', 'Bandipore', 'Baramulla', 'Doda', 'Ganderbal', 'Jammu', 'Kargil', 'Kathua', 'Kishtwar', 'Kulgam', 'Kupwara', 'Leh (Ladakh)', 'Poonch', 'Pulwama', 'Rajouri', 'Ramban', 'Reasi', 'Samba', 'Shupiyan', 'Srinagar', 'Udhampur', 'Pathankot', 'Rupnagar', 'Almora', 'Bageshwar', 'Chamoli', 'Champawat', 'Dehradun', 'Garhwal', 'Nainital', 'Pithoragarh', 'Rudraprayag', 'Tehri Garhwal', 'Uttarkashi'], + 'EHR': ['Anjaw', 'Changlang', 'Dibang Valley', 'East Kameng', 'East Siang', 'Kurung Kumey', 'Lohit', 'Longding', 'Lower Dibang Valley', 'Lower Subansiri', 'Namsai', 'Papum Pare', 'Tawang', 'Tirap', 'Upper Siang', 'Upper Subansiri', 'West Kameng', 'West Siang', 'Baksa', 'Barpeta', 'Bongaigaon', 'Cachar', 'Chirang', 'Darrang', 'Dhemaji', 'Dhubri', 'Dibrugarh', 'Dima Hasao', 'Goalpara', 'Golaghat', 'Hailakandi', 'Jorhat', 'Kamrup Metropolitan', 'Kamrup', 'Karbi Anglong', 'Karimganj', 'Kokrajhar', 'Lakhimpur', 'Morigaon', 'Nagaon', 'Nalbari', 'Sivasagar', 'Sonitpur', 'Tinsukia', 'Udalguri', 'Bishnupur', 'Chandel', 'Churachandpur', 'Imphal East', 'Imphal West', 'Senapati', 'Tamenglong', 'Thoubal', 'Ukhrul', 'East Garo Hills', 'East Khasi Hills', 'Jaintia Hills', 'North Garo Hills', 'Ri Bhoi', 'South Garo Hills', 'South West Garo Hills', 'South West Khasi Hills', 'West Garo Hills', 'West Khasi Hills', 'Aizawl', 'Champhai', 'Kolasib', 'Lawangtlai', 'Lunglei', 'Mamit', 'Saiha', 'Serchhip', 'Dimapur', 'Kiphire', 'Kohima', 'Longleng', 'Mokokchung', 'Mon', 'Peren', 'Phek', 'Tuensang', 'Wokha', 'Zunheboto', 'East Sikkim', 'North Sikkim', 'South Sikkim', 'West Sikkim', 'Dhalai', 'Gomati', 'Khowai', 'North Tripura', 'Sipahijala', 'South Tripura', 'Unokoti', 'West Tripura', 'Alipurduar', 'Darjiling', 'Jalpaiguri', 'Koch Bihar'] +}; + +acz = acronym; + +var dist_list = dist_list_dict[acz]; +var india_district = ee.FeatureCollection('projects/ee-indiasat/assets/india_district_boundaries'); +var aoi = india_district.filter(ee.Filter.eq('Name', dist_list[0])).geometry(); + +for (var i=1; i year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2018; + +// var india_district_boundary = ee.FeatureCollection("projects/ee-indiasat/assets/india_district_boundaries"); +// var aoi = india_district_boundary.filter(ee.Filter.eq('Name', "Baloda Bazar")).geometry(); + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +// var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +// Set the change list according to the following: +// -2 -> Deforestation +// -1 -> Degradation +// 0 -> No Change +// 1 -> Improvement +// 2 -> Afforestation +// 3 -> Missing Data +var change_list = [-1, 1]; + +Map.addLayer(aoi, {'color': 'black'}, 'AOI'); +Map.centerObject(aoi, 7); + +var year_in_0 = String(year_1-1); +var year_in_1 = String(year_1); +var year_in_2 = String(year_1+1); + +var year_fi_0 = String(year_2-1); +var year_fi_1 = String(year_2); +var year_fi_2 = String(year_2+1); + +var project_path = 'projects/corestack-trees/assets/tree_characteristics'; + +var ccd_in_0 = ee.ImageCollection(project_path+'/ccd_' + year_in_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + + +ccd_in_0 = ee.Algorithms.If( + ccd_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_in_0.mean().clip(aoi).select('cc') +); + +ccd_in_0 = ee.Image(ccd_in_0); + + +// var tree_cover_in_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_0 = tree_cover.get_tree_cover(aoi, year_in_0); +tree_cover_in_0 = tree_cover_in_0.rename(['label_' + year_in_0.slice(-2)]); + + +var ccd_in_1 = ee.ImageCollection(project_path+'/ccd_' + year_in_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_in_1 = ee.Algorithms.If( + ccd_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_in_1.mean().clip(aoi).select('cc') +); +ccd_in_1 = ee.Image(ccd_in_1); + +// var tree_cover_in_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_1 = tree_cover.get_tree_cover(aoi, year_in_1); +tree_cover_in_1 = tree_cover_in_1.rename(['label_' + year_in_1.slice(-2)]); + +var ccd_in_2 = ee.ImageCollection(project_path+'/ccd_' + year_in_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_in_2 = ee.Algorithms.If( + ccd_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_in_2.mean().clip(aoi).select('cc') +); +ccd_in_2 = ee.Image(ccd_in_2); + +// var tree_cover_in_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_2 = tree_cover.get_tree_cover(aoi, year_in_2); +tree_cover_in_2 = tree_cover_in_2.rename(['label_' + year_in_2.slice(-2)]); + +var ccd_fi_0 = ee.ImageCollection(project_path+'/ccd_' + year_fi_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_fi_0 = ee.Algorithms.If( + ccd_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_fi_0.mean().clip(aoi).select('cc') +); +ccd_fi_0 = ee.Image(ccd_fi_0); + +// var tree_cover_fi_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_0 = tree_cover.get_tree_cover(aoi, year_fi_0); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_' + year_fi_0.slice(-2)]); + +var ccd_fi_1 = ee.ImageCollection(project_path+'/ccd_' + year_fi_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_fi_1 = ee.Algorithms.If( + ccd_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_fi_1.mean().clip(aoi).select('cc') +); +ccd_fi_1 = ee.Image(ccd_fi_1); + +// var tree_cover_fi_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_1 = tree_cover.get_tree_cover(aoi, year_fi_1); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_' + year_fi_1.slice(-2)]); + +var ccd_fi_2 = ee.ImageCollection(project_path+'/ccd_' + year_fi_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_fi_2 = ee.Algorithms.If( + ccd_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_fi_2.mean().clip(aoi).select('cc') +); +ccd_fi_2 = ee.Image(ccd_fi_2); + +// var tree_cover_fi_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_2 = tree_cover.get_tree_cover(aoi, year_fi_2); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_' + year_fi_2.slice(-2)]); + +var correction_in_0 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_in_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_in_0).toInt32(); + }); + +correction_in_0 = ee.Algorithms.If( + correction_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_in_0) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_0.mode().clip(aoi).select('cc_' + year_in_0) +); +correction_in_0 = ee.Image(correction_in_0); + +var correction_in_1 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_in_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_in_1).toInt32(); + }); + +correction_in_1 = ee.Algorithms.If( + correction_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_in_1) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_1.mode().clip(aoi).select('cc_' + year_in_1) +); +correction_in_1 = ee.Image(correction_in_1); + +var correction_in_2 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_in_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_in_2).toInt32(); + }); + +correction_in_2 = ee.Algorithms.If( + correction_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_in_2) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_2.mode().clip(aoi).select('cc_' + year_in_2) +); +correction_in_2 = ee.Image(correction_in_2); + +var correction_fi_0 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_fi_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_fi_0).toInt32(); + }); + +correction_fi_0 = ee.Algorithms.If( + correction_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_fi_0) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_0.mode().clip(aoi).select('cc_' + year_fi_0) +); +correction_fi_0 = ee.Image(correction_fi_0); + +var correction_fi_1 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_fi_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_fi_1).toInt32(); + }); + +correction_fi_1 = ee.Algorithms.If( + correction_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_fi_1) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_1.mode().clip(aoi).select('cc_' + year_fi_1) +); +correction_fi_1 = ee.Image(correction_fi_1); + +var correction_fi_2 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_fi_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_fi_2).toInt32(); + }); + +correction_fi_2 = ee.Algorithms.If( + correction_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_fi_2) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_2.mode().clip(aoi).select('cc_' + year_fi_2) +); +correction_fi_2 = ee.Image(correction_fi_2); + + +var palette2 =['FFA500', '007500', '000000']; +Map.addLayer(correction_fi_2, {bands:['cc_'+year_fi_2], min: 0, max: 2, palette: palette2}, 'Empty'); + +correction_in_0 = correction_in_0.rename(['cc_in_0']); +correction_in_1 = correction_in_1.rename(['cc_in_1']); +correction_in_2 = correction_in_2.rename(['cc_in_2']); +correction_fi_0 = correction_fi_0.rename(['cc_fi_0']); +correction_fi_1 = correction_fi_1.rename(['cc_fi_1']); +correction_fi_2 = correction_fi_2.rename(['cc_fi_2']); + + + + +ccd_in_0 = ccd_in_0.addBands(correction_in_0); +ccd_in_0 = ccd_in_0.unmask(-9999); +ccd_in_0 = ccd_in_0.expression( + "((b('cc_in_0')!=b('cc')) and (b('cc_in_0')!=-9999)) ? (b('cc_in_0'))"+ + ":b('cc')" + ).clip(aoi); +ccd_in_0 = ccd_in_0.updateMask(ccd_in_0.neq(-9999)); +ccd_in_0 = ccd_in_0.rename(['cc_in_0']); + + +ccd_in_1 = ccd_in_1.addBands(correction_in_1); +ccd_in_1 = ccd_in_1.unmask(-9999); +ccd_in_1 = ccd_in_1.expression( + "((b('cc_in_1')!=b('cc')) and (b('cc_in_1')!=-9999)) ? (b('cc_in_1'))"+ + ":b('cc')" + ).clip(aoi); +ccd_in_1 = ccd_in_1.updateMask(ccd_in_1.neq(-9999)); +ccd_in_1 = ccd_in_1.rename(['cc_in_1']); + + +ccd_in_2 = ccd_in_2.addBands(correction_in_2); +ccd_in_2 = ccd_in_2.unmask(-9999); +ccd_in_2 = ccd_in_2.expression( + "((b('cc_in_2')!=b('cc')) and (b('cc_in_2')!=-9999)) ? (b('cc_in_2'))"+ + ":b('cc')" + ).clip(aoi); +ccd_in_2 = ccd_in_2.updateMask(ccd_in_2.neq(-9999)); +ccd_in_2 = ccd_in_2.rename(['cc_in_2']); + + +ccd_fi_0 = ccd_fi_0.addBands(correction_fi_0); +ccd_fi_0 = ccd_fi_0.unmask(-9999); +ccd_fi_0 = ccd_fi_0.expression( + "((b('cc_fi_0')!=b('cc')) and (b('cc_fi_0')!=-9999)) ? (b('cc_fi_0'))"+ + ":b('cc')" + ).clip(aoi); +ccd_fi_0 = ccd_fi_0.updateMask(ccd_fi_0.neq(-9999)); +ccd_fi_0 = ccd_fi_0.rename(['cc_fi_0']); + + +ccd_fi_1 = ccd_fi_1.addBands(correction_fi_1); +ccd_fi_1 = ccd_fi_1.unmask(-9999); +ccd_fi_1 = ccd_fi_1.expression( + "((b('cc_fi_1')!=b('cc')) and (b('cc_fi_1')!=-9999)) ? (b('cc_fi_1'))"+ + ":b('cc')" + ).clip(aoi); +ccd_fi_1 = ccd_fi_1.updateMask(ccd_fi_1.neq(-9999)); +ccd_fi_1 = ccd_fi_1.rename(['cc_fi_1']); + + +ccd_fi_2 = ccd_fi_2.addBands(correction_fi_2); +ccd_fi_2 = ccd_fi_2.unmask(-9999); +ccd_fi_2 = ccd_fi_2.expression( + "((b('cc_fi_2')!=b('cc')) and (b('cc_fi_2')!=-9999)) ? (b('cc_fi_2'))"+ + ":b('cc')" + ).clip(aoi); +ccd_fi_2 = ccd_fi_2.updateMask(ccd_fi_2.neq(-9999)); +ccd_fi_2 = ccd_fi_2.rename(['cc_fi_2']); + + +tree_cover_in_0 = tree_cover_in_0.rename(['label_in_0']); +tree_cover_in_1 = tree_cover_in_1.rename(['label_in_1']); +tree_cover_in_2 = tree_cover_in_2.rename(['label_in_2']); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_fi_0']); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_fi_1']); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_fi_2']); + +var tree_cover_initial = tree_cover_in_0.addBands(tree_cover_in_1).addBands(tree_cover_in_2); +tree_cover_initial = tree_cover_initial.unmask(-9999); +tree_cover_initial = tree_cover_initial.expression( + "((b('label_in_1')!=-9999)) ? (b('label_in_1'))"+ + ":((b('label_in_0')!=-9999) and (b('label_in_1')==-9999) and (b('label_in_2')!=-9999)) ? (b('label_in_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_initial = tree_cover_initial.rename(['label']); +tree_cover_initial = tree_cover_initial.updateMask(tree_cover_initial.neq(-9999)); +// print(tree_cover_initial); + + +var tree_cover_final = tree_cover_fi_0.addBands(tree_cover_fi_1).addBands(tree_cover_fi_2); +tree_cover_final = tree_cover_final.unmask(-9999); +tree_cover_final = tree_cover_final.expression( + "((b('label_fi_1')!=-9999)) ? (b('label_fi_1'))"+ + ":((b('label_fi_0')!=-9999) and (b('label_fi_1')==-9999) and (b('label_fi_2')!=-9999)) ? (b('label_fi_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_final = tree_cover_final.rename(['label']); +tree_cover_final = tree_cover_final.updateMask(tree_cover_final.neq(-9999)); + +Map.addLayer(tree_cover_initial, {palette: ['white']}, 'tree cover initial'); +Map.addLayer(tree_cover_final, {palette: ['white']}, 'tree cover final'); + +// 2 denotes missing data in modal images + +var initial_image = ccd_in_0.addBands(ccd_in_1).addBands(ccd_in_2); +initial_image = initial_image.unmask(-9999); +// 0 denotes Low; 1 denotes High; 2 denotes missing data +initial_image = initial_image.expression( + "((b('cc_in_0')==0) and (b('cc_in_1')==0) and (b('cc_in_2')==0)) ? (0)"+ + ":((b('cc_in_0')==0) and (b('cc_in_1')==0) and (b('cc_in_2')==1)) ? (0)"+ + ":((b('cc_in_0')==0) and (b('cc_in_1')==1) and (b('cc_in_2')==0)) ? (0)"+ + ":((b('cc_in_0')==0) and (b('cc_in_1')==1) and (b('cc_in_2')==1)) ? (1)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==0) and (b('cc_in_2')==0)) ? (0)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==0) and (b('cc_in_2')==1)) ? (1)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==1) and (b('cc_in_2')==0)) ? (1)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==1) and (b('cc_in_2')==1)) ? (1)"+ + ":((b('cc_in_0')==-9999 or b('cc_in_2')==-9999) and (b('cc_in_1')!=-9999)) ? (b('cc_in_1'))"+ + ":((b('cc_in_0')!=-9999 and b('cc_in_1')==-9999 and b('cc_in_2')!=-9999) and (b('cc_in_0')==b('cc_in_2'))) ? (b('cc_in_0'))"+ + ":((b('cc_in_0')!=-9999 and b('cc_in_1')==-9999 and b('cc_in_2')==-9999)) ? (b('cc_in_0'))"+ + ":((b('cc_in_1')==-9999 and b('cc_in_2')!=-9999) and (b('cc_in_0')!=b('cc_in_2'))) ? (b('cc_in_2'))"+ + ":(-9999)" + ).clip(aoi); +initial_image = initial_image.rename(['cc']); +initial_image = initial_image.updateMask(initial_image.neq(-9999)); + +initial_image = initial_image.addBands(tree_cover_initial); +initial_image = initial_image.unmask(-9999); +// 2 denotes missing data +initial_image = initial_image.expression( + "((b('cc') == -9999) and (b('label') != -9999)) ? (2)"+ + ":b('cc')" + ).clip(aoi); +initial_image = initial_image.rename(['cc']); +initial_image = initial_image.updateMask(initial_image.neq(-9999)); +print("initial_image", initial_image) + +var final_image = ccd_fi_0.addBands(ccd_fi_1).addBands(ccd_fi_2); +final_image = final_image.unmask(-9999); +final_image = final_image.expression( + "((b('cc_fi_0')==0) and (b('cc_fi_1')==0) and (b('cc_fi_2')==0)) ? (0)"+ + ":((b('cc_fi_0')==0) and (b('cc_fi_1')==0) and (b('cc_fi_2')==1)) ? (0)"+ + ":((b('cc_fi_0')==0) and (b('cc_fi_1')==1) and (b('cc_fi_2')==0)) ? (0)"+ + ":((b('cc_fi_0')==0) and (b('cc_fi_1')==1) and (b('cc_fi_2')==1)) ? (1)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==0) and (b('cc_fi_2')==0)) ? (0)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==0) and (b('cc_fi_2')==1)) ? (1)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==1) and (b('cc_fi_2')==0)) ? (1)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==1) and (b('cc_fi_2')==1)) ? (1)"+ + ":((b('cc_fi_0')==-9999 or b('cc_fi_2')==-9999) and (b('cc_fi_1')!=-9999)) ? (b('cc_fi_1'))"+ + ":((b('cc_fi_0')!=-9999 and b('cc_fi_1')==-9999 and b('cc_fi_2')!=-9999) and (b('cc_fi_0')==b('cc_fi_2'))) ? (b('cc_fi_0'))"+ + ":((b('cc_fi_0')!=-9999 and b('cc_fi_1')==-9999 and b('cc_fi_2')==-9999)) ? (b('cc_fi_0'))"+ + ":((b('cc_fi_1')==-9999 and b('cc_fi_2')!=-9999) and (b('cc_fi_0')!=b('cc_fi_2'))) ? (b('cc_fi_2'))"+ + ":(-9999)" + ).clip(aoi); +final_image = final_image.rename(['cc']); +final_image = final_image.updateMask(final_image.neq(-9999)); + +final_image = final_image.addBands(tree_cover_final); +final_image = final_image.unmask(-9999); +// 2 denotes missing data +final_image = final_image.expression( + "((b('cc') == -9999) and (b('label') != -9999)) ? (2)"+ + ":b('cc')" + ).clip(aoi); +final_image = final_image.rename(['cc']); +final_image = final_image.updateMask(final_image.neq(-9999)); + +// var palette = ['orange', 'green', 'black']; +var palette =['FFA500', '007500', '000000']; +Map.addLayer(initial_image, {bands:['cc'], min: 0, max: 2, palette: palette}, 'initial modal image'); +Map.addLayer(final_image, {bands:['cc'], min: 0, max: 2, palette: palette}, 'final modal image'); + +// var change = initial_image.addBands(final_image); +// change = change.unmask(-9999); + +// print(change); + +// // 3 denotes missing data +// change = change.expression( +// "((b('cc')==0) and (b('cc_1')==0)) ? (0)"+ +// ":((b('cc')==1) and (b('cc_1')==1)) ? (0)"+ +// ":((b('cc')==0) and (b('cc_1')==1)) ? (1)"+ +// ":((b('cc')==1) and (b('cc_1')==0)) ? (-1)"+ +// ":((b('cc')==-9999) and (b('cc_1')!=-9999)) ? (2)"+ +// ":((b('cc')!=-9999) and (b('cc_1')==-9999)) ? (-2)"+ +// ":((b('cc')==2) or (b('cc_1')==2)) ? (3)"+ +// ":-9999" +// ) +// .clip(aoi); +// change = change.updateMask(change.neq(-9999)); +// // var palette = ['red', 'orange', 'white', 'lightgreen', 'darkgreen', 'black']; +// var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +// Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer'); + +// Export modal initial image +Export.image.toAsset({ + image: initial_image, + description: 'ccd_' + year_in_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ccd_' + year_in_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// Export modal final image +Export.image.toAsset({ + image: final_image, + description: 'ccd_' + year_fi_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ccd_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// // Export CCD change image +// Export.image.toAsset({ +// image: change, +// description: 'ccd_change_' + agroclimaticZoneAcronymDict[acz], +// assetId: project_path+'/ccd_change_' + year_in_1 + '_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], +// region: aoi, +// scale: 25, +// crs: 'EPSG:4326', +// maxPixels: 10000000000 +// }); + +//=========================================== +// var change_ccd = change; + +// for (var c=0; c year_1 and both must be of Integer type +var year_1 = 2023; +var year_2 = 2022; + +// var india_district_boundary = ee.FeatureCollection("projects/ee-indiasat/assets/india_district_boundaries"); +// var aoi = india_district_boundary.filter(ee.Filter.eq('Name', "Baloda Bazar")).geometry(); + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +// Set the change list according to the following: +// -2 -> Deforestation +// -1 -> Degradation +// 0 -> No Change +// 1 -> Improvement +// 2 -> Afforestation +// 3 -> Missing Data +var change_list = [-1, 1]; + +Map.addLayer(aoi, {'color': 'black'}, 'AOI'); +Map.centerObject(aoi, 7); + +var year_in_0 = String(year_1-1); +var year_in_1 = String(year_1); +var year_in_2 = String(year_1+1); + +var year_fi_0 = String(year_2-1); +var year_fi_1 = String(year_2); +var year_fi_2 = String(year_2+1); + +var project_path = 'projects/corestack1-dev-alpha/assets/tree_characteristics'; + +// var tree_cover_in_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_0 = tree_cover.get_tree_cover(aoi, year_in_0); +tree_cover_in_0 = tree_cover_in_0.rename(['label_' + year_in_0.slice(-2)]); + + +// var tree_cover_in_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_1 = tree_cover.get_tree_cover(aoi, year_in_1); +tree_cover_in_1 = tree_cover_in_1.rename(['label_' + year_in_1.slice(-2)]); + + +// var tree_cover_in_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_2 = tree_cover.get_tree_cover(aoi, year_in_2); +tree_cover_in_2 = tree_cover_in_2.rename(['label_' + year_in_2.slice(-2)]); + + +// var tree_cover_fi_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_0 = tree_cover.get_tree_cover(aoi, year_fi_0); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_' + year_fi_0.slice(-2)]); + + +// var tree_cover_fi_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_1 = tree_cover.get_tree_cover(aoi, year_fi_1); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_' + year_fi_1.slice(-2)]); + + +// var tree_cover_fi_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_2 = tree_cover.get_tree_cover(aoi, year_fi_2); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_' + year_fi_2.slice(-2)]); + +tree_cover_in_0 = tree_cover_in_0.rename(['label_in_0']); +tree_cover_in_1 = tree_cover_in_1.rename(['label_in_1']); +tree_cover_in_2 = tree_cover_in_2.rename(['label_in_2']); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_fi_0']); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_fi_1']); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_fi_2']); + +var tree_cover_initial = tree_cover_in_0.addBands(tree_cover_in_1).addBands(tree_cover_in_2); +tree_cover_initial = tree_cover_initial.unmask(-9999); +tree_cover_initial = tree_cover_initial.expression( + "((b('label_in_1')!=-9999)) ? (b('label_in_1'))"+ + ":((b('label_in_0')!=-9999) and (b('label_in_1')==-9999) and (b('label_in_2')!=-9999)) ? (b('label_in_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_initial = tree_cover_initial.rename(['label']); +tree_cover_initial = tree_cover_initial.updateMask(tree_cover_initial.neq(-9999)); +// print(tree_cover_initial); + + +var tree_cover_final = tree_cover_fi_0.addBands(tree_cover_fi_1).addBands(tree_cover_fi_2); +tree_cover_final = tree_cover_final.unmask(-9999); +tree_cover_final = tree_cover_final.expression( + "((b('label_fi_1')!=-9999)) ? (b('label_fi_1'))"+ + ":((b('label_fi_0')!=-9999) and (b('label_fi_1')==-9999) and (b('label_fi_2')!=-9999)) ? (b('label_fi_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_final = tree_cover_final.rename(['label']); +tree_cover_final = tree_cover_final.updateMask(tree_cover_final.neq(-9999)); + +Map.addLayer(tree_cover_initial, {palette: ['white']}, 'tree cover initial'); +Map.addLayer(tree_cover_final, {palette: ['white']}, 'tree cover final'); + + +// CH visualizations + +// var ch_in_0 = ee.ImageCollection(project_path+'/ch_' + year_in_0) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_in_0 = ee.ImageCollection(project_path+'/ch_' + year_in_0).filterBounds(aoi); + +ch_in_0 = ee.Algorithms.If( + ch_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_in_0.mean().clip(aoi) +); + +ch_in_0 = ee.Image(ch_in_0); + +// var ch_in_1 = ee.ImageCollection(project_path+'/ch_' + year_in_1) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_in_1 = ee.ImageCollection(project_path+'/ch_' + year_in_1).filterBounds(aoi); + +ch_in_1 = ee.Algorithms.If( + ch_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_in_1.mean().clip(aoi) +); + +ch_in_1 = ee.Image(ch_in_1); + +// var ch_in_2 = ee.ImageCollection(project_path+'/ch_' + year_in_2) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_in_2 = ee.ImageCollection(project_path+'/ch_' + year_in_2).filterBounds(aoi); + +ch_in_2 = ee.Algorithms.If( + ch_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_in_2.mean().clip(aoi) +); + +ch_in_2 = ee.Image(ch_in_2); + +// var ch_fi_0 = ee.ImageCollection(project_path+'/ch_' + year_fi_0) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_fi_0 = ee.ImageCollection(project_path+'/ch_' + year_fi_0).filterBounds(aoi); + +ch_fi_0 = ee.Algorithms.If( + ch_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_fi_0.mean().clip(aoi) +); + +ch_fi_0 = ee.Image(ch_fi_0); + +// var ch_fi_1 = ee.ImageCollection(project_path+'/ch_' + year_fi_1) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_fi_1 = ee.ImageCollection(project_path+'/ch_' + year_fi_1).filterBounds(aoi); + +ch_fi_1 = ee.Algorithms.If( + ch_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_fi_1.mean().clip(aoi) +); + +ch_fi_1 = ee.Image(ch_fi_1); + +// var ch_fi_2 = ee.ImageCollection(project_path+'/ch_' + year_fi_2) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_fi_2 = ee.ImageCollection(project_path+'/ch_' + year_fi_2).filterBounds(aoi); + +ch_fi_2 = ee.Algorithms.If( + ch_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_fi_2.mean().clip(aoi) +); + +ch_fi_2 = ee.Image(ch_fi_2); + +// var correction_img = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/corrections_ch') +// .filterBounds(aoi) +// .mode() +// .clip(aoi); + +// var correction_in_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]); + +var correction_in_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_0).filterBounds(aoi); + +correction_in_0 = ee.Algorithms.If( + correction_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_0.mode().clip(aoi).select(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]) +); +correction_in_0 = ee.Image(correction_in_0); + +// var correction_in_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]); +var correction_in_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_1).filterBounds(aoi); + +correction_in_1 = ee.Algorithms.If( + correction_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_1.mode().clip(aoi).select(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]) +); +correction_in_1 = ee.Image(correction_in_1); + +// var correction_in_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]); + +var correction_in_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_2).filterBounds(aoi); + +correction_in_2 = ee.Algorithms.If( + correction_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_2.mode().clip(aoi).select(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]) +); +correction_in_2 = ee.Image(correction_in_2); + +// var correction_fi_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]); + +var correction_fi_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_0).filterBounds(aoi); + +correction_fi_0 = ee.Algorithms.If( + correction_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_0.mode().clip(aoi).select(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]) +); +correction_fi_0 = ee.Image(correction_fi_0); + +// var correction_fi_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]); + +var correction_fi_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_1).filterBounds(aoi); + +correction_fi_1 = ee.Algorithms.If( + correction_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_1.mode().clip(aoi).select(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]) +); +correction_fi_1 = ee.Image(correction_fi_1); + +// var correction_fi_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]); + +var correction_fi_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_2).filterBounds(aoi); + +correction_fi_2 = ee.Algorithms.If( + correction_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_2.mode().clip(aoi).select(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]) +); +correction_fi_2 = ee.Image(correction_fi_2); + +// var correction_in_0 = correction_img.select(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]); +// var correction_in_1 = correction_img.select(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]); +// var correction_in_2 = correction_img.select(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]); +// var correction_fi_0 = correction_img.select(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]); +// var correction_fi_1 = correction_img.select(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]); +// var correction_fi_2 = correction_img.select(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]); + + +correction_in_0 = correction_in_0.rename(['ch_in_0', 'rh98_in_0', 'rh75_in_0', 'rh50_in_0']); +correction_in_1 = correction_in_1.rename(['ch_in_1', 'rh98_in_1', 'rh75_in_1', 'rh50_in_1']); +correction_in_2 = correction_in_2.rename(['ch_in_2', 'rh98_in_2', 'rh75_in_2', 'rh50_in_2']); +correction_fi_0 = correction_fi_0.rename(['ch_fi_0', 'rh98_fi_0', 'rh75_fi_0', 'rh50_fi_0']); +correction_fi_1 = correction_fi_1.rename(['ch_fi_1', 'rh98_fi_1', 'rh75_fi_1', 'rh50_fi_1']); +correction_fi_2 = correction_fi_2.rename(['ch_fi_2', 'rh98_fi_2', 'rh75_fi_2', 'rh50_fi_2']); + + +var ch_in_0_ch = ch_in_0.select('ch_class'); +ch_in_0_ch = ch_in_0_ch.addBands(correction_in_0); +ch_in_0_ch = ch_in_0_ch.unmask(-9999); +ch_in_0_ch = ch_in_0_ch.expression( + "((b('ch_in_0')!=b('ch_class')) and (b('ch_in_0')!=-9999)) ? (b('ch_in_0'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_in_0_ch = ch_in_0_ch.updateMask(ch_in_0_ch.neq(-9999)); +ch_in_0_ch = ch_in_0_ch.rename(['ch_in_0_ch']); + + +var ch_in_0_rh98 = ch_in_0.select('rh98_class'); +ch_in_0_rh98 = ch_in_0_rh98.addBands(correction_in_0); +ch_in_0_rh98 = ch_in_0_rh98.unmask(-9999); +ch_in_0_rh98 = ch_in_0_rh98.expression( + "((b('rh98_in_0')!=b('rh98_class')) and (b('rh98_in_0')!=-9999)) ? (b('rh98_in_0'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_in_0_rh98 = ch_in_0_rh98.updateMask(ch_in_0_rh98.neq(-9999)); +ch_in_0_rh98 = ch_in_0_rh98.rename(['ch_in_0_rh98']); + +var ch_in_0_rh75 = ch_in_0.select('rh75_class'); +ch_in_0_rh75 = ch_in_0_rh75.addBands(correction_in_0); +ch_in_0_rh75 = ch_in_0_rh75.unmask(-9999); +ch_in_0_rh75 = ch_in_0_rh75.expression( + "((b('rh75_in_0')!=b('rh75_class')) and (b('rh75_in_0')!=-9999)) ? (b('rh75_in_0'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_in_0_rh75 = ch_in_0_rh75.updateMask(ch_in_0_rh75.neq(-9999)); +ch_in_0_rh75 = ch_in_0_rh75.rename(['ch_in_0_rh75']);ch_in_0_rh75 + + +var ch_in_0_rh50 = ch_in_0.select('rh50_class'); +ch_in_0_rh50 = ch_in_0_rh50.addBands(correction_in_0); +ch_in_0_rh50 = ch_in_0_rh50.unmask(-9999); +ch_in_0_rh50 = ch_in_0_rh50.expression( + "((b('rh50_in_0')!=b('rh50_class')) and (b('rh50_in_0')!=-9999)) ? (b('rh50_in_0'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_in_0_rh50 = ch_in_0_rh50.updateMask(ch_in_0_rh50.neq(-9999)); +ch_in_0_rh50 = ch_in_0_rh50.rename(['ch_in_0_rh50']); + + +ch_in_0 = ch_in_0_ch.addBands(ch_in_0_rh98).addBands(ch_in_0_rh75).addBands(ch_in_0_rh50); + + +var ch_in_1_ch = ch_in_1.select('ch_class'); +ch_in_1_ch = ch_in_1_ch.addBands(correction_in_1); +ch_in_1_ch = ch_in_1_ch.unmask(-9999); +ch_in_1_ch = ch_in_1_ch.expression( + "((b('ch_in_1')!=b('ch_class')) and (b('ch_in_1')!=-9999)) ? (b('ch_in_1'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_in_1_ch = ch_in_1_ch.updateMask(ch_in_1_ch.neq(-9999)); +ch_in_1_ch = ch_in_1_ch.rename(['ch_in_1_ch']); + + +var ch_in_1_rh98 = ch_in_1.select('rh98_class'); +ch_in_1_rh98 = ch_in_1_rh98.addBands(correction_in_1); +ch_in_1_rh98 = ch_in_1_rh98.unmask(-9999); +ch_in_1_rh98 = ch_in_1_rh98.expression( + "((b('rh98_in_1')!=b('rh98_class')) and (b('rh98_in_1')!=-9999)) ? (b('rh98_in_1'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_in_1_rh98 = ch_in_1_rh98.updateMask(ch_in_1_rh98.neq(-9999)); +ch_in_1_rh98 = ch_in_1_rh98.rename(['ch_in_1_rh98']); + + +var ch_in_1_rh75 = ch_in_1.select('rh75_class'); +ch_in_1_rh75 = ch_in_1_rh75.addBands(correction_in_1); +ch_in_1_rh75 = ch_in_1_rh75.unmask(-9999); +ch_in_1_rh75 = ch_in_1_rh75.expression( + "((b('rh75_in_1')!=b('rh75_class')) and (b('rh75_in_1')!=-9999)) ? (b('rh75_in_1'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_in_1_rh75 = ch_in_1_rh75.updateMask(ch_in_1_rh75.neq(-9999)); +ch_in_1_rh75 = ch_in_1_rh75.rename(['ch_in_1_rh75']);ch_in_1_rh75 + + +var ch_in_1_rh50 = ch_in_1.select('rh50_class'); +ch_in_1_rh50 = ch_in_1_rh50.addBands(correction_in_1); +ch_in_1_rh50 = ch_in_1_rh50.unmask(-9999); +ch_in_1_rh50 = ch_in_1_rh50.expression( + "((b('rh50_in_1')!=b('rh50_class')) and (b('rh50_in_1')!=-9999)) ? (b('rh50_in_1'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_in_1_rh50 = ch_in_1_rh50.updateMask(ch_in_1_rh50.neq(-9999)); +ch_in_1_rh50 = ch_in_1_rh50.rename(['ch_in_1_rh50']); + + +ch_in_1 = ch_in_1_ch.addBands(ch_in_1_rh98).addBands(ch_in_1_rh75).addBands(ch_in_1_rh50); + +// print(ch_in_1); + +var ch_in_2_ch = ch_in_2.select('ch_class'); +ch_in_2_ch = ch_in_2_ch.addBands(correction_in_2); +ch_in_2_ch = ch_in_2_ch.unmask(-9999); +ch_in_2_ch = ch_in_2_ch.expression( + "((b('ch_in_2')!=b('ch_class')) and (b('ch_in_2')!=-9999)) ? (b('ch_in_2'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_in_2_ch = ch_in_2_ch.updateMask(ch_in_2_ch.neq(-9999)); +ch_in_2_ch = ch_in_2_ch.rename(['ch_in_2_ch']); + + +var ch_in_2_rh98 = ch_in_2.select('rh98_class'); +ch_in_2_rh98 = ch_in_2_rh98.addBands(correction_in_2); +ch_in_2_rh98 = ch_in_2_rh98.unmask(-9999); +ch_in_2_rh98 = ch_in_2_rh98.expression( + "((b('rh98_in_2')!=b('rh98_class')) and (b('rh98_in_2')!=-9999)) ? (b('rh98_in_2'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_in_2_rh98 = ch_in_2_rh98.updateMask(ch_in_2_rh98.neq(-9999)); +ch_in_2_rh98 = ch_in_2_rh98.rename(['ch_in_2_rh98']); + + +var ch_in_2_rh75 = ch_in_2.select('rh75_class'); +ch_in_2_rh75 = ch_in_2_rh75.addBands(correction_in_2); +ch_in_2_rh75 = ch_in_2_rh75.unmask(-9999); +ch_in_2_rh75 = ch_in_2_rh75.expression( + "((b('rh75_in_2')!=b('rh75_class')) and (b('rh75_in_2')!=-9999)) ? (b('rh75_in_2'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_in_2_rh75 = ch_in_2_rh75.updateMask(ch_in_2_rh75.neq(-9999)); +ch_in_2_rh75 = ch_in_2_rh75.rename(['ch_in_2_rh75']);ch_in_2_rh75 + + +var ch_in_2_rh50 = ch_in_2.select('rh50_class'); +ch_in_2_rh50 = ch_in_2_rh50.addBands(correction_in_2); +ch_in_2_rh50 = ch_in_2_rh50.unmask(-9999); +ch_in_2_rh50 = ch_in_2_rh50.expression( + "((b('rh50_in_2')!=b('rh50_class')) and (b('rh50_in_2')!=-9999)) ? (b('rh50_in_2'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_in_2_rh50 = ch_in_2_rh50.updateMask(ch_in_2_rh50.neq(-9999)); +ch_in_2_rh50 = ch_in_2_rh50.rename(['ch_in_2_rh50']); + + +ch_in_2 = ch_in_2_ch.addBands(ch_in_2_rh98).addBands(ch_in_2_rh75).addBands(ch_in_2_rh50); + + +var ch_fi_0_ch = ch_fi_0.select('ch_class'); +ch_fi_0_ch = ch_fi_0_ch.addBands(correction_fi_0); +ch_fi_0_ch = ch_fi_0_ch.unmask(-9999); +ch_fi_0_ch = ch_fi_0_ch.expression( + "((b('ch_fi_0')!=b('ch_class')) and (b('ch_fi_0')!=-9999)) ? (b('ch_fi_0'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_fi_0_ch = ch_fi_0_ch.updateMask(ch_fi_0_ch.neq(-9999)); +ch_fi_0_ch = ch_fi_0_ch.rename(['ch_fi_0_ch']); + + +var ch_fi_0_rh98 = ch_fi_0.select('rh98_class'); +ch_fi_0_rh98 = ch_fi_0_rh98.addBands(correction_fi_0); +ch_fi_0_rh98 = ch_fi_0_rh98.unmask(-9999); +ch_fi_0_rh98 = ch_fi_0_rh98.expression( + "((b('rh98_fi_0')!=b('rh98_class')) and (b('rh98_fi_0')!=-9999)) ? (b('rh98_fi_0'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_fi_0_rh98 = ch_fi_0_rh98.updateMask(ch_fi_0_rh98.neq(-9999)); +ch_fi_0_rh98 = ch_fi_0_rh98.rename(['ch_fi_0_rh98']); + + +var ch_fi_0_rh75 = ch_fi_0.select('rh75_class'); +ch_fi_0_rh75 = ch_fi_0_rh75.addBands(correction_fi_0); +ch_fi_0_rh75 = ch_fi_0_rh75.unmask(-9999); +ch_fi_0_rh75 = ch_fi_0_rh75.expression( + "((b('rh75_fi_0')!=b('rh75_class')) and (b('rh75_fi_0')!=-9999)) ? (b('rh75_fi_0'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_fi_0_rh75 = ch_fi_0_rh75.updateMask(ch_fi_0_rh75.neq(-9999)); +ch_fi_0_rh75 = ch_fi_0_rh75.rename(['ch_fi_0_rh75']);ch_fi_0_rh75 + + +var ch_fi_0_rh50 = ch_fi_0.select('rh50_class'); +ch_fi_0_rh50 = ch_fi_0_rh50.addBands(correction_fi_0); +ch_fi_0_rh50 = ch_fi_0_rh50.unmask(-9999); +ch_fi_0_rh50 = ch_fi_0_rh50.expression( + "((b('rh50_fi_0')!=b('rh50_class')) and (b('rh50_fi_0')!=-9999)) ? (b('rh50_fi_0'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_fi_0_rh50 = ch_fi_0_rh50.updateMask(ch_fi_0_rh50.neq(-9999)); +ch_fi_0_rh50 = ch_fi_0_rh50.rename(['ch_fi_0_rh50']); + + +ch_fi_0 = ch_fi_0_ch.addBands(ch_fi_0_rh98).addBands(ch_fi_0_rh75).addBands(ch_fi_0_rh50); + + +var ch_fi_1_ch = ch_fi_1.select('ch_class'); +ch_fi_1_ch = ch_fi_1_ch.addBands(correction_fi_1); +ch_fi_1_ch = ch_fi_1_ch.unmask(-9999); +ch_fi_1_ch = ch_fi_1_ch.expression( + "((b('ch_fi_1')!=b('ch_class')) and (b('ch_fi_1')!=-9999)) ? (b('ch_fi_1'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_fi_1_ch = ch_fi_1_ch.updateMask(ch_fi_1_ch.neq(-9999)); +ch_fi_1_ch = ch_fi_1_ch.rename(['ch_fi_1_ch']); + + +var ch_fi_1_rh98 = ch_fi_1.select('rh98_class'); +ch_fi_1_rh98 = ch_fi_1_rh98.addBands(correction_fi_1); +ch_fi_1_rh98 = ch_fi_1_rh98.unmask(-9999); +ch_fi_1_rh98 = ch_fi_1_rh98.expression( + "((b('rh98_fi_1')!=b('rh98_class')) and (b('rh98_fi_1')!=-9999)) ? (b('rh98_fi_1'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_fi_1_rh98 = ch_fi_1_rh98.updateMask(ch_fi_1_rh98.neq(-9999)); +ch_fi_1_rh98 = ch_fi_1_rh98.rename(['ch_fi_1_rh98']); + + +var ch_fi_1_rh75 = ch_fi_1.select('rh75_class'); +ch_fi_1_rh75 = ch_fi_1_rh75.addBands(correction_fi_1); +ch_fi_1_rh75 = ch_fi_1_rh75.unmask(-9999); +ch_fi_1_rh75 = ch_fi_1_rh75.expression( + "((b('rh75_fi_1')!=b('rh75_class')) and (b('rh75_fi_1')!=-9999)) ? (b('rh75_fi_1'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_fi_1_rh75 = ch_fi_1_rh75.updateMask(ch_fi_1_rh75.neq(-9999)); +ch_fi_1_rh75 = ch_fi_1_rh75.rename(['ch_fi_1_rh75']);ch_fi_1_rh75 + + +var ch_fi_1_rh50 = ch_fi_1.select('rh50_class'); +ch_fi_1_rh50 = ch_fi_1_rh50.addBands(correction_fi_1); +ch_fi_1_rh50 = ch_fi_1_rh50.unmask(-9999); +ch_fi_1_rh50 = ch_fi_1_rh50.expression( + "((b('rh50_fi_1')!=b('rh50_class')) and (b('rh50_fi_1')!=-9999)) ? (b('rh50_fi_1'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_fi_1_rh50 = ch_fi_1_rh50.updateMask(ch_fi_1_rh50.neq(-9999)); +ch_fi_1_rh50 = ch_fi_1_rh50.rename(['ch_fi_1_rh50']); + + +ch_fi_1 = ch_fi_1_ch.addBands(ch_fi_1_rh98).addBands(ch_fi_1_rh75).addBands(ch_fi_1_rh50); + + +var ch_fi_2_ch = ch_fi_2.select('ch_class'); +ch_fi_2_ch = ch_fi_2_ch.addBands(correction_fi_2); +ch_fi_2_ch = ch_fi_2_ch.unmask(-9999); +ch_fi_2_ch = ch_fi_2_ch.expression( + "((b('ch_fi_2')!=b('ch_class')) and (b('ch_fi_2')!=-9999)) ? (b('ch_fi_2'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_fi_2_ch = ch_fi_2_ch.updateMask(ch_fi_2_ch.neq(-9999)); +ch_fi_2_ch = ch_fi_2_ch.rename(['ch_fi_2_ch']); + + +var ch_fi_2_rh98 = ch_fi_2.select('rh98_class'); +ch_fi_2_rh98 = ch_fi_2_rh98.addBands(correction_fi_2); +ch_fi_2_rh98 = ch_fi_2_rh98.unmask(-9999); +ch_fi_2_rh98 = ch_fi_2_rh98.expression( + "((b('rh98_fi_2')!=b('rh98_class')) and (b('rh98_fi_2')!=-9999)) ? (b('rh98_fi_2'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_fi_2_rh98 = ch_fi_2_rh98.updateMask(ch_fi_2_rh98.neq(-9999)); +ch_fi_2_rh98 = ch_fi_2_rh98.rename(['ch_fi_2_rh98']); + + +var ch_fi_2_rh75 = ch_fi_2.select('rh75_class'); +ch_fi_2_rh75 = ch_fi_2_rh75.addBands(correction_fi_2); +ch_fi_2_rh75 = ch_fi_2_rh75.unmask(-9999); +ch_fi_2_rh75 = ch_fi_2_rh75.expression( + "((b('rh75_fi_2')!=b('rh75_class')) and (b('rh75_fi_2')!=-9999)) ? (b('rh75_fi_2'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_fi_2_rh75 = ch_fi_2_rh75.updateMask(ch_fi_2_rh75.neq(-9999)); +ch_fi_2_rh75 = ch_fi_2_rh75.rename(['ch_fi_2_rh75']);ch_fi_2_rh75 + + +var ch_fi_2_rh50 = ch_fi_2.select('rh50_class'); +ch_fi_2_rh50 = ch_fi_2_rh50.addBands(correction_fi_2); +ch_fi_2_rh50 = ch_fi_2_rh50.unmask(-9999); +ch_fi_2_rh50 = ch_fi_2_rh50.expression( + "((b('rh50_fi_2')!=b('rh50_class')) and (b('rh50_fi_2')!=-9999)) ? (b('rh50_fi_2'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_fi_2_rh50 = ch_fi_2_rh50.updateMask(ch_fi_2_rh50.neq(-9999)); +ch_fi_2_rh50 = ch_fi_2_rh50.rename(['ch_fi_2_rh50']); + + +ch_fi_2 = ch_fi_2_ch.addBands(ch_fi_2_rh98).addBands(ch_fi_2_rh75).addBands(ch_fi_2_rh50); + +var initial_image = ch_in_0.addBands(ch_in_1).addBands(ch_in_2); +// print(initial_image); + +var initial_image_ch = initial_image.select(['ch_in_0_ch', 'ch_in_1_ch', 'ch_in_2_ch']); +initial_image_ch = initial_image_ch.unmask(-9999); +initial_image_ch = initial_image_ch.expression( + "((b('ch_in_0_ch')==b('ch_in_2_ch')) and (b('ch_in_0_ch')!=-9999)) ? (b('ch_in_0_ch'))"+ + ":((b('ch_in_0_ch')==-9999 or b('ch_in_2_ch')==-9999) and (b('ch_in_1_ch')!=-9999)) ? (b('ch_in_1_ch'))"+ + ":((b('ch_in_0_ch')!=-9999 and b('ch_in_1_ch')==-9999 and b('ch_in_2_ch')==-9999)) ? (b('ch_in_0_ch'))"+ + ":((b('ch_in_1_ch')==-9999 and b('ch_in_2_ch')!=-9999) and (b('ch_in_0_ch')!=b('ch_in_2_ch'))) ? (b('ch_in_2_ch'))"+ + ":(b('ch_in_1_ch'))" + ).clip(aoi); +initial_image_ch = initial_image_ch.rename(['ch_class']); +initial_image_ch = initial_image_ch.updateMask(initial_image_ch.neq(-9999)); + +var initial_image_rh98 = initial_image.select(['ch_in_0_rh98', 'ch_in_1_rh98', 'ch_in_2_rh98']); +initial_image_rh98 = initial_image_rh98.unmask(-9999); +initial_image_rh98 = initial_image_rh98.expression( + "((b('ch_in_0_rh98')==b('ch_in_2_rh98')) and (b('ch_in_0_rh98')!=-9999)) ? (b('ch_in_0_rh98'))"+ + ":((b('ch_in_0_rh98')==-9999 or b('ch_in_2_rh98')==-9999) and (b('ch_in_1_rh98')!=-9999)) ? (b('ch_in_1_rh98'))"+ + ":((b('ch_in_0_rh98')!=-9999 and b('ch_in_1_rh98')==-9999 and b('ch_in_2_rh98')==-9999)) ? (b('ch_in_0_rh98'))"+ + ":((b('ch_in_1_rh98')==-9999 and b('ch_in_2_rh98')!=-9999) and (b('ch_in_0_rh98')!=b('ch_in_2_rh98'))) ? (b('ch_in_2_rh98'))"+ + ":(b('ch_in_1_rh98'))" + ).clip(aoi); +initial_image_rh98 = initial_image_rh98.rename(['rh98_class']); +initial_image_rh98 = initial_image_rh98.updateMask(initial_image_rh98.neq(-9999)); + +var initial_image_rh75 = initial_image.select(['ch_in_0_rh75', 'ch_in_1_rh75', 'ch_in_2_rh75']); +initial_image_rh75 = initial_image_rh75.unmask(-9999); +initial_image_rh75 = initial_image_rh75.expression( + "((b('ch_in_0_rh75')==b('ch_in_2_rh75')) and (b('ch_in_0_rh75')!=-9999)) ? (b('ch_in_0_rh75'))"+ + ":((b('ch_in_0_rh75')==-9999 or b('ch_in_2_rh75')==-9999) and (b('ch_in_1_rh75')!=-9999)) ? (b('ch_in_1_rh75'))"+ + ":((b('ch_in_0_rh75')!=-9999 and b('ch_in_1_rh75')==-9999 and b('ch_in_2_rh75')==-9999)) ? (b('ch_in_0_rh75'))"+ + ":((b('ch_in_1_rh75')==-9999 and b('ch_in_2_rh75')!=-9999) and (b('ch_in_0_rh75')!=b('ch_in_2_rh75'))) ? (b('ch_in_2_rh75'))"+ + ":(b('ch_in_1_rh75'))" + ).clip(aoi); +initial_image_rh75 = initial_image_rh75.rename(['rh75_class']); +initial_image_rh75 = initial_image_rh75.updateMask(initial_image_rh75.neq(-9999)); + +var initial_image_rh50 = initial_image.select(['ch_in_0_rh50', 'ch_in_1_rh50', 'ch_in_2_rh50']); +initial_image_rh50 = initial_image_rh50.unmask(-9999); +initial_image_rh50 = initial_image_rh50.expression( + "((b('ch_in_0_rh50')==b('ch_in_2_rh50')) and (b('ch_in_0_rh50')!=-9999)) ? (b('ch_in_0_rh50'))"+ + ":((b('ch_in_0_rh50')==-9999 or b('ch_in_2_rh50')==-9999) and (b('ch_in_1_rh50')!=-9999)) ? (b('ch_in_1_rh50'))"+ + ":((b('ch_in_0_rh50')!=-9999 and b('ch_in_1_rh50')==-9999 and b('ch_in_2_rh50')==-9999)) ? (b('ch_in_0_rh50'))"+ + ":((b('ch_in_1_rh50')==-9999 and b('ch_in_2_rh50')!=-9999) and (b('ch_in_0_rh50')!=b('ch_in_2_rh50'))) ? (b('ch_in_2_rh50'))"+ + ":(b('ch_in_1_rh50'))" + ).clip(aoi); +initial_image_rh50 = initial_image_rh50.rename(['rh50_class']); +initial_image_rh50 = initial_image_rh50.updateMask(initial_image_rh50.neq(-9999)); + +initial_image_ch = initial_image_ch.addBands(tree_cover_initial); +initial_image_ch = initial_image_ch.unmask(-9999); + +// 4 denotes missing data +initial_image_ch = initial_image_ch.expression( + "((b('ch_class') == -9999) and (b('label') != -9999)) ? (4)"+ + ":b('ch_class')" + ).clip(aoi); +initial_image_ch = initial_image_ch.rename(['ch_class']); +initial_image_ch = initial_image_ch.updateMask(initial_image_ch.neq(-9999)); + +initial_image = initial_image_ch.addBands(initial_image_rh98).addBands(initial_image_rh75) + .addBands(initial_image_rh50); + + +var final_image = ch_fi_0.addBands(ch_fi_1).addBands(ch_fi_2); + +var final_image_ch = final_image.select(['ch_fi_0_ch', 'ch_fi_1_ch', 'ch_fi_2_ch']); +final_image_ch = final_image_ch.unmask(-9999); +final_image_ch = final_image_ch.expression( + "((b('ch_fi_0_ch')==b('ch_fi_2_ch')) and (b('ch_fi_0_ch')!=-9999)) ? (b('ch_fi_0_ch'))"+ + ":((b('ch_fi_0_ch')==-9999 or b('ch_fi_2_ch')==-9999) and (b('ch_fi_1_ch')!=-9999)) ? (b('ch_fi_1_ch'))"+ + ":((b('ch_fi_0_ch')!=-9999 and b('ch_fi_1_ch')==-9999 and b('ch_fi_2_ch')==-9999)) ? (b('ch_fi_0_ch'))"+ + ":((b('ch_fi_1_ch')==-9999 and b('ch_fi_2_ch')!=-9999) and (b('ch_fi_0_ch')!=b('ch_fi_2_ch'))) ? (b('ch_fi_2_ch'))"+ + ":(b('ch_fi_1_ch'))" + ).clip(aoi); +final_image_ch = final_image_ch.rename(['ch_class']); +final_image_ch = final_image_ch.updateMask(final_image_ch.neq(-9999)); + +var final_image_rh98 = final_image.select(['ch_fi_0_rh98', 'ch_fi_1_rh98', 'ch_fi_2_rh98']); +final_image_rh98 = final_image_rh98.unmask(-9999); +final_image_rh98 = final_image_rh98.expression( + "((b('ch_fi_0_rh98')==b('ch_fi_2_rh98')) and (b('ch_fi_0_rh98')!=-9999)) ? (b('ch_fi_0_rh98'))"+ + ":((b('ch_fi_0_rh98')==-9999 or b('ch_fi_2_rh98')==-9999) and (b('ch_fi_1_rh98')!=-9999)) ? (b('ch_fi_1_rh98'))"+ + ":((b('ch_fi_0_rh98')!=-9999 and b('ch_fi_1_rh98')==-9999 and b('ch_fi_2_rh98')==-9999)) ? (b('ch_fi_0_rh98'))"+ + ":((b('ch_fi_1_rh98')==-9999 and b('ch_fi_2_rh98')!=-9999) and (b('ch_fi_0_rh98')!=b('ch_fi_2_rh98'))) ? (b('ch_fi_2_rh98'))"+ + ":(b('ch_fi_1_rh98'))" + ).clip(aoi); +final_image_rh98 = final_image_rh98.rename(['rh98_class']); +final_image_rh98 = final_image_rh98.updateMask(final_image_rh98.neq(-9999)); + +var final_image_rh75 = final_image.select(['ch_fi_0_rh75', 'ch_fi_1_rh75', 'ch_fi_2_rh75']); +final_image_rh75 = final_image_rh75.unmask(-9999); +final_image_rh75 = final_image_rh75.expression( + "((b('ch_fi_0_rh75')==b('ch_fi_2_rh75')) and (b('ch_fi_0_rh75')!=-9999)) ? (b('ch_fi_0_rh75'))"+ + ":((b('ch_fi_0_rh75')==-9999 or b('ch_fi_2_rh75')==-9999) and (b('ch_fi_1_rh75')!=-9999)) ? (b('ch_fi_1_rh75'))"+ + ":((b('ch_fi_0_rh75')!=-9999 and b('ch_fi_1_rh75')==-9999 and b('ch_fi_2_rh75')==-9999)) ? (b('ch_fi_0_rh75'))"+ + ":((b('ch_fi_1_rh75')==-9999 and b('ch_fi_2_rh75')!=-9999) and (b('ch_fi_0_rh75')!=b('ch_fi_2_rh75'))) ? (b('ch_fi_2_rh75'))"+ + ":(b('ch_fi_1_rh75'))" + ).clip(aoi); +final_image_rh75 = final_image_rh75.rename(['rh75_class']); +final_image_rh75 = final_image_rh75.updateMask(final_image_rh75.neq(-9999)); + +var final_image_rh50 = final_image.select(['ch_fi_0_rh50', 'ch_fi_1_rh50', 'ch_fi_2_rh50']); +final_image_rh50 = final_image_rh50.unmask(-9999); +final_image_rh50 = final_image_rh50.expression( + "((b('ch_fi_0_rh50')==b('ch_fi_2_rh50')) and (b('ch_fi_0_rh50')!=-9999)) ? (b('ch_fi_0_rh50'))"+ + ":((b('ch_fi_0_rh50')==-9999 or b('ch_fi_2_rh50')==-9999) and (b('ch_fi_1_rh50')!=-9999)) ? (b('ch_fi_1_rh50'))"+ + ":((b('ch_fi_0_rh50')!=-9999 and b('ch_fi_1_rh50')==-9999 and b('ch_fi_2_rh50')==-9999)) ? (b('ch_fi_0_rh50'))"+ + ":((b('ch_fi_1_rh50')==-9999 and b('ch_fi_2_rh50')!=-9999) and (b('ch_fi_0_rh50')!=b('ch_fi_2_rh50'))) ? (b('ch_fi_2_rh50'))"+ + ":(b('ch_fi_1_rh50'))" + ).clip(aoi); +final_image_rh50 = final_image_rh50.rename(['rh50_class']); +final_image_rh50 = final_image_rh50.updateMask(final_image_rh50.neq(-9999)); + +final_image_ch = final_image_ch.addBands(tree_cover_final); +final_image_ch = final_image_ch.unmask(-9999); +// 4 denotes missing data +final_image_ch = final_image_ch.expression( + "((b('ch_class') == -9999) and (b('label') != -9999)) ? (4)"+ + ":b('ch_class')" + ).clip(aoi); +final_image_ch = final_image_ch.rename(['ch_class']); +final_image_ch = final_image_ch.updateMask(final_image_ch.neq(-9999)); + +final_image = final_image_ch.addBands(final_image_rh98).addBands(final_image_rh75) + .addBands(final_image_rh50); + + +var palette =['FFA500', 'DEE64C', 'DEE64C','007500', '000000']; +Map.addLayer(initial_image, {bands:['ch_class'], min: 0, max: 4, palette: palette}, 'initial modal image ch'); +Map.addLayer(final_image, {bands:['ch_class'], min: 0, max: 4, palette: palette}, 'final modal image ch'); + +initial_image = initial_image.unmask(-9999); +initial_image = initial_image.expression( + "((b('ch_class') == 4)) ? (8)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (0)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (1)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (2)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (3)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (4)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (5)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (6)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (7)"+ + ": ((b('rh50_class') != -9999) or (b('rh75_class') != -9999) and (b('rh98_class') != -9999)) ? (8)"+ + ":-9999" + ).clip(aoi); +initial_image = initial_image.updateMask(initial_image.neq(-9999)); +initial_image = initial_image.rename(['ch_class']); + + +final_image = final_image.unmask(-9999); +final_image = final_image.expression( + "((b('ch_class') == 4)) ? (8)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (0)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (1)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (2)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (3)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (4)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (5)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (6)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (7)"+ + ": ((b('rh50_class') != -9999) or (b('rh75_class') != -9999) and (b('rh98_class') != -9999)) ? (8)"+ + ":-9999" + ).clip(aoi); +final_image = final_image.updateMask(final_image.neq(-9999)); +final_image = final_image.rename(['ch_class']); + +var palette =['FFA500', 'FFA500', 'DEE64C', 'DEE64C', 'DEE64C', 'DEE64C', '007500', '007500', '000000']; +Map.addLayer(initial_image, {bands:['ch_class'], min: 0, max: 8, palette: palette}, 'initial modal image ch'); +Map.addLayer(final_image, {bands:['ch_class'], min: 0, max: 8, palette: palette}, 'final modal image ch'); + + +var change = initial_image.addBands(final_image); +change = change.unmask(-9999); +// 3 denotes missing data +change = change.expression( + "((b('ch_class') == -9999) and (b('ch_class_1') != -9999)) ? (2)" + + ": ((b('ch_class') != -9999) and (b('ch_class_1') == -9999)) ? (-2)"+ + ": ((b('ch_class') == 8) or (b('ch_class_1') == 8)) ? (3)" + + ": ((b('ch_class') == b('ch_class_1')) and (b('ch_class') != -9999)) ? (0)" + + ": ((b('ch_class') < b('ch_class_1')) and (b('ch_class') != -9999) and ((b('ch_class') <= 1) or (b('ch_class') >= 4))) ? (1)" + + ": ((b('ch_class') > b('ch_class_1')) and (b('ch_class_1') != -9999)) ? (-1)" + + ": ((b('ch_class') == 2) and ((b('ch_class_1') == 3) or (b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": ((b('ch_class') == 3) and ((b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": (((b('ch_class') == 2) or (b('ch_class') == 3)) and ((b('ch_class_1') == 4) or (b('ch_class_1') == 5))) ? (-1)"+ + ":-9999" +).clip(aoi); +change = change.updateMask(change.neq(-9999)); +// var palette = ['red', 'orange', 'white', 'lightgreen', 'green', 'black']; +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer ch'); + +// Export modal initial image +Export.image.toAsset({ + image: initial_image, + description: 'ch_' + year_in_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ch_' + year_in_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// Export modal final image +Export.image.toAsset({ + image: final_image, + description: 'ch_' + year_fi_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ch_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// // Export CH change image +// Export.image.toAsset({ +// image: change, +// description: 'ch_change_' + agroclimaticZoneAcronymDict[acz], +// assetId: project_path+'/ch_change_' + year_in_1 + '_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], +// region: aoi, +// scale: 25, +// crs: 'EPSG:4326', +// maxPixels: 10000000000 +// }); + +// Quantifying CH change classes statistics +// var change_chm = change; + +// for (var c=0; c year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2023; + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +// var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// var fc = ee.FeatureCollection('users/mtpictd/world_boundary'); +// fc = fc.filter(ee.Filter.eq('Name', 'India')); +// var aoi = fc.geometry(); + +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); +var project_path = 'projects/corestack-trees/assets/tree_characteristics'; + +var year_in_1 = String(year_1); +var year_fi_1 = String(year_2); + + +var change_ccd = ee.ImageCollection(project_path+'/ccd_change_' + year_in_1 + '_' + year_fi_1) + .filterBounds(aoi) + .mean() + .clip(aoi); + +var change_chm = ee.ImageCollection(project_path+'/ch_change_' + year_in_1 + '_' + year_fi_1) + .filterBounds(aoi) + .mean() + .clip(aoi); + + +var palette = ['red', 'orange', 'white', 'lightgreen', 'green', 'black']; +Map.addLayer(change_ccd, {min: -2, max: 3, palette: palette}, 'ccd change'); +Map.addLayer(change_chm, {min: -2, max: 3, palette: palette}, 'ch change'); + + +var overall_change = change_ccd.addBands(change_chm); + +overall_change = overall_change.unmask(-9999); +overall_change = overall_change.expression( + "((b('constant') == 3) or (b('constant_1') == 3)) ? (5)"+ + ": ((b('constant') == -2) or (b('constant') == 2)) ? (b('constant'))"+ + ": ((b('constant') == 1) and (b('constant_1') == 1)) ? (1)"+ + ": ((b('constant') == 1) and (b('constant_1') == -1)) ? (3)"+ + ": ((b('constant') == 1) and (b('constant_1') == 0)) ? (1)"+ + ": ((b('constant') == -1) and (b('constant_1') == 1)) ? (4)"+ + ": ((b('constant') == -1) and (b('constant_1') == -1)) ? (-1)"+ + ": ((b('constant') == -1) and (b('constant_1') == 0)) ? (-1)"+ + ": ((b('constant') == 0) and (b('constant_1') == 1)) ? (1)"+ + ": ((b('constant') == 0) and (b('constant_1') == -1)) ? (-1)"+ + ": ((b('constant') == 0) and (b('constant_1') == 0)) ? (0)"+ + ": -9999" +).clip(aoi); +overall_change = overall_change.updateMask(overall_change.neq(-9999)); +// print(overall_change); +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', 'DEE64C', 'DEE64C', '000000']; +Map.addLayer(overall_change, {min: -2, max: 5, palette: palette}, 'overall change'); + +Export.image.toAsset({ + image: overall_change, + description: 'overall_change_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/overall_change_' + year_in_1 + '_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + + +// // Set the change list according to the following: +// // -2 -> Deforestation +// // -1 -> Degradation +// // 0 -> No Change +// // 1 -> Improvement +// // 2 -> Afforestation +// // 3 denotes partial improvement and degradation where ccd improves and ch degrades +// // 4 denotes partial improvement and degradation where ccd degrades and ch improves +// // 5 -> Missing Data + +// // var change_list = [2, -2, 1, -1, 3, 4, 0, 5]; +// var change_list = [-1, 3, 4]; + +// for (var c=0; c Date: Thu, 11 Jun 2026 17:46:12 +0530 Subject: [PATCH 07/38] Hotfix/email timeout (#1010) * patched name * email timeout increased to 15 minutes --- nrm_app/settings.py | 2 +- plans/tests.py | 12 ++++++++++++ plans/utils.py | 24 +++++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 19a80786..4139d003 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -352,7 +352,7 @@ def resolve_env_path(name, default="", *, trailing_sep=False): EMAIL_USE_TLS = False EMAIL_HOST_USER = env("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") -EMAIL_TIMEOUT = 30 +EMAIL_TIMEOUT = 900 PASSWORD_RESET_TIMEOUT = 259200 # 3 days in seconds GEOSERVER_URL = env("GEOSERVER_URL", default="") diff --git a/plans/tests.py b/plans/tests.py index bb68993b..d59c6045 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -471,6 +471,18 @@ def test_block_name_with_spaces_matches_underscore_block_param(self): self.assertTrue(result) + def test_block_name_with_underscores_matches_underscore_block_param(self): + _create_settlement(plan_id="42", block_name="test_block", settlement_id="SETT004") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_id"], "SETT004") + def test_only_matching_plan_id_returned(self): _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") diff --git a/plans/utils.py b/plans/utils.py index 7e96cfcf..f4f51403 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -5,6 +5,7 @@ import dateutil.parser import requests +from django.db.models import Q from dpr.models import ( ODK_settlement, ODK_well, ODK_waterbody, @@ -37,6 +38,27 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() + +def _block_filter_q(block): + """ + Accept both historical block-name storage styles in the DB: + `foo bar` and `foo_bar`. + """ + raw = (block or "").strip() + if not raw: + return Q() + + variants = { + raw, + raw.replace("_", " ").strip(), + raw.replace(" ", "_").strip(), + } + + query = Q() + for variant in variants: + query |= Q(block_name__icontains=variant) + return query + _RESOURCE_TYPES_FLAT_HEADER = frozenset({ "settlement", "well", "waterbody", "cropping", }) @@ -603,7 +625,7 @@ def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) if has_block_col: - qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) + qs = qs.filter(_block_filter_q(block)) raw_rows = list(qs.values(data_field, *projection_fields)) logger.info( From 839493e65fbea7036a080a4f5857308a68eb78fa Mon Sep 17 00:00:00 2001 From: Aman Verma Date: Thu, 11 Jun 2026 17:51:57 +0530 Subject: [PATCH 08/38] Revert "Hotfix/email timeout (#1010)" (#1012) This reverts commit 0bfb37afbf7e93c572388374c1ea1d917af44bbc. --- nrm_app/settings.py | 2 +- plans/tests.py | 12 ------------ plans/utils.py | 24 +----------------------- 3 files changed, 2 insertions(+), 36 deletions(-) diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 4139d003..19a80786 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -352,7 +352,7 @@ def resolve_env_path(name, default="", *, trailing_sep=False): EMAIL_USE_TLS = False EMAIL_HOST_USER = env("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") -EMAIL_TIMEOUT = 900 +EMAIL_TIMEOUT = 30 PASSWORD_RESET_TIMEOUT = 259200 # 3 days in seconds GEOSERVER_URL = env("GEOSERVER_URL", default="") diff --git a/plans/tests.py b/plans/tests.py index d59c6045..bb68993b 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -471,18 +471,6 @@ def test_block_name_with_spaces_matches_underscore_block_param(self): self.assertTrue(result) - def test_block_name_with_underscores_matches_underscore_block_param(self): - _create_settlement(plan_id="42", block_name="test_block", settlement_id="SETT004") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_id"], "SETT004") - def test_only_matching_plan_id_returned(self): _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") diff --git a/plans/utils.py b/plans/utils.py index f4f51403..7e96cfcf 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -5,7 +5,6 @@ import dateutil.parser import requests -from django.db.models import Q from dpr.models import ( ODK_settlement, ODK_well, ODK_waterbody, @@ -38,27 +37,6 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() - -def _block_filter_q(block): - """ - Accept both historical block-name storage styles in the DB: - `foo bar` and `foo_bar`. - """ - raw = (block or "").strip() - if not raw: - return Q() - - variants = { - raw, - raw.replace("_", " ").strip(), - raw.replace(" ", "_").strip(), - } - - query = Q() - for variant in variants: - query |= Q(block_name__icontains=variant) - return query - _RESOURCE_TYPES_FLAT_HEADER = frozenset({ "settlement", "well", "waterbody", "cropping", }) @@ -625,7 +603,7 @@ def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) if has_block_col: - qs = qs.filter(_block_filter_q(block)) + qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) raw_rows = list(qs.values(data_field, *projection_fields)) logger.info( From 1f4823c00572f914f385ddd80bef39f08d0a40b1 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Thu, 11 Jun 2026 18:06:23 +0530 Subject: [PATCH 09/38] check missing layers and send mail (#1007) * check missing layers and send mail * add caching * increase email timeout --- computing/api.py | 17 ++- computing/urls.py | 4 + computing/utils.py | 46 +++++++ computing/views.py | 313 ++++++++++++++++++++++++++++++++------------ nrm_app/settings.py | 3 +- 5 files changed, 297 insertions(+), 86 deletions(-) diff --git a/computing/api.py b/computing/api.py index 7abeb36c..f30348f0 100644 --- a/computing/api.py +++ b/computing/api.py @@ -69,7 +69,12 @@ from .surface_water_bodies.merge_swb_ponds import merge_swb_ponds from utilities.auth_check_decorator import api_security_check from computing.layer_dependency.layer_generation_in_order import layer_generate_map -from .views import layer_status, get_layers_of_workspace, check_missing_layers +from .views import ( + layer_status, + get_layers_of_workspace, + missing_layer_for_all_workspace, + clear_layer_cache, +) from .misc.lcw_conflict import generate_lcw_conflict_data from .misc.agroecological_space import generate_agroecological_data from .misc.factory_csr import generate_factory_csr_data @@ -1877,14 +1882,20 @@ def sync_layer_remote(request): @schema(None) def missing_layers(request): try: - workspace = request.query_params.get("workspace").lower() - result = check_missing_layers(workspace) + result = missing_layer_for_all_workspace() return Response({"result": result}, status=status.HTTP_200_OK) except Exception as e: print("Exception in get_layers_for_workspace api :: ", e) return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) +@api_view(["GET"]) +@schema(None) +def refresh_layer_cache(request, workspace=None): + clear_layer_cache(workspace) + return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) + + @api_view(["POST"]) @schema(None) def generate_fabdem_layer(request): diff --git a/computing/urls.py b/computing/urls.py index a829c83c..d450e737 100644 --- a/computing/urls.py +++ b/computing/urls.py @@ -238,4 +238,8 @@ api.generate_canal_vector, name="generate-canal-vector", ), + path("refresh_cache/", api.refresh_layer_cache, name="refresh_cache"), + path( + "refresh_cache//", api.refresh_layer_cache, name="refresh_cache" + ), ] diff --git a/computing/utils.py b/computing/utils.py index b14d063c..99393b48 100644 --- a/computing/utils.py +++ b/computing/utils.py @@ -41,6 +41,8 @@ valid_gee_text, ) from utilities.geoserver_utils import Geoserver +from django.core.mail import EmailMessage +import time logger = logging.getLogger(__name__) @@ -1030,3 +1032,47 @@ def update_layer_sync_status( except Exception as e: print(f"Error updating layer sync status: {e}") + + +# send missing layer to recipient email +def send_missing_layers_report(result: dict, recipients: list = None) -> bool: + if recipients is None: + recipients = getattr(settings, "MISSING_LAYER_RECIPIENTS", []) + + if isinstance(recipients, str): + recipients = [recipients] + + if not recipients: + logger.error("No recipients configured for missing layers report.") + return False + + try: + email = EmailMessage( + subject="Missing Layers Report", + body=json.dumps(result, indent=4), + from_email=settings.EMAIL_HOST_USER, + to=recipients, + ) + email.send() + logger.info(f"Missing layers report sent to {recipients}") + return True + except Exception as e: + logger.exception(f"Failed to send missing layers report: {e}") + return False + + +def _is_cache_valid(cache: dict, workspace: str) -> bool: + if workspace not in cache: + return False + age = time.time() - cache[workspace]["cached_at"] + if age > 3600: + logger.info(f"Cache expired for {workspace} (age: {int(age)}s)") + return False + return True + + +def _set_cache(cache: dict, workspace: str, data: set): + cache[workspace] = { + "data": data, + "cached_at": time.time(), + } diff --git a/computing/views.py b/computing/views.py index 88dd6982..d1a075b3 100644 --- a/computing/views.py +++ b/computing/views.py @@ -7,10 +7,18 @@ from computing.models import * from utilities.geoserver_utils import Geoserver import json -import os from django.conf import settings from pathlib import Path from utilities.constants import GEOSERVER_BASE +from utilities.logger import setup_logger +from concurrent.futures import ThreadPoolExecutor, as_completed +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +from rest_framework.response import Response +from rest_framework import status +from .utils import send_missing_layers_report, _is_cache_valid, _set_cache + +logger = setup_logger(__name__) def get_url(geoserver_url, workspace, layer_name): @@ -201,94 +209,200 @@ def get_layers_of_workspace(self, workspace): return [] -def check_missing_layers(workspace): - missing_layers = [] - print(f"{workspace=}") - workspace_config = load_workspace_config() - workspace_types = get_workspace_types(workspace) - print(f"Found types: {workspace_types}") - if not workspace_types: - print(f"No config found for workspace: {workspace}") - return {"no config found": []} - layer_config = get_layer_config_by_type( - workspace_config, workspace, workspace_types - ) - # fetch raster layers once - available_raster_layers = valid_raster_layers_for_workspace(workspace) - count = 0 - active_tehsils = TehsilSOI.objects.filter(active_status=True) - for tehsil in active_tehsils: - state = tehsil.district.state.state_name - district = tehsil.district.district_name - tehsil = tehsil.tehsil_name - district_name = valid_gee_text(district.lower()) - tehsil_name = valid_gee_text(tehsil.lower()) - for layer_type, config in layer_config.items(): - prefix = config.get("prefix") - suffix = config.get("suffix") - layer_name_parts = [ - prefix, - district_name, - tehsil_name, - suffix, - ] - layer_name = "_".join(part for part in layer_name_parts if part) - # raster check - if layer_type == "raster": - count += 1 - if layer_name not in available_raster_layers: - missing_layers.append(state + " " + layer_name) - - # vector check - elif layer_type == "vector": - count += 1 - is_valid = is_valid_vector_layer(workspace, layer_name) - - if not is_valid: - missing_layers.append(state + " " + layer_name) - - return {"missing_layers": missing_layers, "layer_count": count} - - -def valid_raster_layers_for_workspace(workspace): - """ +_raster_cache = {} +_vector_cache = {} - Args: - workspace: - Returns: - all valid (have data)layers for particular workspace - """ +def valid_raster_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_raster_cache, workspace): + logger.info(f"Cache hit for raster: {workspace}") + return _raster_cache[workspace]["data"] + + session = get_session_with_retry() capabilities_url = ( f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" ) - response = requests.get(capabilities_url) - if response.status_code != 200: - print(f"Failed to retrieve capabilities from {workspace}.") - return [] + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching raster capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed raster capabilities for {workspace}: {e} — not caching") + return set() + root = ET.fromstring(response.content) ns = {"wms": root.tag.split("}")[0].strip("{")} layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) - available_layers = [layer.text for layer in layers] - return available_layers + result = {layer.text for layer in layers} + _set_cache(_raster_cache, workspace, result) # cache with timestamp + logger.info(f"Cached raster layers for {workspace}: {len(result)} layers") + return result -def is_valid_vector_layer(workspace, layer_name): - """ - Args: - workspace: - layer_name: +def valid_vector_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_vector_cache, workspace): + logger.info(f"Cache hit for vector: {workspace}") + return _vector_cache[workspace]["data"] - Returns: - True if layer have data else False + session = get_session_with_retry() + capabilities_url = ( + f"{GEOSERVER_BASE}{workspace}/wfs" + f"?service=WFS&version=2.0.0&request=GetCapabilities" + ) + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching vector capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed vector capabilities for {workspace}: {e} — not caching") + return set() + + root = ET.fromstring(response.content) + ns = {"wfs": "http://www.opengis.net/wfs/2.0"} + names = root.findall(".//wfs:FeatureType/wfs:Name", namespaces=ns) + result = {name.text.split(":")[-1] for name in names} + + _set_cache(_vector_cache, workspace, result) # cache with timestamp + logger.info(f"Cached vector layers for {workspace}: {len(result)} layers") + return result + + +def is_valid_vector_layer(workspace: str, layer_name: str) -> bool: + """Used in parallel fallback only.""" + session = get_session_with_retry() + try: + layer_url = get_url(GEOSERVER_URL, workspace, layer_name) + res = session.get(layer_url, timeout=(5, 10)) # shorter timeout per layer + if res.status_code == 200: + root = ET.fromstring(res.text) + return int(root.attrib.get("numberOfFeatures", 0)) > 0 + except requests.exceptions.Timeout: + logger.warning(f"Timeout checking vector layer: {layer_name}") + except Exception as e: + logger.warning(f"Vector check failed for {layer_name}: {e}") + return False + + +def bulk_check_vector_layers( + workspace: str, + layer_names: list[str], + max_workers: int = 20, +) -> dict[str, bool]: + """ + Checks multiple vector layers concurrently using a thread pool. + Returns {layer_name: is_valid} mapping. """ - layer_url = get_url(GEOSERVER_URL, workspace, layer_name) - res_layer_url = requests.get(layer_url) - if res_layer_url.status_code == 200: - root = ET.fromstring(res_layer_url.text) - total_features = int(root.attrib.get("numberOfFeatures", 0)) - return True if total_features > 0 else False + results = {} + with ThreadPoolExecutor(max_workers=max_workers) as executor: + future_to_layer = { + executor.submit(is_valid_vector_layer, workspace, name): name + for name in layer_names + } + for future in as_completed(future_to_layer): + layer_name = future_to_layer[future] + try: + results[layer_name] = future.result() + except Exception as e: + logger.warning(f"Failed check for {layer_name}: {e}") + results[layer_name] = False + return results + + +def missing_layer_for_all_workspace(): + workspaces = ( + Dataset.objects.filter(workspace__isnull=False) + .values_list("workspace", flat=True) + .distinct() + ) + workspaces = [w.strip() for w in workspaces if w and w.strip()] + result = {} + for workspace in workspaces: + result[workspace] = check_missing_layers(workspace) + send_missing_layers_report(result) + return result + + +def check_missing_layers(workspace: str) -> dict: + logger.info(f"{workspace=}") + workspace_config = load_workspace_config() + workspace_types = get_workspace_types(workspace) + logger.info(f"Found types: {workspace_types}") + + if not workspace_types: + logger.critical(f"No config found for workspace: {workspace}") + return {"no config found": []} + + layer_config = get_layer_config_by_type( + workspace_config, workspace, workspace_types + ) + + # ── Fetch all available layers ONCE (bulk, cached) ────────────────────── + available_raster_layers = valid_raster_layers_for_workspace(workspace) + available_vector_layers = valid_vector_layers_for_workspace(workspace) # bulk WFS` + use_bulk_vector = bool(available_vector_layers) # fallback if WFS unavailable + + # ── Pre-build all (tehsil, layer_type, layer_name, state) combos ──────── + active_tehsils = TehsilSOI.objects.select_related( + "district__state" # eliminates N+1 DB queries + ).filter(active_status=True) + + tasks = [] # (state, layer_type, layer_name) + for tehsil_obj in active_tehsils: + state = tehsil_obj.district.state.state_name + district_name = valid_gee_text(tehsil_obj.district.district_name.lower()) + tehsil_name = valid_gee_text(tehsil_obj.tehsil_name.lower()) + + for layer_type, configs in layer_config.items(): + for config in configs: + prefix = config.get("prefix") + suffix = config.get("suffix") + layer_name = "_".join( + p for p in [prefix, district_name, tehsil_name, suffix] if p + ) + tasks.append( + (state, district_name, tehsil_name, layer_type, layer_name) + ) + + # ── Check raster layers (pure set lookup, no HTTP) ─────────────────────── + missing_layers = [] + vector_tasks = [] # collect for batch/parallel processing + + for state, district_name, tehsil_name, layer_type, layer_name in tasks: + if layer_type == "raster": + if layer_name not in available_raster_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + elif layer_type == "vector": + if use_bulk_vector: + # O(1) set lookup — no HTTP call needed + if layer_name not in available_vector_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + else: + vector_tasks.append( + (state, district_name, tehsil_name, layer_name) + ) # queue for parallel check + + # ── Parallel fallback for vector layers (if WFS bulk fetch failed) ─────── + if vector_tasks: + layer_names = [ln for _, _, _, ln in vector_tasks] + info_by_name = {ln: (st, dn, tn) for st, dn, tn, ln in vector_tasks} + validity = bulk_check_vector_layers(workspace, layer_names) + + for layer_name, is_valid in validity.items(): + if not is_valid: + state, district_name, tehsil_name = info_by_name[layer_name] + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + return {"missing_layers": missing_layers} def get_workspace_types(workspace_name): @@ -305,7 +419,7 @@ def get_workspace_types(workspace_name): def get_layer_config_by_type(workspace_config, workspace_name, layer_types): """ - Return prefix and suffix for each layer type. + Return list of prefix/suffix configs for each layer type. Args: workspace_config (dict): config JSON @@ -313,7 +427,7 @@ def get_layer_config_by_type(workspace_config, workspace_name, layer_types): layer_types (list): ['raster', 'vector'] Returns: - dict + dict: {'raster': [{'prefix': ..., 'suffix': ...}, ...], 'vector': [...]} """ result = {} @@ -321,9 +435,44 @@ def get_layer_config_by_type(workspace_config, workspace_name, layer_types): if config.get("name") == workspace_name and config.get("type") in layer_types: layer_type = config["type"] - result[layer_type] = { - "prefix": config.get("prefix"), - "suffix": config.get("suffix"), - } + if layer_type not in result: + result[layer_type] = [] + + result[layer_type].append( + { + "prefix": config.get("prefix"), + "suffix": config.get("suffix"), + } + ) return result + + +def get_session_with_retry(): + """Creates a requests session with retry and timeout handling.""" + session = requests.Session() + retry = Retry( + total=3, + backoff_factor=1, # waits 1s, 2s, 4s between retries + status_forcelist=[500, 502, 503, 504], + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + + +def clear_layer_cache(workspace: str = None): + if workspace: + _raster_cache.pop(workspace, None) + _vector_cache.pop(workspace, None) + logger.info(f"Cleared cache for {workspace}") + else: + _raster_cache.clear() + _vector_cache.clear() + logger.info("Cleared all layer caches") + + +def refresh_layer_cache(request, workspace=None): + clear_layer_cache(workspace) + return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 19a80786..725c6040 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -352,7 +352,8 @@ def resolve_env_path(name, default="", *, trailing_sep=False): EMAIL_USE_TLS = False EMAIL_HOST_USER = env("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") -EMAIL_TIMEOUT = 30 +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="") From 7d9704d8fb52fab58690f505118f92426fae1447 Mon Sep 17 00:00:00 2001 From: Pawan Date: Tue, 16 Jun 2026 07:21:54 +0530 Subject: [PATCH 10/38] Added loggers and exception handling while logging with captcha --- users/views.py | 94 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/users/views.py b/users/views.py index 29a74bbe..9aa23653 100644 --- a/users/views.py +++ b/users/views.py @@ -98,17 +98,46 @@ def post(self, request, *args, **kwargs): ) def verify_recaptcha(token): - response = requests.post( - "https://www.google.com/recaptcha/api/siteverify", - data={ - "secret": settings.RECAPTCHA_SECRET_KEY, - "response": token, - }, - ) + try: + response = requests.post( + "https://www.google.com/recaptcha/api/siteverify", + data={ + "secret": settings.RECAPTCHA_SECRET_KEY, + "response": token, + }, + timeout=10, + ) + + response.raise_for_status() + + result = response.json() + success = result.get("success", False) + + if not success: + logger.warning( + "reCAPTCHA verification failed. Response: %s", + result, + ) + + return success - result = response.json() + except requests.exceptions.Timeout: + logger.error("reCAPTCHA request timed out") + return False - return result.get("success", False) + except requests.exceptions.RequestException as exc: + logger.exception( + "Error while verifying reCAPTCHA: %s", + str(exc), + ) + return False + + except Exception as exc: + logger.exception( + "Unexpected error during reCAPTCHA verification: %s", + str(exc), + ) + return False class LoginView(TokenObtainPairView): """ @@ -120,30 +149,55 @@ class LoginView(TokenObtainPairView): def post(self, request, *args, **kwargs): # Call parent class method to validate credentials and get tokens - captcha_token = request.data.get("captcha") + try: + captcha_token = request.data.get("captcha") - if not captcha_token: + if not captcha_token: + logger.warning( + "Login attempt without captcha. Username: %s", + request.data.get("username"), + ) return Response( {"message": "Captcha is required"}, status=status.HTTP_400_BAD_REQUEST, ) - if not verify_recaptcha(captcha_token): + if not verify_recaptcha(captcha_token): + logger.warning( + "Invalid captcha during login. Username: %s", + request.data.get("username"), + ) return Response( {"message": "Invalid captcha"}, status=status.HTTP_400_BAD_REQUEST, - ) + ) + + response = super().post(request, *args, **kwargs) - response = super().post(request, *args, **kwargs) + token = response.data.get("access") + jwt_auth = JWTAuthentication() + validated_token = jwt_auth.get_validated_token(token) + user = jwt_auth.get_user(validated_token) - token = response.data.get("access") - jwt_auth = JWTAuthentication() - validated_token = jwt_auth.get_validated_token(token) - user = jwt_auth.get_user(validated_token) + logger.info( + "User logged in successfully. User ID: %s Username: %s", + user.id, + user.username, + ) + + response.data["user"] = UserSerializer(user).data + return response - response.data["user"] = UserSerializer(user).data + except Exception as exc: + logger.exception( + "Unexpected error during login: %s", + str(exc), + ) - return response + return Response( + {"message": "An error occurred during login"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) class LogoutView(generics.GenericAPIView): From 16ca4693ca8daf75ce77190efdc7efdde4bd1587 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 16 Jun 2026 12:49:13 +0530 Subject: [PATCH 11/38] Features/missing layers (#1014) * check missing layers and send mail * add caching * increase email timeout * add retries and json as attach file --- computing/utils.py | 71 +++++++++++++++++++++++++++++++++++++--------- computing/views.py | 3 +- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/computing/utils.py b/computing/utils.py index 99393b48..b0d9a4a8 100644 --- a/computing/utils.py +++ b/computing/utils.py @@ -41,7 +41,7 @@ valid_gee_text, ) from utilities.geoserver_utils import Geoserver -from django.core.mail import EmailMessage +from django.core.mail import EmailMessage, get_connection import time logger = logging.getLogger(__name__) @@ -1046,19 +1046,62 @@ def send_missing_layers_report(result: dict, recipients: list = None) -> bool: logger.error("No recipients configured for missing layers report.") return False - try: - email = EmailMessage( - subject="Missing Layers Report", - body=json.dumps(result, indent=4), - from_email=settings.EMAIL_HOST_USER, - to=recipients, - ) - email.send() - logger.info(f"Missing layers report sent to {recipients}") - return True - except Exception as e: - logger.exception(f"Failed to send missing layers report: {e}") - return False + summary = [] + total_missing = 0 + + for layer, data in result.items(): + count = len(data.get("missing_layers", [])) + total_missing += count + summary.append(f"{layer}: {count}") + body = ( + "Missing Layers Report\n\n" + f"Total Missing: {total_missing}\n\n" + + "\n".join(summary) + + "\n\nDetailed report attached." + ) + + attachment_content = json.dumps(result, indent=4) + max_retries = 3 + + for attempt in range(max_retries): + connection = None + try: + connection = get_connection(timeout=120) + connection.open() + email = EmailMessage( + subject="Missing Layers Report", + body=body, + from_email=settings.EMAIL_HOST_USER, + to=recipients, + connection=connection, + ) + email.attach( + "missing_layers.json", + attachment_content, + "application/json", + ) + email.send() + logger.info(f"Missing layers report sent to {recipients}") + logger.info( + f"Attachment size: " + f"{len(attachment_content.encode('utf-8')) / 1024:.2f} KB" + ) + return True + except Exception as e: + logger.exception(f"Attempt {attempt + 1}/{max_retries} failed: {e}") + if attempt < max_retries - 1: + wait_time = 5 * (attempt + 1) + logger.info(f"Retrying after {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error("All attempts to send email failed.") + return False + finally: + if connection: + try: + connection.close() + except Exception: + pass def _is_cache_valid(cache: dict, workspace: str) -> bool: diff --git a/computing/views.py b/computing/views.py index d1a075b3..1d22bb02 100644 --- a/computing/views.py +++ b/computing/views.py @@ -317,7 +317,8 @@ def bulk_check_vector_layers( return results -def missing_layer_for_all_workspace(): +@app.task(bind=True) +def missing_layer_for_all_workspace(self): workspaces = ( Dataset.objects.filter(workspace__isnull=False) .values_list("workspace", flat=True) From 437b2538ef5520f6b9681dba578d1460f83626e8 Mon Sep 17 00:00:00 2001 From: shiv1122prakash Date: Tue, 16 Jun 2026 16:15:51 +0530 Subject: [PATCH 12/38] updated column with unit (#1001) * updated column with unit * updated sheet not aviable * chnages for handle exception * chnages for drainage density * added for livestock * chnages for excel --- .../lulc_X_terrain/lulc_on_plain_cluster.py | 2 +- stats_generator/mws_indicators.py | 180 +++++----- stats_generator/utils.py | 339 +++++++++++------- stats_generator/village_indicators.py | 50 +-- 4 files changed, 324 insertions(+), 247 deletions(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 00c073ed..5c706e32 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -31,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters_bk02_june" + + "_lulcXplains_clusters" ) asset_id = get_gee_asset_path(state, district, block) + asset_description diff --git a/stats_generator/mws_indicators.py b/stats_generator/mws_indicators.py index bf8f311b..9b0c6962 100644 --- a/stats_generator/mws_indicators.py +++ b/stats_generator/mws_indicators.py @@ -143,7 +143,7 @@ def generate_mws_data_for_kyl_filters( 0 ] # terrain except: - terrainCluster_ID = "" + terrainCluster_ID = -9999 try: df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ @@ -256,13 +256,15 @@ def sens_slope(data): except Exception as e: # Handle exception and ensure all variables are set - cropping_intensity_avg = 0 - cropping_intensity_trend = "" - avg_single_cropped = 0 - avg_double_cropped = 0 - avg_triply_cropped = 0 + cropping_intensity_avg = -9999 + cropping_intensity_trend = -9999 + avg_single_cropped = -9999 + avg_double_cropped = -9999 + avg_triply_cropped = -9999 print(f"Error occurred: {e}") + + ##################### SWB ##################### try: df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id @@ -351,21 +353,22 @@ def sens_slope(data): ) except Exception as e: - avg_wsr_ratio_kharif = 0 - avg_wsr_ratio_rabi = 0 - avg_wsr_ratio_zaid = 0 + avg_wsr_ratio_kharif = -9999 + avg_wsr_ratio_rabi = -9999 + avg_wsr_ratio_zaid = -9999 print(f"Error occurred: {e}") ############ Swb_average - avg_kharif_surface_water_mws = 0 - avg_rabi_surface_water_mws = 0 - avg_zaid_surface_water_mws = 0 - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - if not df_swb_annual_mws_data.empty: - total_swb_area = df_swb_annual_mws_data.iloc[0][ - "total_swb_area_in_ha" + avg_kharif_surface_water_mws = -9999 + avg_rabi_surface_water_mws = -9999 + avg_zaid_surface_water_mws = -9999 + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + if not df_swb_annual_mws_data.empty: + total_swb_area = df_swb_annual_mws_data.iloc[0][ + "total_swb_area_in_ha" ] if total_swb_area != 0: # Check if total_swb_area is not zero @@ -420,11 +423,11 @@ def sens_slope(data): avg_perc_kharif_surface_water_mws = ( avg_perc_rabi_surface_water_mws ) = avg_perc_zaid_surface_water_mws = 0 - else: - print("DataFrame is empty. No data to process.") + + except: avg_perc_kharif_surface_water_mws = ( avg_perc_rabi_surface_water_mws - ) = avg_perc_zaid_surface_water_mws = 0 + ) = avg_perc_zaid_surface_water_mws = -9999 ################# SWB Trend ###################### try: @@ -445,33 +448,36 @@ def sens_slope(data): else: trend_swb = "-1" except: - trend_swb = "-1" + trend_swb = -9999 ######### G Trend ################# - G_Trend = ( - hydro_annual_mws_data.filter(like="G") - .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) - .dropna() - ) - G_Trend = G_Trend.squeeze().tolist() - result = mk.original_test(G_Trend) - - def sens_slope(data): - slopes = [] - for i in range(len(data) - 1): - for j in range(i + 1, len(data)): - s = (data[j] - data[i]) / (j - i) - slopes.append(s) - return np.median(slopes) - - trend_g_value = sens_slope(G_Trend) - trend_g = None - if result.trend == "no trend": - trend_g = "0" - elif result.trend == "increasing": - trend_g = "1" - else: - trend_g = "-1" + try: + G_Trend = ( + hydro_annual_mws_data.filter(like="G") + .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) + .dropna() + ) + G_Trend = G_Trend.squeeze().tolist() + result = mk.original_test(G_Trend) + + def sens_slope(data): + slopes = [] + for i in range(len(data) - 1): + for j in range(i + 1, len(data)): + s = (data[j] - data[i]) / (j - i) + slopes.append(s) + return np.median(slopes) + + trend_g_value = sens_slope(G_Trend) + trend_g = None + if result.trend == "no trend": + trend_g = "0" + elif result.trend == "increasing": + trend_g = "1" + else: + trend_g = "-1" + except: + trend_g = -9999 ######### drought_category ############## try: @@ -526,8 +532,8 @@ def sens_slope(data): 4, ) except: - drought_category = 0 - avg_dry_spell_in_weeks = 0 + drought_category = -9999 + avg_dry_spell_in_weeks = -9999 ################# avg_runoff runoff_columns = hydro_annual_mws_data.filter( @@ -549,7 +555,7 @@ def sens_slope(data): .sum() ) except: - nrega_assets_sum = 0 + nrega_assets_sum = -9999 ############ MWS Intersect Villages ######################## try: @@ -603,8 +609,8 @@ def sens_slope(data): ) except: - degradation_land_area = 0 - change_in_cropping_intensity_area = 0 + degradation_land_area = -9999 + change_in_cropping_intensity_area = -9999 ############ Change Detection Afforestation ################### try: @@ -622,7 +628,7 @@ def sens_slope(data): "total_afforestation_area_in_ha", None ).iloc[0] except: - afforestation_land_area = 0 + afforestation_land_area = -9999 ############ Change Detection Deforestation ################### try: @@ -636,7 +642,7 @@ def sens_slope(data): "total_deforestation_area_in_ha", None ).iloc[0] except: - deforestation_land_area = 0 + deforestation_land_area = -9999 ############ Change Detection Urbanization ################### try: @@ -647,7 +653,7 @@ def sens_slope(data): "total_urbanization_area_in_ha", None ).iloc[0] except: - urbanization_land_area = 0 + urbanization_land_area = -9999 ############# Terrain lulc slope / plain ##################### try: @@ -661,7 +667,7 @@ def sens_slope(data): ) except: - lulc_slope_category = "" + lulc_slope_category = -9999 try: df_lulc_plain_mws_data = sheets["terrain_lulc_plain"][ @@ -674,7 +680,7 @@ def sens_slope(data): ) except: - lulc_plain_category = "" + lulc_plain_category = -9999 ################# Restoration Vector ######################### try: @@ -688,8 +694,8 @@ def sens_slope(data): "protection_area_in_ha", None ).iloc[0] except: - wide_scale_restoration = 0 - area_protection = 0 + wide_scale_restoration = -9999 + area_protection = -9999 ################# Aquifer Vector ######################### aquifer_class_map = {0: "Hard Rock", 1: "Alluvial"} @@ -706,7 +712,7 @@ def sens_slope(data): aquifer_class_name = "Alluvial" aquifer_class = int(class_to_id.get(aquifer_class_name, "")) except Exception: - aquifer_class = "" + aquifer_class = -9999 ################# SOGE Vector ######################### Soge_class = { @@ -729,7 +735,7 @@ def sens_slope(data): class_to_id.get(soge_class_name, "") ) # Returns None if not found except Exception: - soge_class = 4 + soge_class = -9999 ################## LCW Conflict ###################### ## if count is 0 then Areas with no conflicts else Areas with conflicts @@ -742,7 +748,7 @@ def sens_slope(data): else: lcw_conflict = 1 except Exception as e: - lcw_conflict = 0 + lcw_conflict = -9999 ################## mining ###################### ## if count is 0 then Areas with no mining else Areas with mining @@ -755,7 +761,7 @@ def sens_slope(data): else: mining = 1 except Exception as e: - mining = 0 + mining = -9999 ################## green credit ###################### ## if count is 0 then Areas with no green credit else Areas with green credit @@ -768,7 +774,7 @@ def sens_slope(data): else: green_credit = 1 except Exception as e: - green_credit = 0 + green_credit = -9999 ################## factory csr ###################### ## if count is 0 then Areas with no factory else Areas with factory @@ -781,7 +787,7 @@ def sens_slope(data): else: factory_csr = 1 except Exception as e: - factory_csr = 0 + factory_csr = -9999 ############ MWS Intersect Swb ######################## try: @@ -825,13 +831,13 @@ def sens_slope(data): mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] # Average of all UID mean elevations - overall_mean_elevation = dem_df["mean_elevation"].mean() + overall_mean_elevation = dem_df["mean_elevation_in_m"].mean() if not mws_dem_data.empty: row = mws_dem_data.iloc[0] relief = round( - row["max_elevation"] - row["min_elevation"], 2 + row["max_elevation_in_m"] - row["min_elevation_in_m"], 2 ) - mean_elevation = round(row["mean_elevation"], 2) + mean_elevation = round(row["mean_elevation_in_m"], 2) # Relative mean elevation if overall_mean_elevation != 0: @@ -847,15 +853,15 @@ def sens_slope(data): relative_mean_elevation = 0 else: - relief = 0 - mean_elevation = 0 - relative_mean_elevation = 0 + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 except Exception as e: print(f"Error in getting DEM data: {e}") - relief = 0 - mean_elevation = 0 - relative_mean_elevation = 0 + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 ############ Canal ######################## try: @@ -872,7 +878,7 @@ def sens_slope(data): except Exception as e: print(f"Error in getting canal data: {e}") - canal_available = False + canal_available = -9999 ############ Canal ######################## try: @@ -889,13 +895,13 @@ def sens_slope(data): except Exception as e: print(f"Error in getting canal data: {e}") - river_available = False + river_available = -9999 ############ lulc vector ######################## try: - lulc_shrub_percent = 0 - lulc_forest_percent = 0 - lulc_crop_percent = 0 + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 lulc_df = sheets["lulc_vector"] @@ -953,13 +959,16 @@ def sens_slope(data): lulc_crop_percent = round( (cropped_area_in_ha / area_in_ha) * 100, 2 ) - + else: + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 except Exception as e: print(f"Error in LULC vector: {e}") - lulc_shrub_percent = 0 - lulc_forest_percent = 0 - lulc_crop_percent = 0 + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 ############ Canal ######################## try: @@ -970,16 +979,17 @@ def sens_slope(data): ] if not mws_drainage_density_data.empty: row = mws_drainage_density_data.iloc[0] - drainage_density = round(row["drainage_density"], 2) + drainage_density = round(row["drainage_density_std_in_km_per_km2"], 2) else: drainage_density = 0 else: - drainage_density = 0 + drainage_density = -9999 + except Exception as e: print(f"Error in getting drainage_density data: {e}") - drainage_density = 0 + drainage_density = -9999 results.append( { diff --git a/stats_generator/utils.py b/stats_generator/utils.py index 6a38a648..bcc2c694 100644 --- a/stats_generator/utils.py +++ b/stats_generator/utils.py @@ -244,8 +244,10 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) elif workspace == "drainage_density": create_excel_for_drainage_density(geojson_data, writer) - elif workspace == "antyodaya_analysis": + elif workspace == "antyodaya_2020": create_excel_for_antyodaya_20(geojson_data, writer) + elif workspace == "livestocks": + create_excel_for_livestock(geojson_data, writer) results.append( {"layer": layer_name, "status": "success", "workspace": workspace} @@ -254,24 +256,49 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): return results +def create_excel_for_livestock(data, writer): + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Columns to exclude + exclude_cols = ["state_name","district_name","TEHSIL"] + df = df.drop(columns=exclude_cols, errors="ignore") + + # Keep important columns first if they exist + first_cols = [c for c in ["pc11_village_id", "NAME"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + df.to_excel(writer, sheet_name="livestock", index=False) + print("Excel file created for livestock") + except Exception as e: + print(f"Error in getting livestock data: {e}") + + def create_excel_for_antyodaya_20(data, writer): - features = data.get("features", []) - df_data = [feature.get("properties", {}) for feature in features] - df = pd.DataFrame(df_data) + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) - # Keep important columns first if they exist - first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] + # Keep important columns first if they exist + first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] - # Round numeric columns - numeric_cols = df.select_dtypes(include=["number"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="antyodaya", index=False) - print("Excel file created for antyodaya") + # Round numeric columns + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="antyodaya", index=False) + print("Excel file created for antyodaya") + except Exception as e: + print(f"Error in getting antyodaya data: {e}") def create_excel_for_drainage_density(data, writer): + import ast print("Inside create_excel_for Drainage Density") df_data = [] features = data["features"] @@ -281,7 +308,9 @@ def create_excel_for_drainage_density(data, writer): row = { "UID": properties.get("uid", ""), "area_in_ha": properties.get("area_in_ha", ""), - "drainage_density": properties.get("drainage_density", ""), + "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), + "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), + "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), } df_data.append(row) @@ -346,44 +375,50 @@ def get_key(base_key, trunc_prefix, idx): def create_excel_for_canal(data, writer): print("Inside create_excel_for Canal") - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "project_name": properties.get("prjname", ""), - "canal_code": properties.get("cancode", ""), - "canal_name": properties.get("canname", ""), - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "project_name": properties.get("prjname", ""), + "canal_code": properties.get("cancode", ""), + "canal_name": properties.get("canname", ""), + } - df_data.append(row) + df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="canal", index=False) - print("Excel file created for canal") + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="canal", index=False) + print("Excel file created for canal") + except Exception as e: + print("Canal Layer not found :: ", e) def create_excel_for_river(data, writer): print("Inside create_excel_for River") - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "river_name": properties.get("rivname", ""), - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "river_name": properties.get("rivname", ""), + } - df_data.append(row) + df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="river", index=False) - print("Excel file created for river") + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="river", index=False) + print("Excel file created for river") + except Exception as e: + print("River Layer not found :: ", e) def create_excel_for_dem(data, writer): @@ -397,9 +432,9 @@ def create_excel_for_dem(data, writer): row = { "UID": properties.get("uid", ""), - "min_elevation": properties.get("min_elevation", ""), - "max_elevation": properties.get("max_elevation", ""), - "mean_elevation": properties.get("mean_elevation", ""), + "min_elevation_in_m": properties.get("min_elevation", ""), + "max_elevation_in_m": properties.get("max_elevation", ""), + "mean_elevation_in_m": properties.get("mean_elevation", ""), } df_data.append(row) @@ -470,19 +505,36 @@ def calculate_intersection_area(geom1, geom2): def create_excel_for_facilities(data, writer): - features = data["features"] - df_data = [feature["properties"] for feature in features] + try: + features = data["features"] + df_data = [feature["properties"] for feature in features] - df = pd.DataFrame(df_data) + df = pd.DataFrame(df_data) - # keep first columns - first_cols = ["censuscode2011", "censusname"] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="facilities_proximity", index=False) - print("Excel file created for facilities_proximity") + first_cols = ["censuscode2011", "censusname"] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] + df.rename( + columns={ + col: f"{col}_in_km" + for col in df.columns + if col not in exclude_cols + }, + inplace=True + ) + + # Write to Excel + df.to_excel(writer, sheet_name="facilities_proximity", index=False) + + print("Excel file created for facilities_proximity") + except Exception as e: + print("facilities_proximity Layer not found :: ", e) + def create_excel_for_mws(data, writer): @@ -571,108 +623,123 @@ def create_excel_for_stream_order(data, writer): def create_excel_for_mining(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["company_na"], - "proposal": properties["proposal"], - "sector_moefcc": properties["sector_moe"], - "village": properties["village"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["company_na"], + "proposal": properties["proposal"], + "sector_moefcc": properties["sector_moe"], + "village": properties["village"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mining", index=False) - print("Excel file created for mining") + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mining", index=False) + print("Excel file created for mining") + except Exception as e: + print("Mining Layer not found :: ", e) def create_excel_for_green_credit(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["division"], - # "parcel_id": properties["parcel_id"], - "land_info": properties["land_info"], - "kml_url": properties["kml_url"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["division"], + # "parcel_id": properties["parcel_id"], + "land_info": properties["land_info"], + "kml_url": properties["kml_url"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="green_credit", index=False) - print("Excel file created for green_credit") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="green_credit", index=False) + print("Excel file created for green_credit") + except Exception as e: + print("green credit Layer not found :: ", e) def create_excel_for_factory_csr(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "Company_Name": properties["COMPANY NA"], - "ADDRESS": properties["ADDRESS"], - "LOCATION T": properties["LOCATION T"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "Company_Name": properties["COMPANY NA"], + "ADDRESS": properties["ADDRESS"], + "LOCATION T": properties["LOCATION T"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="factory_csr", index=False) - print("Excel file created for factory_csr") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="factory_csr", index=False) + print("Excel file created for factory_csr") + except Exception as e: + print("factory csr Layer not found :: ", e) def create_excel_for_agroecological(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "organization_name": properties["organization_name"], - "organization_type": properties["organization_type"], - "created_at": properties["created_at"], - "contact_person": properties["contact_person"], - "email": properties["email"], - "domains": properties["domains"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "organization_name": properties["organization_name"], + "organization_type": properties["organization_type"], + "created_at": properties["created_at"], + "contact_person": properties["contact_person"], + "email": properties["email"], + "domains": properties["domains"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="agroecological", index=False) - print("Excel file created for agroecological") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="agroecological", index=False) + print("Excel file created for agroecological") + except Exception as e: + print("agroecological Layer not found :: ", e) def create_excel_for_lcw(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "title_of_conflict": properties["Title of Conflict"], - "link_to_conflict": properties["Link to conflict"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "title_of_conflict": properties["Title of Conflict"], + "link_to_conflict": properties["Link to conflict"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="lcw_conflict", index=False) - print("Excel file created for lcw_conflict") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="lcw_conflict", index=False) + print("Excel file created for lcw_conflict") + except Exception as e: + print("lcw Layer not found :: ", e) def create_excel_for_soge(data, xlsx_file, writer): diff --git a/stats_generator/village_indicators.py b/stats_generator/village_indicators.py index b3bbdee7..e33942c2 100644 --- a/stats_generator/village_indicators.py +++ b/stats_generator/village_indicators.py @@ -51,66 +51,66 @@ def safe_val(v): result = { "essential_education_infra": get_max( [ - row.get("school_primary_distance", -1), - row.get("school_upper_primary_distance", -1), - row.get("school_secondary_distance", -1), + row.get("school_primary_distance_in_km", -1), + row.get("school_upper_primary_distance_in_km", -1), + row.get("school_secondary_distance_in_km", -1), ] ), "higher_education_infra": get_min( [ - row.get("school_higher_secondary_distance", -1), - row.get("college_distance", -1), - row.get("universities_distance", -1), + row.get("school_higher_secondary_distance_in_km", -1), + row.get("college_distance_in_km", -1), + row.get("universities_distance_in_km", -1), ] ), "essential_health_services": get_max( [ - row.get("health_sub_cen_distance", -1), - row.get("health_phc_distance", -1), + row.get("health_sub_cen_distance_in_km", -1), + row.get("health_phc_distance_in_km", -1), ] ), "advanced_health_services": get_min( [ - row.get("health_chc_distance", -1), - row.get("health_dis_h_distance", -1), - row.get("health_s_t_h_distance", -1), + row.get("health_chc_distance_in_km", -1), + row.get("health_dis_h_distance_in_km", -1), + row.get("health_s_t_h_distance_in_km", -1), ] ), "public_distribution_system": get_max( [ - row.get("pds_distance", -1), + row.get("pds_distance_in_km", -1), ] ), "financial_inclusion": get_max( [ - row.get("csc_distance", -1), - row.get("bank_mitra_distance", -1), - row.get("bank_branch_distance", -1), - row.get("bank_atm_distance", -1), + row.get("csc_distance_in_km", -1), + row.get("bank_mitra_distance_in_km", -1), + row.get("bank_branch_distance_in_km", -1), + row.get("bank_atm_distance_in_km", -1), ] ), "agri_market_access": get_min( [ - row.get("apmc_distance", -1), - row.get("agri_industry_markets_trading_distance", -1), + row.get("apmc_distance_in_km", -1), + row.get("agri_industry_markets_trading_distance_in_km", -1), ] ), "post_harvest_infra": get_min( [ - row.get("agri_industry_storage_warehousing_distance", -1), - row.get("agri_industry_distribution_utilities_distance", -1), - row.get("agri_industry_agri_processing_distance", -1), - row.get("agri_industry_industrial_manufacturing_distance", -1), + row.get("agri_industry_storage_warehousing_distance_in_km", -1), + row.get("agri_industry_distribution_utilities_distance_in_km", -1), + row.get("agri_industry_agri_processing_distance_in_km", -1), + row.get("agri_industry_industrial_manufacturing_distance_in_km", -1), ] ), "farmer_cooperatives_access": safe_val( - row.get("agri_industry_co_operatives_societies_distance", -1) + row.get("agri_industry_co_operatives_societies_distance_in_km", -1) ), "livestock_management_centers": safe_val( - row.get("agri_industry_dairy_animal_husbandry_distance", -1) + row.get("agri_industry_dairy_animal_husbandry_distance_in_km", -1) ), "agricultural_support_infrastructure": safe_val( - row.get("agri_industry_agri_support_infrastructure_distance", -1) + row.get("agri_industry_agri_support_infrastructure_distance_in_km", -1) ), } From e4819c19ef8d1958e166e347c022001d1172e1c4 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 16 Jun 2026 16:36:53 +0530 Subject: [PATCH 13/38] Features/multilingual dpr (#999) * convert till section c * add section d,e & f * transaltion till section g * send multiligual DPR on email * move files to data directory * fix section d data * read label json from data dir * add footnote * email timeout time inc --------- Co-authored-by: Ankit Kumar --- dpr/api.py | 15 +- dpr/gen_dpr.py | 6 +- dpr/get_dpr_sectionwise_data.py | 1291 +++++++++++++++++ dpr/models.py | 16 +- dpr/service/__init__.py | 0 dpr/service/form_download_service.py | 249 ++++ dpr/service/translation_service.py | 16 + dpr/tasks.py | 47 +- dpr/templatetags/__init__.py | 0 dpr/templatetags/custom_filters.py | 11 + dpr/urls.py | 14 +- dpr/utils.py | 74 +- dpr/views.py | 64 +- templates/dpr/base.html | 147 ++ templates/dpr/section_a.html | 61 + templates/dpr/section_b.html | 117 ++ templates/dpr/section_c_crop.html | 72 + templates/dpr/section_c_livelihood.html | 52 + templates/dpr/section_c_mgnrega.html | 66 + templates/dpr/section_c_socio_economic.html | 82 ++ templates/dpr/section_d_base.html | 7 + .../dpr/section_d_detail_water_structure.html | 87 ++ templates/dpr/section_d_detail_well_info.html | 87 ++ templates/dpr/section_d_mws_information.html | 31 + .../section_d_water_structure_summary.html | 36 + templates/dpr/section_d_well_summary.html | 39 + templates/dpr/section_e_base.html | 7 + .../dpr/section_e_irrigation_structure.html | 48 + ...tion_e_maintenance_work_by_asset_type.html | 26 + .../dpr/section_e_recharge_structure.html | 48 + templates/dpr/section_e_rs_swb.html | 48 + templates/dpr/section_e_water_structure.html | 48 + templates/dpr/section_f.html | 64 + templates/dpr/section_g.html | 115 ++ 34 files changed, 3031 insertions(+), 60 deletions(-) create mode 100644 dpr/get_dpr_sectionwise_data.py create mode 100644 dpr/service/__init__.py create mode 100644 dpr/service/form_download_service.py create mode 100644 dpr/service/translation_service.py create mode 100644 dpr/templatetags/__init__.py create mode 100644 dpr/templatetags/custom_filters.py create mode 100644 templates/dpr/base.html create mode 100644 templates/dpr/section_a.html create mode 100644 templates/dpr/section_b.html create mode 100644 templates/dpr/section_c_crop.html create mode 100644 templates/dpr/section_c_livelihood.html create mode 100644 templates/dpr/section_c_mgnrega.html create mode 100644 templates/dpr/section_c_socio_economic.html create mode 100644 templates/dpr/section_d_base.html create mode 100644 templates/dpr/section_d_detail_water_structure.html create mode 100644 templates/dpr/section_d_detail_well_info.html create mode 100644 templates/dpr/section_d_mws_information.html create mode 100644 templates/dpr/section_d_water_structure_summary.html create mode 100644 templates/dpr/section_d_well_summary.html create mode 100644 templates/dpr/section_e_base.html create mode 100644 templates/dpr/section_e_irrigation_structure.html create mode 100644 templates/dpr/section_e_maintenance_work_by_asset_type.html create mode 100644 templates/dpr/section_e_recharge_structure.html create mode 100644 templates/dpr/section_e_rs_swb.html create mode 100644 templates/dpr/section_e_water_structure.html create mode 100644 templates/dpr/section_f.html create mode 100644 templates/dpr/section_g.html diff --git a/dpr/api.py b/dpr/api.py index 87911e34..935d01de 100644 --- a/dpr/api.py +++ b/dpr/api.py @@ -4,6 +4,7 @@ from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import render +from django.templatetags.i18n import language from django.urls import reverse from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema @@ -138,6 +139,7 @@ def generate_dpr(request): try: plan_id = request.data.get("plan_id") email_id = request.data.get("email_id") + language = request.data.get("language") regenerate = request.data.get("regenerate", False) logger.info( @@ -161,7 +163,9 @@ def generate_dpr(request): {"error": "Plan not found"}, status=status.HTTP_404_NOT_FOUND ) - generate_dpr_task.apply_async(args=[plan_id, email_id, regenerate], queue="dpr") + generate_dpr_task.apply_async( + args=[plan_id, email_id, language, regenerate], queue="dpr" + ) return Response( { @@ -572,13 +576,15 @@ def generate_village_report(request): for key, value in params.items(): result[key] = value - + 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 + "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 } return render(request, "village-report.html", context) @@ -587,6 +593,7 @@ def generate_village_report(request): logger.exception("Exception in generate_village_report api :: ", e) return render(request, "error-page.html", {}) + # --------------------------------------------------------------------------- # DPR Data API # --------------------------------------------------------------------------- diff --git a/dpr/gen_dpr.py b/dpr/gen_dpr.py index 917ba55d..9aebfce0 100644 --- a/dpr/gen_dpr.py +++ b/dpr/gen_dpr.py @@ -190,9 +190,9 @@ def initialize_document(): doc = Document() heading = doc.add_heading("Detailed Project Report", 0) heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER - doc.add_paragraph( - date.today().strftime("%B %d, %Y") - ).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + doc.add_paragraph(date.today().strftime("%B %d, %Y")).alignment = ( + WD_PARAGRAPH_ALIGNMENT.CENTER + ) return doc diff --git a/dpr/get_dpr_sectionwise_data.py b/dpr/get_dpr_sectionwise_data.py new file mode 100644 index 00000000..d47b2012 --- /dev/null +++ b/dpr/get_dpr_sectionwise_data.py @@ -0,0 +1,1291 @@ +import geopandas as gpd +from .gen_dpr import ( + get_settlement_coordinates_for_plan, + get_mws_uid_for_settlement_gdf, + get_data_for_settlement, + get_crops_data, + get_livestock_data, + get_all_wells_with_mws, + get_all_waterbodies_with_mws, + sort_key, +) +from .utils import to_utf8 +from collections import defaultdict +from dpr.utils import ensure_str, get_waterbody_repair_activities +from .mapping import populate_maintenance_from_waterbody +from .services import ( + get_nrm_works_data, +) +from .service.form_download_service import ( + load_form_labels, + translate_choice, + translate_multiple_choices, +) +from dpr.models import ( + GW_maintenance, + ODK_livelihood, + ODK_agrohorticulture, + Agri_maintenance, + SWB_maintenance, + SWB_RS_maintenance, + ODK_agri, + ODK_groundwater, +) + + +def get_section_b_data(plan, total_settlements, mws_fortnight): + + mws_gdf = gpd.GeoDataFrame.from_features(mws_fortnight["features"]) + + settlement_mws_ids = [] + settlement_coordinates = get_settlement_coordinates_for_plan(plan.id) + + for settlement_name, latitude, longitude in settlement_coordinates: + mws_uid = get_mws_uid_for_settlement_gdf(mws_gdf, latitude, longitude) + + if mws_uid: + settlement_mws_ids.append( + { + "settlement": settlement_name, + "mws_id": mws_uid, + } + ) + + centroid = None + + if settlement_mws_ids: + intersecting_mws = mws_gdf[ + mws_gdf["uid"].isin([item["mws_id"] for item in settlement_mws_ids]) + ] + + if not intersecting_mws.empty: + centroid = intersecting_mws.geometry.unary_union.centroid + + return ( + { + "village_name": to_utf8(plan.village_name), + "gram_panchayat": to_utf8(plan.gram_panchayat), + "tehsil": to_utf8(plan.tehsil_soi.tehsil_name), + "district": to_utf8(plan.district_soi.district_name), + "state": to_utf8(plan.state_soi.state_name), + "total_settlements": total_settlements, + "settlement_mws_pairs": settlement_mws_ids, + "village_coordinates": ( + f"{centroid.y:.8f}, {centroid.x:.8f}" if centroid else "Not available" + ), + }, + settlement_mws_ids, + mws_gdf, + ) + + +def get_section_c_data(plan, language): + settlement_data = get_data_for_settlement(plan.id) + labels = load_form_labels("Add_Settlements_form _V1.0.1") + crop_data = get_crops_data(plan.id) + crop_labels = load_form_labels("crop_form_V1.0.0") + for settlement in settlement_data: + + settlement.largest_caste_label = translate_choice( + labels, + "select_one_type", + settlement.largest_caste, + language, + ) + + if settlement.largest_caste.lower() == "single caste group": + + settlement.smallest_caste_label = translate_choice( + labels, + "caste_group_single", + settlement.smallest_caste, + language, + ) + + elif settlement.largest_caste.lower() == "mixed caste group": + + settlement.settlement_status_label = translate_multiple_choices( + labels, + "caste_group_single", + settlement.settlement_status, + language, + ) + settlement.nrega_past_work_label = translate_multiple_choices( + labels, + "work_demands", + clean_odk_value(settlement.nrega_past_work), + language, + ) + + settlement.nrega_demand_label = translate_choice( + labels, + "select_one_demands", + clean_odk_value(settlement.nrega_demand), + language, + ) + + settlement.nrega_issues_label = translate_multiple_choices( + labels, + "select_multiple_issues", + settlement.nrega_issues, + language, + ) + for crop in crop_data: + crop["irrigation_source"] = translate_multiple_choices( + crop_labels, + "select_multiple_widgets", + clean_odk_value(crop["irrigation_source"]), + language, + ) + + crop["land_classification"] = translate_choice( + crop_labels, + "select_one_classified", + clean_odk_value(crop["land_classification"]), + language, + ) + + crop["kharif_crops"] = translate_multiple_choices( + crop_labels, + "select_multiple_cropping_kharif", + clean_odk_value(crop["kharif_crops"]), + language, + ) + + crop["rabi_crops"] = translate_multiple_choices( + crop_labels, + "select_multiple_cropping_Rabi", + clean_odk_value(crop["rabi_crops"]), + language, + ) + + crop["zaid_crops"] = translate_multiple_choices( + crop_labels, + "select_multiple_cropping_Zaid", + clean_odk_value(crop["zaid_crops"]), + language, + ) + + crop["cropping_intensity"] = translate_choice( + crop_labels, + "select_one_productivity", + clean_odk_value(crop["cropping_intensity"]), + language, + ) + return { + "socio_eco": settlement_data, + "mgnrega": settlement_data, + "crop_info": crop_data, + "livestock_info": get_livestock_data(plan.id), + } + + +def get_section_d_data(plan, settlement_mws_ids, mws_gdf, language): + unique_mws_ids = sorted({item["mws_id"] for item in settlement_mws_ids}) + + all_wells_with_mws = get_all_wells_with_mws( + plan, + unique_mws_ids, + mws_gdf, + ) + + all_waterbodies_with_mws = get_all_waterbodies_with_mws( + plan, + unique_mws_ids, + mws_gdf, + ) + + well_labels = load_form_labels("Add_well_form_V1.0.1") + water_body_labels_repair = load_form_labels( + "NRM_form_NRM_form_Waterbody_Screen_V1.0.0" + ) + water_body_labels = load_form_labels("Add_Waterbodies_Form_V1.0.3") + return { + "mws": get_mws_table_data(unique_mws_ids, mws_gdf), + "well_summary": get_well_summary_data(all_wells_with_mws), + "wells": get_detailed_well_data(all_wells_with_mws, well_labels, language), + "water_summary": get_waterbody_summary_data( + all_waterbodies_with_mws, water_body_labels, language + ), + "water_structures": get_detailed_waterbody_data( + all_waterbodies_with_mws, + water_body_labels, + language, + water_body_labels_repair, + ), + } + + +def get_mws_table_data(unique_mws_ids, mws_gdf): + + data = [] + + for mws_id in unique_mws_ids: + + matching_feature = mws_gdf[mws_gdf["uid"] == mws_id] + + centroid = None + + if not matching_feature.empty: + c = matching_feature.geometry.centroid.iloc[0] + centroid = f"{c.y:.8f}, {c.x:.8f}" + + data.append( + { + "mws_id": mws_id, + "centroid": centroid, + } + ) + + return data + + +def get_well_summary_data(all_wells_with_mws): + + wells_count = defaultdict(int) + households_count = defaultdict(int) + + for well, _ in all_wells_with_mws: + + wells_count[well.beneficiary_settlement] += 1 + + households_count[well.beneficiary_settlement] += int( + well.households_benefitted or 0 + ) + + rows = [] + + for settlement in sorted(wells_count.keys(), key=sort_key): + + rows.append( + { + "settlement": settlement, + "num_wells": wells_count[settlement], + "households": households_count[settlement], + } + ) + + return rows + + +def get_detailed_well_data(all_wells_with_mws, labels, language): + + rows = [] + + all_wells_with_mws_sorted = sorted( + all_wells_with_mws, + key=lambda x: ( + not x[0].beneficiary_settlement or x[0].beneficiary_settlement == "NA", + (x[0].beneficiary_settlement or "").lower(), + ), + ) + + for well, mws_id in all_wells_with_mws_sorted: + + well_usage = None + + if well.data_well and "Well_usage" in well.data_well: + + usage = well.data_well["Well_usage"] + + used = ensure_str(usage.get("select_one_well_used")) + + other = usage.get("select_one_well_used_other") + + if used and used.lower() == "other" and other: + well_usage = f"Other: {other}" + + elif used: + well_usage = translate_choice( + labels, + "select_one_well_used", + used, + language, + ) + + repair_activities = None + + if well.data_well and "Well_usage" in well.data_well: + + usage = well.data_well["Well_usage"] + + repairs = ensure_str(usage.get("repairs_type")) + repairs_other = usage.get("repairs_type_other") + + if repairs and repairs.lower() == "other" and repairs_other: + repair_activities = repairs_other + + elif repairs: + repair_activities = translate_multiple_choices( + labels, + "repairs_type", + repairs, + language, + ) + rows.append( + { + "mws_id": mws_id, + "settlement": well.beneficiary_settlement, + "well_type": translate_choice( + labels, + "select_one_well_type", + well.data_well.get("select_one_well_type"), + language, + ), + "owner": translate_choice( + labels, + "select_one_owns", + well.owner, + language, + ), + "beneficiary_name": well.data_well.get("Beneficiary_name") or None, + "father_name": well.data_well.get("ben_father") or None, + "water_availability": translate_choice( + labels, + "select_one_year", + well.data_well.get("select_one_year"), + language, + ), + "households_benefitted": well.households_benefitted, + "caste_uses": translate_multiple_choices( + labels, + "select_multiple_caste_use", + well.caste_uses, + language, + ), + "well_usage": well_usage, + "need_maintenance": translate_choice( + labels, + "is_maintenance_required", + well.need_maintenance, + language, + ), + "repair_activities": repair_activities, + "latitude": well.latitude, + "longitude": well.longitude, + } + ) + + return rows + + +def get_waterbody_summary_data(all_waterbodies_with_mws, water_body_labels, language): + + waterbody_count = defaultdict(int) + + households_count = defaultdict(int) + + for waterbody, _ in all_waterbodies_with_mws: + + structure_type = waterbody.water_structure_type + + key = ( + waterbody.beneficiary_settlement, + structure_type, + ) + + waterbody_count[key] += 1 + + households_count[key] += int(waterbody.household_benefitted or 0) + + rows = [] + + for ( + settlement, + structure_type, + ) in sorted( + waterbody_count.keys(), + key=lambda x: sort_key(x[0]), + ): + + rows.append( + { + "settlement": settlement, + "structure_type": translate_choice( + water_body_labels, + "select_one_water_structure", + structure_type, + language, + ), + "count": waterbody_count[(settlement, structure_type)], + "households": households_count[(settlement, structure_type)], + } + ) + + return rows + + +def get_detailed_waterbody_data( + all_waterbodies_with_mws, waterbody_labels, language, water_body_labels_repair +): + + rows = [] + + for ( + waterbody, + mws_id, + ) in sorted( + all_waterbodies_with_mws, + key=lambda x: sort_key(x[0].beneficiary_settlement), + ): + + who_manages = waterbody.who_manages or None + + if who_manages: + + if who_manages.lower() == "other": + who_manages = "Other: " + (waterbody.specify_other_manager or "") + + else: + who_manages = translate_choice( + waterbody_labels, + "select_one_manages", + who_manages.lower(), + language, + ) + + structure_type = waterbody.water_structure_type or None + structure_type_eng = structure_type + + if structure_type: + + if structure_type.lower() == "other": + structure_type = "Other: " + (waterbody.water_structure_other or "") + structure_type_eng = structure_type + + else: + structure_type = translate_choice( + waterbody_labels, + "select_one_water_structure", + structure_type, + language, + ) + repair_activities = get_waterbody_repair_activities( + waterbody.data_waterbody, + structure_type_eng, + ) + repair_label_key = REPAIR_LABEL_MAPPING.get( + str(waterbody.water_structure_type).strip().lower() + ) + wb_owner = waterbody.owner + wb_owner = classify_demand_type(wb_owner.lower()) + rows.append( + { + "mws_id": mws_id, + "settlement": waterbody.beneficiary_settlement, + "owner": translate_choice( + water_body_labels_repair, + "demand_type", + wb_owner, + language, + ), + "beneficiary_name": waterbody.data_waterbody.get("Beneficiary_name") + or None, + "father_name": waterbody.data_waterbody.get("ben_father") or None, + "who_manages": who_manages, + "caste_uses": translate_multiple_choices( + waterbody_labels, + "select_multiple_caste_use", + waterbody.caste_who_uses, + language, + ), + "households_benefitted": waterbody.household_benefitted, + "structure_type": structure_type, + "usage": translate_multiple_choices( + waterbody_labels, + "select_multiple_uses_structure", + waterbody.data_waterbody.get("select_multiple_uses_structure"), + language, + ), + "need_maintenance": translate_choice( + waterbody_labels, + "select_one_maintenance", + waterbody.need_maintenance, + language, + ), + "repair_activities": translate_multiple_choices( + water_body_labels_repair, + repair_label_key, + repair_activities, + language, + ), + "latitude": waterbody.latitude, + "longitude": waterbody.longitude, + } + ) + + return rows + + +def get_section_e_data(plan, language): + populate_maintenance_from_waterbody(plan) + gw_data = get_maintenance_data(plan.id, "gw") + agri_data = get_maintenance_data(plan.id, "agri") + swb_data = get_maintenance_data(plan.id, "swb") + swb_rs_data = get_maintenance_data(plan.id, "swb_rs") + gw_label = load_form_labels( + "Propose_Maintenance_on_Existing_Water_Recharge_Structures_V1.1.1" + ) + agri_label = load_form_labels( + "Propose_Maintenance_on_Existing_Irrigation_Structures_V1.1.1" + ) + swb_rs_label = load_form_labels("PM_Remote_Sensed_Surface_Water_structure_V1.0.0") + swb_label = load_form_labels("NRM_form_NRM_form_Waterbody_Screen_V1.0.0") + + for row in gw_data: + + row["demand_type"] = translate_choice( + gw_label, + "demand_type", + row["demand_type"], + language, + ) + + row["structure_type"] = translate_choice( + gw_label, + "select_one_recharge_structure", + row["structure_type"], + language, + ) + for row in agri_data: + demand_type = classify_demand_type(row["demand_type"].lower()) + row["demand_type"] = translate_choice( + agri_label, + "demand_type".lower().replace(" ", "_"), + demand_type, + language, + ) + row["structure_type"] = translate_choice( + agri_label, + "select_one_irrigation_structure", + row["structure_type"], + language, + ) + for row in swb_data: + row["demand_type"] = translate_choice( + swb_label, + "demand_type", + row["demand_type"], + language, + ) + row["structure_type"] = translate_choice( + swb_rs_label, + "TYPE_OF_WORK", + row["structure_type"], + language, + ) + for row in swb_rs_data: + row["demand_type"] = translate_choice( + swb_rs_label, + "demand_type", + row["demand_type"], + language, + ) + + original_structure = row["structure_type"] + original_structure = str(original_structure).strip().lower() + repair_key = RS_WATER_STRUCTIRE_REVERSE_MAPPING.get(original_structure) + if repair_key and row.get("repair_activities"): + row["repair_activities"] = translate_choice( + swb_rs_label, + repair_key, + clean_odk_value(row["repair_activities"]), + language, + ) + row["structure_type"] = translate_choice( + swb_rs_label, + "TYPE_OF_WORK", + row["structure_type"], + language, + ) + return { + "gw": gw_data, + "agri": agri_data, + "swb": swb_data, + "swb_rs": swb_rs_data, + } + + +def get_section_f_data(plan, language): + gw_labels = load_form_labels("NRM_form_propose_new_recharge_structure_V1.0.0") + agri_labels = load_form_labels("NRM_form_Agri_Screen_V1.0.0") + works = get_nrm_works_data(plan.id) + for row in works: + if row["work_category"] == "Recharge Structure": + row["demand_type"] = translate_choice( + gw_labels, + "demand_type", + row["demand_type"], + language, + ) + row["work_demand"] = translate_choice( + gw_labels, + "TYPE_OF_WORK_ID", + row["work_demand"], + language, + ) + row["gender"] = translate_choice( + gw_labels, + "select_gender", + row["gender"], + language, + ) + row["work_category"] = translate_work_category( + row["work_category"], + language, + ) + elif row["work_category"] == "Irrigation Work": + row["demand_type"] = translate_choice( + agri_labels, + "demand_type_irrigation", + row["demand_type"], + language, + ) + row["work_demand"] = translate_choice( + agri_labels, + "TYPE_OF_WORK_ID", + row["work_demand"], + language, + ) + row["gender"] = translate_choice( + agri_labels, + "gender", + row["gender"], + language, + ) + row["work_category"] = translate_work_category( + row["work_category"], + language, + ) + return {"works": works} + + +def get_section_g_data(plan, language): + all_livelihood = get_livelihood_data(plan.id) + agro_labels = load_form_labels("Agrohorticulture") + livelihood_labels = load_form_labels("NRM Livelihood Form") + + livestock_fisheries = [ + r for r in all_livelihood if r["livelihood_work"] in ("Livestock", "Fisheries") + ] + for row in livestock_fisheries: + livelihood_work = row.get("livelihood_work") + + if livelihood_work == "Livestock": + row["demand_type"] = translate_choice( + livelihood_labels, + "livestock_demand", + row.get("demand_type"), + language, + ) + + row["work_demand"] = translate_choice( + livelihood_labels, + "demands_promoting_livestock", + row.get("work_demand"), + language, + ) + row["gender"] = translate_choice( + livelihood_labels, + "gender_livestock", + row.get("gender"), + language, + ) + + elif livelihood_work == "Fisheries": + row["demand_type"] = translate_choice( + livelihood_labels, + "demand_type_fisheries", + row.get("demand_type"), + language, + ) + + row["work_demand"] = translate_choice( + livelihood_labels, + "select_one_promoting_fisheries", + row.get("work_demand"), + language, + ) + row["gender"] = translate_choice( + livelihood_labels, + "gender_fisheries", + row.get("gender"), + language, + ) + + plantations_etc = [ + r + for r in all_livelihood + if r["livelihood_work"] not in ("Livestock", "Fisheries") + ] + for row in plantations_etc: + livelihood_work = row.get("livelihood_work") + + if livelihood_work == "Plantations": + + row["demand_type"] = translate_choice( + agro_labels, + "demand_type_plantations", + row.get("demand_type"), + language, + ) + row["gender"] = translate_choice( + agro_labels, + "gender", + row.get("gender"), + language, + ) + + species = row.get("work_demand") + + if species: + translated_species = [] + + for item in species.split(): + translated_species.append( + translate_choice( + agro_labels, + "select_multiple_species", + item.strip().lower(), + language, + ) + ) + + row["work_demand"] = ", ".join(translated_species) + + elif livelihood_work == "Kitchen Garden": + + row["demand_type"] = translate_choice( + livelihood_labels, + "demand_type_kitchen_garden", + row.get("demand_type"), + language, + ) + row["gender"] = translate_choice( + livelihood_labels, + "gender_kitchen_gardens", + row.get("gender"), + language, + ) + for row in livestock_fisheries: + row["livelihood_work"] = translate_livelihood_work( + row["livelihood_work"], + language, + ) + + for row in plantations_etc: + row["livelihood_work"] = translate_livelihood_work( + row["livelihood_work"], + language, + ) + return { + "livestock_fisheries": livestock_fisheries, + "plantations": plantations_etc, + } + + +def clean_odk_value(value): + if value is None: + return None + + value = str(value).strip() + + if value.upper() == "NA": + return None + + return value + + +REPAIR_LABEL_MAPPING = { + "community pond": "select_one_community_pond", + "large water body": "select_one_repair_large_water_body", + "farm pond": "select_one_farm_pond", + "canal": "select_one_repair_canal", + "check dam": "select_one_check_dam", + "percolation tank": "select_one_percolation_tank", + "rock fill dam": "select_one_rock_fill_dam", + "loose boulder structure": "select_one_loose_boulder_structure", + "5% model structure": "select_one_model5_structure", + "30-40 model structure": "select_one_model30_40_structure", +} +REPAIR_ACTIVITY_MAPPING = { + "check dam": "select_one_check_dam", + "percolation tank": "select_one_percolation_tank", + "earthen gully plug": "select_one_earthen_gully_plug", + "drainage/soakage channels": "select_one_drainage_soakage_channels", + "recharge pits": "select_one_recharge_pits", + "sokage pits": "select_one_sokage_pits", + "trench cum bund network": "select_one_trench_cum_bund_network", + "continuous contour trenches (cct)": "select_one_continuous_contour_trenches", + "staggered contour trenches(sct)": "select_one_staggered_contour_trenches", + "water absorption trenches(wat)": "select_one_water_absorption_trenches", + "loose boulder structure": "select_one_loose_boulder_structure", + "rock fill dam": "select_one_rock_fill_dam", + "stone bunding": "select_one_stone_bunding", + "diversion drains": "select_one_diversion_drains", + "bunding:contour bunds/ graded bunds": "select_one_bunding", + "5% model structure": "select_one_model5_structure", + "30-40 model structure": "select_one_model30_40_structure", +} + + +def get_maintenance_data(plan_id, maintenance_type): + pid = str(plan_id) + result = [] + + if maintenance_type == "gw": + for m in GW_maintenance.objects.filter(plan_id=pid).exclude(is_deleted=True): + d = m.data_gw_maintenance or {} + structure_type = d.get("select_one_recharge_structure") or None + repair = _resolve_repair_activity( + d, + structure_type, + RECHARGE_STRUCTURE_MAPPING, + ) + result.append( + { + "id": m.gw_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "gender": d.get("select_gender"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + elif maintenance_type == "agri": + for m in Agri_maintenance.objects.filter(plan_id=pid).exclude(is_deleted=True): + d = m.data_agri_maintenance or {} + structure_type = ( + d.get("select_one_water_structure") + or d.get("select_one_irrigation_structure") + or "NA" + ) + repair = _resolve_repair_activity( + d, structure_type, IRRIGATION_STRUCTURE_REVERSE_MAPPING + ) + result.append( + { + "id": m.agri_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + elif maintenance_type == "swb": + for m in SWB_maintenance.objects.filter(plan_id=pid).exclude(is_deleted=True): + d = m.data_swb_maintenance or {} + structure_type = ( + d.get("TYPE_OF_WORK") or d.get("select_one_water_structure") or "NA" + ) + repair = _resolve_repair_activity( + d, structure_type, WATER_STRUCTURE_REVERSE_MAPPING + ) + result.append( + { + "id": m.swb_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "gender": d.get("select_gender"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + elif maintenance_type == "swb_rs": + for m in SWB_RS_maintenance.objects.filter(plan_id=pid).exclude( + is_deleted=True + ): + d = m.data_swb_rs_maintenance or {} + structure_type = d.get("TYPE_OF_WORK") or "NA" + repair = _resolve_repair_activity( + d, structure_type, RS_WATER_STRUCTIRE_REVERSE_MAPPING + ) + + result.append( + { + "id": m.swb_rs_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "gender": d.get("select_gender"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + return result + + +def _resolve_repair_activity( + data, structure_type, mapping, fallback_key="select_one_activities" +): + repair_activities = None + + structure_key = str(structure_type).strip().lower() + + repair_key = mapping.get(structure_key) + + if repair_key: + repair_activities = data.get(repair_key) + + if repair_activities == "other": + repair_activities = data.get(f"{repair_key}_other") + + if not repair_activities: + repair_activities = data.get(fallback_key) + + return repair_activities + + +RECHARGE_STRUCTURE_MAPPING = { + "Check dam": "select_one_check_dam", + "Percolation tank": "select_one_percolation_tank", + "Earthen gully plug": "select_one_earthen_gully_plug", + "Drainage/soakage channels": "select_one_drainage_soakage_channels", + "Recharge pits": "select_one_recharge_pits", + "Sokage pits": "select_one_sokage_pits", + "Trench cum bund network": "select_one_trench_cum_bund_network", + "Continuous contour trenches (CCT)": "select_one_continuous_contour_trenches", + "Staggered Contour trenches(SCT)": "select_one_staggered_contour_trenches", + "Water absorption trenches(WAT)": "select_one_water_absorption_trenches", + "Loose boulder structure": "select_one_loose_boulder_structure", + "Rock fill dam": "select_one_rock_fill_dam", + "Stone bunding": "select_one_stone_bunding", + "Diversion drains": "select_one_diversion_drains", + "Bunding:Contour bunds/ graded bunds": "select_one_bunding", + "5% model structure": "select_one_model5_structure", + "30-40 model structure": "select_one_model30_40_structure", +} + + +def get_livelihood_data(plan_id): + pid = str(plan_id) + result = [] + + for record in ( + ODK_livelihood.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + dl = record.data_livelihood or {} + + livestock_group = dl.get("Livestock") or {} + fisheries_group = dl.get("fisheries") or {} + plantation_group = dl.get("plantations") or {} + kitchen_garden_group = dl.get("kitchen_gardens") or {} + + is_livestock = ( + ensure_str(livestock_group.get("is_demand_livestock", "")).lower() == "yes" + or ensure_str(dl.get("select_one_demand_promoting_livestock", "")).lower() + == "yes" + ) + if is_livestock: + demands = ensure_str(livestock_group.get("demands_promoting_livestock")) + if demands and demands.lower() == "other": + demands = livestock_group.get("demands_promoting_livestock_other") + if not demands: + demands = ensure_str(dl.get("select_one_promoting_livestock")) + if demands and demands.lower() == "other": + demands = dl.get("select_one_promoting_livestock_other") + result.append( + { + "livelihood_work": "Livestock", + "demand_type": livestock_group.get("livestock_demand"), + "work_demand": demands, + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or livestock_group.get("ben_livestock"), + "gender": livestock_group.get("gender_livestock"), + "beneficiary_father_name": livestock_group.get( + "ben_father_livestock" + ), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + is_fisheries = ( + ensure_str(fisheries_group.get("is_demand_fisheris", "")).lower() == "yes" + or ensure_str(dl.get("select_one_demand_promoting_fisheries", "")).lower() + == "yes" + ) + if is_fisheries: + demands = ensure_str(fisheries_group.get("select_one_promoting_fisheries")) + if demands and demands.lower() == "other": + demands = fisheries_group.get("select_one_promoting_fisheries_other") + if not demands: + demands = ensure_str(dl.get("select_one_promoting_fisheries")) + if demands and demands.lower() == "other": + demands = dl.get("select_one_promoting_fisheries_other") + result.append( + { + "livelihood_work": "Fisheries", + "demand_type": fisheries_group.get("demand_type_fisheries"), + "work_demand": demands, + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or fisheries_group.get("ben_fisheries"), + "gender": fisheries_group.get("gender_fisheries"), + "beneficiary_father_name": fisheries_group.get( + "ben_father_fisheries" + ), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + is_plantation = ( + ensure_str(dl.get("select_one_demand_plantation", "")).lower() == "yes" + or ensure_str(plantation_group.get("select_plantation_demands", "")).lower() + == "yes" + ) + if is_plantation: + result.append( + { + "livelihood_work": "Plantations", + "demand_type": plantation_group.get("demand_type_plantations"), + "work_demand": dl.get("Plantation") + or plantation_group.get("crop_name"), + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or plantation_group.get("ben_plantation"), + "gender": plantation_group.get("gender"), + "beneficiary_father_name": plantation_group.get("ben_father"), + "total_acres": dl.get("Plantation_crop") + or plantation_group.get("crop_area"), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + is_kitchen_garden = ( + ensure_str(dl.get("indi_assets", "")).lower() == "yes" + or ensure_str(kitchen_garden_group.get("assets_kg", "")).lower() == "yes" + ) + if is_kitchen_garden: + result.append( + { + "livelihood_work": "Kitchen Garden", + "demand_type": kitchen_garden_group.get( + "demand_type_kitchen_garden" + ), + "work_demand": dl.get("Plantation"), + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or kitchen_garden_group.get("ben_kitchen_gardens"), + "gender": kitchen_garden_group.get("gender_kitchen_gardens"), + "beneficiary_father_name": kitchen_garden_group.get( + "ben_father_kitchen_gardens" + ), + "total_acres": dl.get("area_didi_badi") + or kitchen_garden_group.get("area_kg"), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + for agrohorti in ( + ODK_agrohorticulture.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + data = agrohorti.data_agohorticulture or {} + species_parts = filter( + None, + [ + data.get("select_multiple_species"), + data.get("select_multiple_species_other"), + ], + ) + species = " ".join(species_parts) or None + result.append( + { + "livelihood_work": "Plantations", + "demand_type": data.get("demand_type_plantations"), + "work_demand": species, + "beneficiary_settlement": data.get("beneficiary_settlement"), + "beneficiary_name": data.get("beneficiary_name"), + "gender": data.get("gender"), + "beneficiary_father_name": data.get("ben_father"), + "total_acres": data.get("crop_area"), + "latitude": agrohorti.latitude, + "longitude": agrohorti.longitude, + } + ) + + return result + + +def translate_livelihood_work(value, language): + translations = { + "hi": { + "Livestock": "पशुपालन", + "Fisheries": "मत्स्य पालन", + "Plantations": "पौधारोपण", + "Kitchen Garden": "रसोई बाड़ी", + }, + "od": { + "Livestock": "ପଶୁପାଳନ", + "Fisheries": "ମତ୍ସ୍ୟଚାଷ", + "Plantations": "ବୃକ୍ଷରୋପଣ", + "Kitchen Garden": "ରୋଷେଇ ବଗିଚା", + }, + } + + return translations.get(language, {}).get(value, value) + + +def get_nrm_works_data( + plan_id, +): + pid = str(plan_id) + result = [] + + for structure in ( + ODK_groundwater.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + dg = structure.data_groundwater or {} + result.append( + { + "work_category": "Recharge Structure", + "demand_type": dg.get("demand_type"), + "work_demand": structure.work_type, + "beneficiary_settlement": structure.beneficiary_settlement, + "beneficiary_name": dg.get("Beneficiary_Name"), + "gender": dg.get("select_gender"), + "beneficiary_father_name": dg.get("ben_father"), + "latitude": structure.latitude, + "longitude": structure.longitude, + } + ) + + for irr in ( + ODK_agri.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + da = irr.data_agri or {} + work_demand = irr.work_type + if (irr.work_type or "").lower() == "other": + work_demand = da.get("TYPE_OF_WORK_ID_other") or "Other (unspecified)" + result.append( + { + "work_category": "Irrigation Work", + "demand_type": da.get("demand_type_irrigation"), + "work_demand": work_demand, + "beneficiary_settlement": irr.beneficiary_settlement, + "beneficiary_name": da.get("Beneficiary_Name"), + "gender": da.get("gender"), + "beneficiary_father_name": da.get("ben_father"), + "latitude": irr.latitude, + "longitude": irr.longitude, + } + ) + + return result + + +def translate_work_category(value, language): + + translations = { + "hi": { + "Recharge Structure": "पुनर्भरण संरचना", + "Irrigation Work": "सिंचाई कार्य", + }, + "od": { + "Recharge Structure": "ପୁନର୍ଭରଣ ସଂରଚନା", + "Irrigation Work": "ସିଚାଇ କାର୍ଯ୍ୟ", + }, + } + + return translations.get(language, {}).get(value, value) + + +_COMMUNITY_DEMAND_VALUES = { + "community", + "community well", + "community demand", + "public", + "public well", + "shared among families", +} +_INDIVIDUAL_DEMAND_VALUES = {"private", "privately owned", "individual demand"} + + +def classify_demand_type(raw_value): + if not raw_value: + return raw_value + normalized = raw_value.strip().lower().replace("_", " ") + if normalized in _COMMUNITY_DEMAND_VALUES: + return "community_demand" + if normalized in _INDIVIDUAL_DEMAND_VALUES: + return "individual_demand" + return raw_value + + +IRRIGATION_STRUCTURE_MAPPING = { + "select_one_farm_pond": "Farm pond", + "select_one_community_pond": "Community Pond", + "select_one_well": "Well", + "select_one_canal": "Canal", + "select_one_farm_bund": "Farm bund", +} + +IRRIGATION_STRUCTURE_REVERSE_MAPPING = { + v.lower(): k for k, v in IRRIGATION_STRUCTURE_MAPPING.items() +} + +RS_WATER_STRUCTURE_MAPPING = { + "select_one_farm_pond": "Farm pond", + "select_one_community_pond": "Community Pond", + "select_one_repair_large_water_body": "Large water body", + "select_one_repair_canal": "Canal", + "select_one_check_dam": "Check dam", + "select_one_percolation_tank": "Percolation tank", + "select_one_rock_fill_dam": "Rock fill dam", + "select_one_loose_boulder_structure": "Loose boulder structure", + "select_one_model5_structure": "5% Model structure", + "select_one_Model30_40_structure": "30-40 Model structure", +} + +RS_WATER_STRUCTIRE_REVERSE_MAPPING = { + v.lower(): k for k, v in RS_WATER_STRUCTURE_MAPPING.items() +} + + +WATER_STRUCTURE_MAPPING = { + "select_one_farm_pond": "Farm pond", + "select_one_community_pond": "Community Pond", + "select_one_repair_large_water_body": "Large water body", + "select_one_repair_canal": "Canal", + "select_one_check_dam": "Check dam", + "select_one_percolation_tank": "Percolation tank", + "select_one_rock_fill_dam": "Rock fill dam", + "select_one_loose_boulder_structure": "Loose boulder structure", + "select_one_model5_structure": "5% Model structure", + "select_one_Model30_40_structure": "30-40 Model structure", +} + +WATER_STRUCTURE_REVERSE_MAPPING = { + v.lower(): k for k, v in WATER_STRUCTURE_MAPPING.items() +} diff --git a/dpr/models.py b/dpr/models.py index 83f7b33e..2959b52d 100644 --- a/dpr/models.py +++ b/dpr/models.py @@ -4,6 +4,7 @@ from django.db.models import Max from django.db.models.functions import Greatest +from django.utils import timezone DPR_STATUS_CHOICES = [ ("PENDING", "PENDING"), @@ -664,8 +665,9 @@ def get_latest_change_time(plan_id): latest_moderation=Max("moderated_at"), ) for key in ("latest_submission", "latest_deletion", "latest_moderation"): - if agg[key]: - times.append(agg[key]) + dt = normalize_datetime(agg[key]) + if dt: + times.append(dt) return max(times) if times else None def needs_regeneration(self): @@ -675,3 +677,13 @@ def needs_regeneration(self): if not latest_change: return False return latest_change > self.dpr_generated_at + + +def normalize_datetime(dt): + if not dt: + return None + + if timezone.is_naive(dt): + return timezone.make_aware(dt, timezone.get_current_timezone()) + + return dt diff --git a/dpr/service/__init__.py b/dpr/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dpr/service/form_download_service.py b/dpr/service/form_download_service.py new file mode 100644 index 00000000..4d2db4c7 --- /dev/null +++ b/dpr/service/form_download_service.py @@ -0,0 +1,249 @@ +import json +from pathlib import Path +import requests +from utilities.constants import ODK_BASE_URL, ODK_PROJECT_ID +from django.conf import settings +from plans.utils import fetch_bearer_token +from nrm_app.settings import ODK_USERNAME, ODK_PASSWORD +import pandas as pd +from utilities.logger import setup_logger +import re + +logger = setup_logger(__name__) + + +def sync_odk_forms(): + """ + Downloads ODK XLSX forms only when: + 1. Form version has changed, OR + 2. XLSX file is missing locally. + + Returns: + dict: Summary of downloaded and skipped forms. + """ + + token = fetch_bearer_token(ODK_USERNAME, ODK_PASSWORD) + headers = {"Authorization": f"Bearer {token}"} + + forms_dir = Path(settings.BASE_DIR) / "data" / "dpr" / "forms" + label_dirs = Path(settings.BASE_DIR) / "data" / "dpr" / "labels" + versions_file = Path(settings.BASE_DIR) / "data" / "odk" / "form_version.json" + + forms_dir.mkdir(parents=True, exist_ok=True) + versions_file.parent.mkdir(parents=True, exist_ok=True) + + if versions_file.exists(): + with open(versions_file, "r") as f: + local_versions = json.load(f) + else: + local_versions = {} + + response = requests.get( + f"{ODK_BASE_URL}{ODK_PROJECT_ID}/forms", + headers=headers, + ) + response.raise_for_status() + + forms = response.json() + + downloaded = [] + skipped = [] + + for form in forms: + form_id = form["xmlFormId"] + current_version = form.get("version") + + saved_version = local_versions.get(form_id) + file_path = forms_dir / f"{form_id}.xlsx" + label_path = label_dirs / f"{form_id}.json" + if ( + saved_version == current_version + and file_path.exists() + and label_path.exists() + ): + skipped.append(form_id) + continue + + print(f"Downloading {form_id} " f"(old={saved_version}, new={current_version})") + + file_response = requests.get( + f"{ODK_BASE_URL}{ODK_PROJECT_ID}/forms/{form_id}.xlsx", + headers=headers, + ) + file_response.raise_for_status() + + with open(file_path, "wb") as f: + f.write(file_response.content) + + local_versions[form_id] = current_version + downloaded.append(form_id) + try: + generate_labels_json( + file_path, + label_path, + ) + except Exception: + logger.exception(f"Failed to generate labels for {form_id}") + with open(versions_file, "w") as f: + json.dump(local_versions, f, indent=2) + + return { + "downloaded": downloaded, + "skipped": skipped, + "total_forms": len(forms), + } + + +def get_select_question_mapping(survey_df): + """ + Returns: + { + "gender": "gender", + "caste_group": "caste" + } + """ + + mapping = {} + + for _, row in survey_df.iterrows(): + + question_type = str(row.get("type", "")).strip() + question_name = row.get("name") + + if pd.isna(question_name): + continue + + question_name = str(question_name).strip() + + parts = question_type.split() + + if len(parts) < 2: + continue + + if parts[0] in ["select_one", "select_multiple"]: + mapping[question_name] = str(parts[1]).strip() + + return mapping + + +def generate_labels_json(form_path, output_path): + """ + Generate labels JSON from an XLSForm. + """ + + survey_df = pd.read_excel(form_path, sheet_name="survey") + choices_df = pd.read_excel(form_path, sheet_name="choices") + + # normalize list_name column + choices_df["list_name"] = choices_df["list_name"].astype(str).str.strip() + + question_mapping = get_select_question_mapping(survey_df) + + language_columns = [ + column for column in choices_df.columns if str(column).startswith("label::") + ] + + labels = {} + + for question_name, list_name in question_mapping.items(): + + question_name = str(question_name).strip() + list_name = str(list_name).strip() + + labels[question_name] = {} + + choice_rows = choices_df[choices_df["list_name"] == list_name] + + for _, row in choice_rows.iterrows(): + + choice_value = row.get("name") + + if pd.isna(choice_value): + continue + + choice_value = str(choice_value).strip().lower() + + labels[question_name][choice_value] = {} + + for column in language_columns: + + language = str(column).replace("label::", "").split("(")[0].strip() + + value = row.get(column) + + labels[question_name][choice_value][language] = ( + None if pd.isna(value) else str(value).strip() + ) + + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, "w", encoding="utf-8") as f: + json.dump(labels, f, ensure_ascii=False, indent=2) + + return labels + + +LANGUAGE_MAP = { + "en": "English", + "hi": "Hindi", + "gu": "Gujarati", + "kn": "Kannada", + "or": "Odia", +} + + +def load_form_labels(form_id): + label_file = Path(settings.BASE_DIR) / "data" / "dpr" / "labels" / f"{form_id}.json" + + if not label_file.exists(): + return {} + + with open(label_file, encoding="utf-8") as f: + return json.load(f) + + +def translate_choice(labels, field_name, value, language="en"): + if not value: + return value + + language_name = LANGUAGE_MAP.get(language, "English") + + normalized_labels = {str(key).strip(): val for key, val in labels.items()} + + field_labels = normalized_labels.get( + str(field_name).strip(), + {}, + ) + + value_normalized = str(value).strip().lower().replace("'", "’") + + translations = field_labels.get(value_normalized) + + if translations: + return translations.get(language_name, value) + + return value + + +def translate_multiple_choices( + labels, + field_name, + value, + language="en", +): + if not value: + return value + + values = [v.strip() for v in re.split(r"[,\s]+", str(value).strip()) if v.strip()] + + translated = [ + translate_choice( + labels, + field_name, + item, + language, + ) + for item in values + ] + + return ", ".join(translated) diff --git a/dpr/service/translation_service.py b/dpr/service/translation_service.py new file mode 100644 index 00000000..cb76f32a --- /dev/null +++ b/dpr/service/translation_service.py @@ -0,0 +1,16 @@ +import json +from pathlib import Path +from django.conf import settings + +TRANSLATION_DIR = Path(settings.BASE_DIR) / "data" / "dpr" / "translations" + + +def load_translations(language="en"): + + file_path = TRANSLATION_DIR / f"{language}.json" + + if not file_path.exists(): + file_path = TRANSLATION_DIR / "en.json" + + with open(file_path, "r", encoding="utf-8") as file: + return json.load(file) diff --git a/dpr/tasks.py b/dpr/tasks.py index b9082826..229a6075 100644 --- a/dpr/tasks.py +++ b/dpr/tasks.py @@ -1,3 +1,4 @@ +from django.templatetags.i18n import language from django.utils import timezone from nrm_app.celery import app from utilities.logger import setup_logger @@ -15,49 +16,61 @@ upload_dpr_to_s3, check_dpr_exists_on_s3, ) +from .views import generate_dpr_pdf logger = setup_logger(__name__) -def get_or_generate_dpr(plan, regenerate=False): +def get_or_generate_dpr(plan, regenerate=False, language="en"): dpr_report, created = DPR_Report.objects.get_or_create( plan_id=plan, - defaults={"plan_name": plan.plan, "status": "PENDING"} + defaults={"plan_name": plan.plan, "status": "PENDING"}, ) - + dpr_report_s3_url = f"https://dpr-resources.s3.ap-south-1.amazonaws.com/dpr-reports/{plan.id}_{plan.plan}_{language}.pdf" if not regenerate: - s3_exists = check_dpr_exists_on_s3(dpr_report.dpr_report_s3_url) + s3_exists = check_dpr_exists_on_s3(dpr_report_s3_url) if not created and not dpr_report.needs_regeneration() and s3_exists: - logger.info(f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}") + logger.info( + f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}" + ) return dpr_report, False - + logger.info(f"Generating new DPR for plan {plan.id}") dpr_report.status = "GENERATING" dpr_report.save(update_fields=["status"]) - - doc = create_dpr_document(plan) - s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan) - + + doc = generate_dpr_pdf(plan, language=language) + s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan, language) + dpr_report.dpr_report_s3_url = s3_url dpr_report.dpr_generated_at = timezone.now() dpr_report.status = "COMPLETED" dpr_report.last_updated_at = timezone.now() - dpr_report.save(update_fields=[ - "dpr_report_s3_url", "dpr_generated_at", "status", "last_updated_at" - ]) - + dpr_report.save( + update_fields=[ + "dpr_report_s3_url", + "dpr_generated_at", + "status", + "last_updated_at", + ] + ) + logger.info(f"DPR generated and saved to S3: {s3_url}") return dpr_report, True @app.task(bind=True, name="dpr.generate_dpr_task") -def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = False): +def generate_dpr_task( + self, plan_id: int, email_id: str, language: str, regenerate: bool = False +): plan = get_plan_details(plan_id) if plan is None: logger.error(f"Plan not found for ID: {plan_id}") return {"error": "Plan not found"} - dpr_report, was_regenerated = get_or_generate_dpr(plan, regenerate=regenerate) + dpr_report, was_regenerated = get_or_generate_dpr( + plan, regenerate=regenerate, language=language + ) mws_Ids = get_mws_ids_for_report(plan) mws_reports = [] @@ -83,7 +96,7 @@ def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = Fals f"https://geoserver.core-stack.org/api/v1/download_report/" f"?report_type=resource&district={district}&block={block}&plan_id={plan_id}&plan_name={plan.plan}" ) - + resource_report = None try: response = requests.get(resource_report_url, timeout=30) diff --git a/dpr/templatetags/__init__.py b/dpr/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dpr/templatetags/custom_filters.py b/dpr/templatetags/custom_filters.py new file mode 100644 index 00000000..2d191c3f --- /dev/null +++ b/dpr/templatetags/custom_filters.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + + +@register.filter +def format_text(value): + if not value: + return "NA" + + return str(value).replace("_", " ") diff --git a/dpr/urls.py b/dpr/urls.py index 2b2e51a0..8cbc6779 100644 --- a/dpr/urls.py +++ b/dpr/urls.py @@ -1,6 +1,7 @@ from django.urls import path from . import api +from . import views urlpatterns = [ path( @@ -21,9 +22,16 @@ name="generate_resource_report", ), path("download_report/", api.download_report, name="download_report"), - path("generate_tehsil_report/", api.generate_tehsil_report, name="generate_tehsil_report"), - path("generate_village_report/", api.generate_village_report, name="generate_village_report"), - + path( + "generate_tehsil_report/", + api.generate_tehsil_report, + name="generate_tehsil_report", + ), + path( + "generate_village_report/", + api.generate_village_report, + name="generate_village_report", + ), # DPR Data API path("dpr_data//summary/", api.dpr_summary, name="dpr_summary"), path( diff --git a/dpr/utils.py b/dpr/utils.py index 07fe2529..5a3acae7 100644 --- a/dpr/utils.py +++ b/dpr/utils.py @@ -14,7 +14,16 @@ from django.core.mail.backends.smtp import EmailBackend from docx import Document -from nrm_app.settings import EMAIL_HOST, EMAIL_HOST_PASSWORD, EMAIL_HOST_USER, EMAIL_PORT, EMAIL_TIMEOUT, EMAIL_USE_SSL, ODK_PASSWORD, ODK_USERNAME +from nrm_app.settings import ( + EMAIL_HOST, + EMAIL_HOST_PASSWORD, + EMAIL_HOST_USER, + EMAIL_PORT, + EMAIL_TIMEOUT, + EMAIL_USE_SSL, + ODK_PASSWORD, + ODK_USERNAME, +) from utilities.constants import ( ODK_URL_AGRI_MAINTENANCE, ODK_URL_GW_MAINTENANCE, @@ -45,7 +54,13 @@ ) import boto3 -from nrm_app.settings import DPR_S3_ACCESS_KEY, DPR_S3_SECRET_KEY, DPR_S3_REGION, DPR_S3_BUCKET, DPR_S3_FOLDER +from nrm_app.settings import ( + DPR_S3_ACCESS_KEY, + DPR_S3_SECRET_KEY, + DPR_S3_REGION, + DPR_S3_BUCKET, + DPR_S3_FOLDER, +) from botocore.exceptions import ClientError warnings.filterwarnings("ignore") @@ -265,8 +280,8 @@ def get_waterbody_repair_activities(data_waterbody, water_structure_type): and data_waterbody.get(other_field) ): return f"Other: {data_waterbody.get(other_field)}" - elif repair_value: - return repair_value.replace("_", " ").title() + # elif repair_value: + # return repair_value.replace("_", " ").title() return "NA" repair_field = structure_type_mapping.get(structure_type_lower) @@ -287,7 +302,7 @@ def get_waterbody_repair_activities(data_waterbody, water_structure_type): else: return "Other" - return repair_activity.replace("_", " ").title() + return repair_activity def sort_key(settlement): @@ -304,9 +319,10 @@ def transform_name(name): name = re.sub(r"^_|_$", "", name) return name.lower() + def to_utf8(value): """Ensure value is a properly encoded UTF-8 string for Word document. - + Handles cases where UTF-8 text was incorrectly decoded as Latin-1, resulting in garbled characters like 'ಪಾà²...' for Kannada/Hindi text. """ @@ -316,13 +332,13 @@ def to_utf8(value): value = " ".join(str(v) for v in value) if isinstance(value, bytes): try: - return value.decode('utf-8') + return value.decode("utf-8") except UnicodeDecodeError: - return value.decode('latin-1') + return value.decode("latin-1") if not isinstance(value, str): value = str(value) try: - return value.encode('latin-1').decode('utf-8') + return value.encode("latin-1").decode("utf-8") except (UnicodeDecodeError, UnicodeEncodeError): return value @@ -443,40 +459,40 @@ def send_dpr_email( logger.error(f"Failed to send email: {e}") -def upload_dpr_to_s3(doc, plan_id, plan_name): - doc_bytes = BytesIO() - doc.save(doc_bytes) - doc_bytes.seek(0) - +def upload_dpr_to_s3(pdf_bytes, plan_id, plan_name, language="en"): + + doc_bytes = BytesIO(pdf_bytes) + safe_plan_name = transform_name(plan_name) - s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}.docx" - + s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}_{language}.pdf" + s3_client = boto3.client( "s3", aws_access_key_id=DPR_S3_ACCESS_KEY, aws_secret_access_key=DPR_S3_SECRET_KEY, region_name=DPR_S3_REGION, ) - + s3_client.upload_fileobj( doc_bytes, DPR_S3_BUCKET, s3_key, ExtraArgs={ - "ContentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.docx"', + "ContentType": "application/pdf", + "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.pdf"', "CacheControl": "no-cache, no-store, must-revalidate", - } + }, ) - + ts = int(time.time()) s3_url = f"https://{DPR_S3_BUCKET}.s3.{DPR_S3_REGION}.amazonaws.com/{s3_key}?v={ts}" + logger.info(f"DPR uploaded to S3: {s3_url}") return s3_url def _extract_s3_key(s3_url): - + parsed = urlparse(s3_url) return parsed.path.lstrip("/") @@ -484,19 +500,19 @@ def _extract_s3_key(s3_url): def check_dpr_exists_on_s3(s3_url): if not s3_url: return False - + try: s3_key = _extract_s3_key(s3_url) except (IndexError, AttributeError): return False - + s3_client = boto3.client( "s3", aws_access_key_id=DPR_S3_ACCESS_KEY, aws_secret_access_key=DPR_S3_SECRET_KEY, region_name=DPR_S3_REGION, ) - + try: s3_client.head_object(Bucket=DPR_S3_BUCKET, Key=s3_key) return True @@ -507,18 +523,18 @@ def check_dpr_exists_on_s3(s3_url): def download_dpr_from_s3(s3_url): s3_key = _extract_s3_key(s3_url) - + s3_client = boto3.client( "s3", aws_access_key_id=DPR_S3_ACCESS_KEY, aws_secret_access_key=DPR_S3_SECRET_KEY, region_name=DPR_S3_REGION, ) - + doc_bytes = BytesIO() s3_client.download_fileobj(DPR_S3_BUCKET, s3_key, doc_bytes) doc_bytes.seek(0) - + doc = Document(doc_bytes) logger.info(f"DPR downloaded from S3: {s3_url}") - return doc \ No newline at end of file + return doc diff --git a/dpr/views.py b/dpr/views.py index 91ea44a2..7c0c9013 100644 --- a/dpr/views.py +++ b/dpr/views.py @@ -1,3 +1,63 @@ -from django.shortcuts import render +from datetime import date +from django.template.loader import render_to_string +from dpr.service.translation_service import load_translations +from weasyprint import HTML +from .gen_dpr import get_settlement_count_for_plan +from .utils import get_vector_layer_geoserver, transform_name +from nrm_app.settings import GEOSERVER_URL +from .get_dpr_sectionwise_data import ( + get_section_b_data, + get_section_c_data, + get_section_d_data, + get_section_e_data, + get_section_f_data, + get_section_g_data, +) +from .service.form_download_service import sync_odk_forms -# Create your views here. + +def generate_dpr_html(plan, language="en"): + translations = load_translations(language) + total_settlements = get_settlement_count_for_plan(plan.id) + mws_fortnight = get_vector_layer_geoserver( + geoserver_url=GEOSERVER_URL, + workspace="mws_layers", + layer_name="deltaG_fortnight_" + + transform_name(str(plan.district_soi.district_name)) + + "_" + + transform_name(str(plan.tehsil_soi.tehsil_name)), + ) + section_b_data, settlement_mws_ids, mws_gdf = get_section_b_data( + plan, total_settlements, mws_fortnight + ) + section_c_data = get_section_c_data(plan, language) + section_d_data = get_section_d_data(plan, settlement_mws_ids, mws_gdf, language) + section_e_data = get_section_e_data(plan, language) + section_f_data = get_section_f_data(plan, language) + section_g_data = get_section_g_data(plan, language) + html = render_to_string( + "dpr/base.html", + { + "t": translations, + "current_date": date.today().strftime("%B %d, %Y"), + "section_a": plan, + "section_b": section_b_data, + "section_c": section_c_data, + "section_d": section_d_data, + "section_e": section_e_data, + "section_f": section_f_data, + "section_g": section_g_data, + "footnote": f"DPR supported by {plan.organization.name} in {date.today().year}", + }, + ) + + return html + + +def generate_dpr_pdf(plan, language="en"): + sync_odk_forms() + html = generate_dpr_html(plan, language) + + pdf = HTML(string=html).write_pdf() + + return pdf diff --git a/templates/dpr/base.html b/templates/dpr/base.html new file mode 100644 index 00000000..d1eb68e2 --- /dev/null +++ b/templates/dpr/base.html @@ -0,0 +1,147 @@ + + + + + + + + + + +

    + {{ t.common.dpr_title }} +

    + +
    + {{ current_date }} +
    + + {% include "dpr/section_a.html" %} + {% include "dpr/section_b.html" %} + {% include "dpr/section_c_socio_economic.html" %} + {% include "dpr/section_c_mgnrega.html" %} + {% include "dpr/section_c_crop.html" %} + {% include "dpr/section_c_livelihood.html" %} + {% include "dpr/section_d_base.html" %} + {% include "dpr/section_d_mws_information.html" %} + {% include "dpr/section_d_well_summary.html" %} + {% include "dpr/section_d_detail_well_info.html" %} + {% include "dpr/section_d_water_structure_summary.html" %} + {% include "dpr/section_d_detail_water_structure.html" %} + {% include "dpr/section_e_base.html" %} + {% include "dpr/section_e_maintenance_work_by_asset_type.html" %} + {% include "dpr/section_e_recharge_structure.html" %} + {% include "dpr/section_e_irrigation_structure.html" %} + {% include "dpr/section_e_water_structure.html" %} + {% include "dpr/section_e_rs_swb.html" %} + {% include "dpr/section_f.html" %} + {% include "dpr/section_g.html" %} + +
    +
    +

    {{ footnote }}

    +
    + + + + \ No newline at end of file diff --git a/templates/dpr/section_a.html b/templates/dpr/section_a.html new file mode 100644 index 00000000..7b6a804c --- /dev/null +++ b/templates/dpr/section_a.html @@ -0,0 +1,61 @@ +
    + +

    + {{ t.section_a.title }} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + {{ t.section_a.organization }} + + {{ section_a.organization.name|default:t.common.na }} +
    + {{ t.section_a.project }} + + {{ section_a.project.name|default:t.common.na }} +
    + {{ t.section_a.plan }} + + {{ section_a.plan|default:t.common.na }} +
    + {{ t.section_a.facilitator }} + + {{ section_a.facilitator_name|default:t.common.na }} +
    + {{ t.section_a.process_preparation }} + + {{ t.section_a.process_preparation_value }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_b.html b/templates/dpr/section_b.html new file mode 100644 index 00000000..0be2ac45 --- /dev/null +++ b/templates/dpr/section_b.html @@ -0,0 +1,117 @@ +
    + +

    + {{ t.section_b.title }} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + {{ t.section_b.village }} + + {{ section_b.village_name|default:t.common.na }} +
    + {{ t.section_b.gram_panchayat }} + + {{ section_b.gram_panchayat|default:t.common.na }} +
    + {{ t.section_b.tehsil }} + + {{ section_b.tehsil|default:t.common.na }} +
    + {{ t.section_b.district }} + + {{ section_b.district|default:t.common.na }} +
    + {{ t.section_b.state }} + + {{ section_b.state|default:t.common.na }} +
    + {{ t.section_b.number_of_settlements }} + + {{ section_b.total_settlements|default:t.common.na }} +
    + {{ t.section_b.intersecting_mws }} + + + {% if section_b.settlement_mws_pairs %} + + + + + + + + {% for item in section_b.settlement_mws_pairs %} + + + + + {% endfor %} + +
    + {{ t.section_b.intersecting_mws_inner_settlement }} + + {{ t.section_b.intersecting_mws_inner_mws }} +
    {{ item.settlement }}{{ item.mws_id }}
    + + {% else %} + No intersecting watersheds + {% endif %} + +
    + {{ t.section_b.latitude_and_longitude }} + + {{ section_b.village_coordinates|default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_c_crop.html b/templates/dpr/section_c_crop.html new file mode 100644 index 00000000..84e00c09 --- /dev/null +++ b/templates/dpr/section_c_crop.html @@ -0,0 +1,72 @@ +
    + +

    + {{ t.section_c_crop.title }} +

    + + + + + + + + + + + + + + + + + + {% for item in section_c.crop_info %} + + + + + + + + + + + + + + + + + + + + + + + + {% endfor %} +
    {{ t.section_c_crop.settlement_name }}{{ t.section_c_crop.irrigation_source }}{{ t.section_c_crop.crop_in_kharif }}{{ t.section_c_crop.kharif_acerage }}{{ t.section_c_crop.crop_in_rabi }}{{ t.section_c_crop.rabi_acerage }}{{ t.section_c_crop.crop_in_zaid }}{{ t.section_c_crop.zaid_acerage }}{{ t.section_c_crop.crop_intensity }}{{ t.section_c_crop.land_classification }}
    + {{ item.beneficiary_settlement|default:t.common.na }} + + {{ item.irrigation_source|default:t.common.na }} + + + {{ item.kharif_crops|default:t.common.na }} + + + {{ item.kharif_acres |default:t.common.na }} + + {{ item.rabi_crops |default:t.common.na}} + + {{ item.rabi_acres |default:t.common.na }} + + {{ item.zaid_crops |default:t.common.na }} + + {{ item.zaid_acres |default:t.common.na }} + + {{ item.cropping_intensity |default:t.common.na }} + + {{ item.land_classification |default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_c_livelihood.html b/templates/dpr/section_c_livelihood.html new file mode 100644 index 00000000..8bab15de --- /dev/null +++ b/templates/dpr/section_c_livelihood.html @@ -0,0 +1,52 @@ + +
    + +

    + {{ t.section_c_livelihood.title }} +

    + + + + + + + + + + + + + {% for item in section_c.livestock_info %} + + + + + + + + + + + + + + + + {% endfor %} +
    {{ t.section_c_livelihood.settlement_name }}{{ t.section_c_livelihood.goat }}{{ t.section_c_livelihood.sheep }}{{ t.section_c_livelihood.cattle }}{{ t.section_c_livelihood.piggery }}{{ t.section_c_livelihood.poultary }}
    + {{ item.settlement_name|default:t.common.na }} + + {{ item.goats|default:t.common.na }} + + + {{ item.sheep|default:t.common.na }} + + + {{ item.cattle |default:t.common.na }} + + {{ item.piggery |default:t.common.na}} + + {{ item.poultry |default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_c_mgnrega.html b/templates/dpr/section_c_mgnrega.html new file mode 100644 index 00000000..040bd3e6 --- /dev/null +++ b/templates/dpr/section_c_mgnrega.html @@ -0,0 +1,66 @@ +
    + +

    + {{ t.section_c_mgnrega.title }} +

    + + + + + + + + + + + + + {% for item in section_c.mgnrega %} + + + + + + + + + + + + + + + + {% endfor %} +
    {{ t.section_c_mgnrega.settlement_name }}{{ t.section_c_mgnrega.total_household_applied_job_card }}{{ t.section_c_mgnrega.work_days_previous_year }}{{ t.section_c_mgnrega.demand_made_in_previous_year }}{{ t.section_c_mgnrega.involved_in_village_planing }}{{ t.section_c_mgnrega.issues }}
    + {{ item.settlement_name|default:t.common.na }} + +

    {{ t.section_c_mgnrega.applied }}: + {% if item.nrega_job_applied == 0 %} + {{ t.common.na }} + {% else %} + {{ item.nrega_job_applied }} + {% endif %} +

    +

    {{ t.section_c_mgnrega.having }}: + {% if item.nrega_job_card == 0 %} + {{ t.common.na }} + {% else %} + {{ item.nrega_job_card }} + {% endif %} +

    +
    + {% if item.nrega_work_days == 0 %} + {{ t.common.na }} + {% else %} + {{ item.nrega_work_days }} + {% endif %} + + {{ item.nrega_past_work_label|default:t.common.na }} + + {{ item.nrega_demand_label|default:t.common.na}} + + {{ item.nrega_issues_label|default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_c_socio_economic.html b/templates/dpr/section_c_socio_economic.html new file mode 100644 index 00000000..341a8a77 --- /dev/null +++ b/templates/dpr/section_c_socio_economic.html @@ -0,0 +1,82 @@ +
    + +

    + {{ t.section_c_socio_economic.title }} +

    + +

    {{ t.section_c_socio_economic.description }}

    + + + + + + + + + + + + + + + + {% for item in section_c.socio_eco %} + + + + + + + + + + + + + + + + {% endfor %} + + +
    {{ t.section_c_socio_economic.name_of_the_settlement }}{{ t.section_c_socio_economic.number_of_households }}{{ t.section_c_socio_economic.settlement_type }}{{ t.section_c_socio_economic.caste_group }}{{ t.section_c_socio_economic.total_households }}{{ t.section_c_socio_economic.marginal_farmer }}
    + {{ item.settlement_name|default:t.common.na }} + + {{ item.number_of_households|default:t.common.na }} + + {{ item.largest_caste_label|default:t.common.na }} + + {% if item.largest_caste|lower == "single caste group" %} + {{ item.smallest_caste_label|default:t.common.na }} + {% elif item.largest_caste|lower == "mixed caste group" %} + {{ item.settlement_status_label|default:t.common.na }} + {% else %} + {{ t.common.na }} + {% endif %} + + + + + + + + + + + + + + + + + + + + + +
    {{ t.section_c_socio_economic.sc }}{{ item.data_settlement.count_sc|default:t.common.na }}
    {{ t.section_c_socio_economic.st }}{{ item.data_settlement.count_st|default:t.common.na }}
    {{ t.section_c_socio_economic.obc }}{{ item.data_settlement.count_obc|default:t.common.na }}
    {{ t.section_c_socio_economic.gen }}{{ item.data_settlement.count_general|default:t.common.na }}
    +
    + {{ item.farmer_family.marginal_farmers|default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_d_base.html b/templates/dpr/section_d_base.html new file mode 100644 index 00000000..64ef8085 --- /dev/null +++ b/templates/dpr/section_d_base.html @@ -0,0 +1,7 @@ +
    + +

    {{ t.section_d_base.title }}

    + +

    {{ t.section_d_base.description }}

    + +
    \ No newline at end of file diff --git a/templates/dpr/section_d_detail_water_structure.html b/templates/dpr/section_d_detail_water_structure.html new file mode 100644 index 00000000..db7b7df7 --- /dev/null +++ b/templates/dpr/section_d_detail_water_structure.html @@ -0,0 +1,87 @@ +
    + +

    {{ t.section_d_detail_water_structure.title }}

    + + {% for waterbody in section_d.water_structures %} + +

    + {{ waterbody.settlement|default:t.common.na }} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{ t.section_d_detail_water_structure.mws_id }}{{ waterbody.mws_id|default:t.common.na }}
    {{ t.section_d_detail_water_structure.beneficiary_settlement }}{{ waterbody.settlement|default:t.common.na }}
    {{ t.section_d_detail_water_structure.water_structure_owner }}{{ waterbody.owner|default:t.common.na }}
    {{ t.section_d_detail_water_structure.beneficiary_name }}{{ waterbody.beneficiary_name|default:t.common.na }}
    {{ t.section_d_detail_water_structure.beneficiary_father_name }}{{ waterbody.father_name|default:t.common.na }}
    {{ t.section_d_detail_water_structure.who_manage }}{{ waterbody.who_manages|default:t.common.na }}
    {{ t.section_d_detail_water_structure.caste_use }}{{ waterbody.caste_uses|default:t.common.na }}
    {{ t.section_d_detail_water_structure.benefitted_household }}{{ waterbody.households_benefitted|default:t.common.na }}
    {{ t.section_d_detail_water_structure.water_structure_type }}{{ waterbody.structure_type|default:t.common.na }}
    {{ t.section_d_detail_water_structure.usage }}{{ waterbody.usage|default:t.common.na }}
    {{ t.section_d_detail_water_structure.maintenance }}{{ waterbody.need_maintenance|default:t.common.na }}
    {{ t.section_d_detail_water_structure.repair_activities }}{{ waterbody.repair_activities|default:t.common.na }}
    {{ t.section_d_detail_water_structure.latitude }}{{ waterbody.latitude|default:t.common.na }}
    {{ t.section_d_detail_water_structure.longitude }}{{ waterbody.longitude|default:t.common.na }}
    + + {% endfor %} + +
    \ No newline at end of file diff --git a/templates/dpr/section_d_detail_well_info.html b/templates/dpr/section_d_detail_well_info.html new file mode 100644 index 00000000..378d25a4 --- /dev/null +++ b/templates/dpr/section_d_detail_well_info.html @@ -0,0 +1,87 @@ +
    + +

    {{ t.section_d_detail_well.title }}

    + + {% for well in section_d.wells %} + +

    + {{ well.settlement|default:t.common.na }} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{ t.section_d_detail_well.mws_id }}{{ well.mws_id|default:t.common.na }}
    {{ t.section_d_detail_well.beneficiary_settlement }}{{ well.settlement|default:t.common.na }}
    {{ t.section_d_detail_well.well_type }}{{ well.well_type|default:t.common.na }}
    {{ t.section_d_detail_well.well_owner }}{{ well.owner|default:t.common.na }}
    {{ t.section_d_detail_well.beneficiary_name }}{{ well.beneficiary_name|default:t.common.na }}
    {{ t.section_d_detail_well.beneficiary_father_name }}{{ well.father_name|default:t.common.na }}
    {{ t.section_d_detail_well.water_availability }}{{ well.water_availability|default:t.common.na }}
    {{ t.section_d_detail_well.households_benefitted }}{{ well.households_benefitted|default:t.common.na }}
    {{ t.section_d_detail_well.caste_use }}{{ well.caste_uses|default:t.common.na }}
    {{ t.section_d_detail_well.well_usage }}{{ well.well_usage|default:t.common.na }}
    {{ t.section_d_detail_well.need_maintenance }}{{ well.need_maintenance|default:t.common.na }}
    {{ t.section_d_detail_well.repair_activities }}{{ well.repair_activities|default:t.common.na }}
    {{ t.section_d_detail_well.latitude }}{{ well.latitude|default:t.common.na }}
    {{ t.section_d_detail_well.longitude }}{{ well.longitude|default:t.common.na }}
    + + {% endfor %} + +
    \ No newline at end of file diff --git a/templates/dpr/section_d_mws_information.html b/templates/dpr/section_d_mws_information.html new file mode 100644 index 00000000..24f14d7a --- /dev/null +++ b/templates/dpr/section_d_mws_information.html @@ -0,0 +1,31 @@ +
    + + + + + + + + + + + + {% for item in section_d.mws %} + + + + + + + + {% endfor %} + + + +
    {{ t.section_d_mws_info.mws_id }}{{ t.section_d_mws_info.lat_lon }}
    + {{ item.mws_id|default:t.common.na }} + + {{ item.centroid|default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_d_water_structure_summary.html b/templates/dpr/section_d_water_structure_summary.html new file mode 100644 index 00000000..f98e2798 --- /dev/null +++ b/templates/dpr/section_d_water_structure_summary.html @@ -0,0 +1,36 @@ +
    + +

    {{ t.section_d_water_structure_summary.title }}

    + + + + + + + + + + + + + + + {% for item in section_d.water_summary %} + + + + + + + + + + + + {% endfor %} + + + +
    {{ t.section_d_water_structure_summary.beneficiary_settlement }}{{ t.section_d_water_structure_summary.water_structure_type }}{{ t.section_d_water_structure_summary.waterbodies_number }}{{ t.section_d_water_structure_summary.household_benefitted }}
    {{ item.settlement|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.count|default:t.common.na }}{{ item.households|default:t.common.na }}
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_d_well_summary.html b/templates/dpr/section_d_well_summary.html new file mode 100644 index 00000000..907ed544 --- /dev/null +++ b/templates/dpr/section_d_well_summary.html @@ -0,0 +1,39 @@ +
    + +

    {{ t.section_d_well_summary.title }}

    + + + + + + + + + + + + + + {% for item in section_d.well_summary %} + + + + + + + + + + {% endfor %} + + + +
    {{ t.section_d_well_summary.beneficiary_settlement }}{{ t.section_d_well_summary.no_of_wells }}{{ t.section_d_well_summary.household_benefitted }}
    + {{ item.settlement|default:t.common.na }} + + {{ item.num_wells|default:t.common.na }} + + {{ item.households|default:t.common.na }} +
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_e_base.html b/templates/dpr/section_e_base.html new file mode 100644 index 00000000..8af3e81f --- /dev/null +++ b/templates/dpr/section_e_base.html @@ -0,0 +1,7 @@ +
    + +

    {{ t.section_e.title }}

    + +

    {{ t.section_e.description }}

    + +
    \ No newline at end of file diff --git a/templates/dpr/section_e_irrigation_structure.html b/templates/dpr/section_e_irrigation_structure.html new file mode 100644 index 00000000..ce811c3f --- /dev/null +++ b/templates/dpr/section_e_irrigation_structure.html @@ -0,0 +1,48 @@ +

    {{ t.section_e.irrigation_structures }}

    + + + + + + + + + + + + + + + + + + + {% for item in section_e.agri %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.irrigation_structure }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
    {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities }}{{ item.latitude }}{{ item.longitude }}
    {{ t.common.na }}
    \ No newline at end of file diff --git a/templates/dpr/section_e_maintenance_work_by_asset_type.html b/templates/dpr/section_e_maintenance_work_by_asset_type.html new file mode 100644 index 00000000..ec7e649f --- /dev/null +++ b/templates/dpr/section_e_maintenance_work_by_asset_type.html @@ -0,0 +1,26 @@ +

    {{ t.section_e.asset_type_heading }}

    + + + + + + + + + + + + + + + + + + + + + + + + +
    {{ t.section_e.asset_type }}
    {{ t.section_e.recharge_structure_asset }}
    {{ t.section_e.irrigation_structure_asset }}
    {{ t.section_e.water_structure_asset }}
    {{ t.section_e.rs_water_structure_asset }}
    \ No newline at end of file diff --git a/templates/dpr/section_e_recharge_structure.html b/templates/dpr/section_e_recharge_structure.html new file mode 100644 index 00000000..0fcb52c4 --- /dev/null +++ b/templates/dpr/section_e_recharge_structure.html @@ -0,0 +1,48 @@ +

    {{ t.section_e.water_recharge_structures }}

    + + + + + + + + + + + + + + + + + + + {% for item in section_e.gw %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.recharge_structure }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
    {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities|default:t.common.na}}{{ item.latitude }}{{ item.longitude }}
    {{ t.common.na }}
    \ No newline at end of file diff --git a/templates/dpr/section_e_rs_swb.html b/templates/dpr/section_e_rs_swb.html new file mode 100644 index 00000000..fb77f219 --- /dev/null +++ b/templates/dpr/section_e_rs_swb.html @@ -0,0 +1,48 @@ +

    {{ t.section_e.remote_sensed_surface_water_structures }}

    + + + + + + + + + + + + + + + + + + + {% for item in section_e.swb_rs %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.type_of_work }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
    {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities | default:t.common.na}}{{ item.latitude }}{{ item.longitude }}
    {{ t.common.na }}
    \ No newline at end of file diff --git a/templates/dpr/section_e_water_structure.html b/templates/dpr/section_e_water_structure.html new file mode 100644 index 00000000..7f8d994b --- /dev/null +++ b/templates/dpr/section_e_water_structure.html @@ -0,0 +1,48 @@ +

    {{ t.section_e.surface_water_structures }}

    + + + + + + + + + + + + + + + + + + + {% for item in section_e.swb %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.type_of_work }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
    {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities }}{{ item.latitude }}{{ item.longitude }}
    {{ t.common.na }}
    \ No newline at end of file diff --git a/templates/dpr/section_f.html b/templates/dpr/section_f.html new file mode 100644 index 00000000..3305f872 --- /dev/null +++ b/templates/dpr/section_f.html @@ -0,0 +1,64 @@ +
    + +

    + {{ t.section_f.title }} +

    + +

    + {{ t.section_f.description }} +

    + + + + + + + + + + + + + + + + + + + + + {% for item in section_f.works %} + + + + + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_f.serial_no }}{{ t.section_f.work_category }}{{ t.section_f.type_of_demand }}{{ t.section_f.work_demand }}{{ t.section_f.beneficiary_settlement }}{{ t.section_f.beneficiary_name }}{{ t.section_f.gender }}{{ t.section_f.father_name }}{{ t.section_f.latitude }}{{ t.section_f.longitude }}
    {{ forloop.counter }}{{ item.work_category|default:t.common.na }}{{ item.demand_type|default:t.common.na }}{{ item.work_demand|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.gender|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.latitude|default:t.common.na }}{{ item.longitude|default:t.common.na }}
    {{ t.common.na }}
    + +
    \ No newline at end of file diff --git a/templates/dpr/section_g.html b/templates/dpr/section_g.html new file mode 100644 index 00000000..7895c2d6 --- /dev/null +++ b/templates/dpr/section_g.html @@ -0,0 +1,115 @@ + +
    + +

    {{ t.section_g.title }}

    + + +

    {{ t.section_g.livestock_and_fisheries }}

    + + + + + + + + + + + + + + + + + + + + {% for item in section_g.livestock_fisheries %} + + + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_g.livelihood_work }}{{ t.section_g.type_of_demand }}{{ t.section_g.work_demand }}{{ t.section_g.beneficiary_settlement }}{{ t.section_g.beneficiary_name }}{{ t.section_g.gender }}{{ t.section_g.father_name }}{{ t.section_g.latitude }}{{ t.section_g.longitude }}
    {{ item.livelihood_work|default:t.common.na }}{{ item.demand_type|default:t.common.na }}{{ item.work_demand|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.gender|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.latitude|default:t.common.na }}{{ item.longitude|default:t.common.na }}
    {{ t.common.na }}
    + + +

    {{ t.section_g.plantations_and_kitchen_gardens }}

    + + + + + + + + + + + + + + + + + + + + + {% for item in section_g.plantations %} + + + + + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
    {{ t.section_g.livelihood_work }}{{ t.section_g.type_of_demand }}{{ t.section_g.beneficiary_settlement }}{{ t.section_g.beneficiary_name }}{{ t.section_g.gender }}{{ t.section_g.father_name }}{{ t.section_g.plantation_crop }}{{ t.section_g.total_acres }}{{ t.section_g.latitude }}{{ t.section_g.longitude }}
    {{ item.livelihood_work|default:t.common.na }}{{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.gender|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.work_demand|default:t.common.na }}{{ item.total_acres|default:t.common.na }}{{ item.latitude|default:t.common.na }}{{ item.longitude|default:t.common.na }}
    {{ t.common.na }}
    + +
    \ No newline at end of file From 2a3b805cc617f7b4303f837133061b7fc54c3c54 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 16 Jun 2026 16:40:54 +0530 Subject: [PATCH 14/38] plan count api (#1013) --- plans/api.py | 33 ++++++++++++++++++++++++++++++++- plans/urls.py | 1 + users/signals.py | 4 ++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/plans/api.py b/plans/api.py index 533a26f3..cd3ed18a 100644 --- a/plans/api.py +++ b/plans/api.py @@ -36,9 +36,11 @@ logger = logging.getLogger(__name__) from .build_layer import build_layer -from .models import ODKSyncLog, Plan +from .models import ODKSyncLog, PlanApp from .serializers import PlanAppSerializer from .utils import fetch_bearer_token, fetch_db_data +from geoadmin.models import GramPanchayat +from django.db.models import Q _COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( "layer_name", @@ -756,3 +758,32 @@ def map_plan_to_gp(request): }, } ) + + +@api_view(["GET"]) +@schema(None) +def plan_count(request): + """ + gives plan count on the basis of org_id or project_id and filter + """ + org_id = request.query_params.get("org_id") + project_id = request.query_params.get("project_id") + is_completed = request.query_params.get("is_completed") + + queryset = PlanApp.objects.filter(enabled=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if is_completed: + queryset = queryset.filter(is_completed=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if org_id: + plan_count = queryset.filter(organization=org_id).count() + elif project_id: + plan_count = queryset.filter(project=project_id).count() + else: + plan_count = 0 + + return Response({"plan_count": plan_count}) diff --git a/plans/urls.py b/plans/urls.py index 9beec875..675bc64a 100644 --- a/plans/urls.py +++ b/plans/urls.py @@ -46,4 +46,5 @@ path("", include(org_watershed_router.urls)), path("", include(global_router.urls)), path("map_plan_to_gp/", api.map_plan_to_gp, name="map_plan_to_gp"), + path("plan_count/", api.plan_count, name="plan_count"), ] diff --git a/users/signals.py b/users/signals.py index 55d4d1d1..34550fc9 100644 --- a/users/signals.py +++ b/users/signals.py @@ -45,7 +45,7 @@ def send_email_to_org_admin(sender, instance, created, **kwargs):

    Please take the following action:

    1. - Assign user to a project @@ -57,7 +57,7 @@ def send_email_to_org_admin(sender, instance, created, **kwargs): - © 2025 CoRE Stack. All rights reserved. + © 2025 CoRE Stack. All rights reserved. From bfb1b14422be5410124b4a545035f38e207a01fc Mon Sep 17 00:00:00 2001 From: "shiv.prakash1" Date: Tue, 16 Jun 2026 17:10:24 +0530 Subject: [PATCH 15/38] chnages for conflict --- computing/lulc_X_terrain/lulc_on_plain_cluster.py | 2 +- plans/api.py | 2 +- stats_generator/mws_indicators.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 5c706e32..00c073ed 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -31,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters" + + "_lulcXplains_clusters_bk02_june" ) asset_id = get_gee_asset_path(state, district, block) + asset_description diff --git a/plans/api.py b/plans/api.py index cd3ed18a..58bd4fc1 100644 --- a/plans/api.py +++ b/plans/api.py @@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) from .build_layer import build_layer -from .models import ODKSyncLog, PlanApp +from .models import ODKSyncLog, PlanApp, Plan from .serializers import PlanAppSerializer from .utils import fetch_bearer_token, fetch_db_data from geoadmin.models import GramPanchayat diff --git a/stats_generator/mws_indicators.py b/stats_generator/mws_indicators.py index 9b0c6962..49cedf07 100644 --- a/stats_generator/mws_indicators.py +++ b/stats_generator/mws_indicators.py @@ -959,6 +959,7 @@ def sens_slope(data): lulc_crop_percent = round( (cropped_area_in_ha / area_in_ha) * 100, 2 ) + else: lulc_shrub_percent = -9999 lulc_forest_percent = -9999 From 94f1c79f0fce4af8a995607298a9a02b6ab3866d Mon Sep 17 00:00:00 2001 From: aman verma Date: Tue, 16 Jun 2026 11:58:50 +0000 Subject: [PATCH 16/38] merge fix --- computing/api.py | 17 +- computing/urls.py | 4 + computing/utils.py | 89 ++ computing/views.py | 314 ++-- dpr/api.py | 15 +- dpr/gen_dpr.py | 6 +- dpr/get_dpr_sectionwise_data.py | 1291 +++++++++++++++++ dpr/models.py | 16 +- dpr/service/__init__.py | 0 dpr/service/form_download_service.py | 249 ++++ dpr/service/translation_service.py | 16 + dpr/tasks.py | 47 +- dpr/templatetags/__init__.py | 0 dpr/templatetags/custom_filters.py | 11 + dpr/urls.py | 14 +- dpr/utils.py | 74 +- dpr/views.py | 64 +- nrm_app/settings.py | 3 +- plans/api.py | 33 +- plans/urls.py | 1 + stats_generator/mws_indicators.py | 179 +-- stats_generator/utils.py | 339 +++-- stats_generator/village_indicators.py | 50 +- templates/dpr/base.html | 147 ++ templates/dpr/section_a.html | 61 + templates/dpr/section_b.html | 117 ++ templates/dpr/section_c_crop.html | 72 + templates/dpr/section_c_livelihood.html | 52 + templates/dpr/section_c_mgnrega.html | 66 + templates/dpr/section_c_socio_economic.html | 82 ++ templates/dpr/section_d_base.html | 7 + .../dpr/section_d_detail_water_structure.html | 87 ++ templates/dpr/section_d_detail_well_info.html | 87 ++ templates/dpr/section_d_mws_information.html | 31 + .../section_d_water_structure_summary.html | 36 + templates/dpr/section_d_well_summary.html | 39 + templates/dpr/section_e_base.html | 7 + .../dpr/section_e_irrigation_structure.html | 48 + ...tion_e_maintenance_work_by_asset_type.html | 26 + .../dpr/section_e_recharge_structure.html | 48 + templates/dpr/section_e_rs_swb.html | 48 + templates/dpr/section_e_water_structure.html | 48 + templates/dpr/section_f.html | 64 + templates/dpr/section_g.html | 115 ++ users/signals.py | 4 +- utilities/scripts/tree_health/README.md | 116 ++ .../IS_DW_sentinel_data_export_new_year.ipynb | 1 + .../predict_ccd_ch_results.ipynb | 1 + .../colab_notebooks/trees_corrections.ipynb | 1 + .../colab_notebooks/uploadAssets.ipynb | 1 + .../uploadAssets_correction.ipynb | 1 + .../tree_health/gee_scripts/ccd_change.js | 76 + .../tree_health/gee_scripts/ch_change.js | 74 + .../tree_health/gee_scripts/fc_to_image.js | 157 ++ .../gee_scripts/fc_to_image_corrections.js | 184 +++ .../gee_scripts/modal_change_analysis_ccd.js | 668 +++++++++ .../gee_scripts/modal_change_analysis_ch.js | 1020 +++++++++++++ .../gee_scripts/modal_overall_change.js | 208 +++ 58 files changed, 6238 insertions(+), 394 deletions(-) create mode 100644 dpr/get_dpr_sectionwise_data.py create mode 100644 dpr/service/__init__.py create mode 100644 dpr/service/form_download_service.py create mode 100644 dpr/service/translation_service.py create mode 100644 dpr/templatetags/__init__.py create mode 100644 dpr/templatetags/custom_filters.py create mode 100644 templates/dpr/base.html create mode 100644 templates/dpr/section_a.html create mode 100644 templates/dpr/section_b.html create mode 100644 templates/dpr/section_c_crop.html create mode 100644 templates/dpr/section_c_livelihood.html create mode 100644 templates/dpr/section_c_mgnrega.html create mode 100644 templates/dpr/section_c_socio_economic.html create mode 100644 templates/dpr/section_d_base.html create mode 100644 templates/dpr/section_d_detail_water_structure.html create mode 100644 templates/dpr/section_d_detail_well_info.html create mode 100644 templates/dpr/section_d_mws_information.html create mode 100644 templates/dpr/section_d_water_structure_summary.html create mode 100644 templates/dpr/section_d_well_summary.html create mode 100644 templates/dpr/section_e_base.html create mode 100644 templates/dpr/section_e_irrigation_structure.html create mode 100644 templates/dpr/section_e_maintenance_work_by_asset_type.html create mode 100644 templates/dpr/section_e_recharge_structure.html create mode 100644 templates/dpr/section_e_rs_swb.html create mode 100644 templates/dpr/section_e_water_structure.html create mode 100644 templates/dpr/section_f.html create mode 100644 templates/dpr/section_g.html create mode 100644 utilities/scripts/tree_health/README.md create mode 100644 utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb create mode 100644 utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb create mode 100644 utilities/scripts/tree_health/gee_scripts/ccd_change.js create mode 100644 utilities/scripts/tree_health/gee_scripts/ch_change.js create mode 100644 utilities/scripts/tree_health/gee_scripts/fc_to_image.js create mode 100644 utilities/scripts/tree_health/gee_scripts/fc_to_image_corrections.js create mode 100644 utilities/scripts/tree_health/gee_scripts/modal_change_analysis_ccd.js create mode 100644 utilities/scripts/tree_health/gee_scripts/modal_change_analysis_ch.js create mode 100644 utilities/scripts/tree_health/gee_scripts/modal_overall_change.js diff --git a/computing/api.py b/computing/api.py index 7abeb36c..f30348f0 100644 --- a/computing/api.py +++ b/computing/api.py @@ -69,7 +69,12 @@ from .surface_water_bodies.merge_swb_ponds import merge_swb_ponds from utilities.auth_check_decorator import api_security_check from computing.layer_dependency.layer_generation_in_order import layer_generate_map -from .views import layer_status, get_layers_of_workspace, check_missing_layers +from .views import ( + layer_status, + get_layers_of_workspace, + missing_layer_for_all_workspace, + clear_layer_cache, +) from .misc.lcw_conflict import generate_lcw_conflict_data from .misc.agroecological_space import generate_agroecological_data from .misc.factory_csr import generate_factory_csr_data @@ -1877,14 +1882,20 @@ def sync_layer_remote(request): @schema(None) def missing_layers(request): try: - workspace = request.query_params.get("workspace").lower() - result = check_missing_layers(workspace) + result = missing_layer_for_all_workspace() return Response({"result": result}, status=status.HTTP_200_OK) except Exception as e: print("Exception in get_layers_for_workspace api :: ", e) return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) +@api_view(["GET"]) +@schema(None) +def refresh_layer_cache(request, workspace=None): + clear_layer_cache(workspace) + return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) + + @api_view(["POST"]) @schema(None) def generate_fabdem_layer(request): diff --git a/computing/urls.py b/computing/urls.py index a829c83c..d450e737 100644 --- a/computing/urls.py +++ b/computing/urls.py @@ -238,4 +238,8 @@ api.generate_canal_vector, name="generate-canal-vector", ), + path("refresh_cache/", api.refresh_layer_cache, name="refresh_cache"), + path( + "refresh_cache//", api.refresh_layer_cache, name="refresh_cache" + ), ] diff --git a/computing/utils.py b/computing/utils.py index b14d063c..b0d9a4a8 100644 --- a/computing/utils.py +++ b/computing/utils.py @@ -41,6 +41,8 @@ valid_gee_text, ) from utilities.geoserver_utils import Geoserver +from django.core.mail import EmailMessage, get_connection +import time logger = logging.getLogger(__name__) @@ -1030,3 +1032,90 @@ def update_layer_sync_status( except Exception as e: print(f"Error updating layer sync status: {e}") + + +# send missing layer to recipient email +def send_missing_layers_report(result: dict, recipients: list = None) -> bool: + if recipients is None: + recipients = getattr(settings, "MISSING_LAYER_RECIPIENTS", []) + + if isinstance(recipients, str): + recipients = [recipients] + + if not recipients: + logger.error("No recipients configured for missing layers report.") + return False + + summary = [] + total_missing = 0 + + for layer, data in result.items(): + count = len(data.get("missing_layers", [])) + total_missing += count + summary.append(f"{layer}: {count}") + body = ( + "Missing Layers Report\n\n" + f"Total Missing: {total_missing}\n\n" + + "\n".join(summary) + + "\n\nDetailed report attached." + ) + + attachment_content = json.dumps(result, indent=4) + max_retries = 3 + + for attempt in range(max_retries): + connection = None + try: + connection = get_connection(timeout=120) + connection.open() + email = EmailMessage( + subject="Missing Layers Report", + body=body, + from_email=settings.EMAIL_HOST_USER, + to=recipients, + connection=connection, + ) + email.attach( + "missing_layers.json", + attachment_content, + "application/json", + ) + email.send() + logger.info(f"Missing layers report sent to {recipients}") + logger.info( + f"Attachment size: " + f"{len(attachment_content.encode('utf-8')) / 1024:.2f} KB" + ) + return True + except Exception as e: + logger.exception(f"Attempt {attempt + 1}/{max_retries} failed: {e}") + if attempt < max_retries - 1: + wait_time = 5 * (attempt + 1) + logger.info(f"Retrying after {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error("All attempts to send email failed.") + return False + finally: + if connection: + try: + connection.close() + except Exception: + pass + + +def _is_cache_valid(cache: dict, workspace: str) -> bool: + if workspace not in cache: + return False + age = time.time() - cache[workspace]["cached_at"] + if age > 3600: + logger.info(f"Cache expired for {workspace} (age: {int(age)}s)") + return False + return True + + +def _set_cache(cache: dict, workspace: str, data: set): + cache[workspace] = { + "data": data, + "cached_at": time.time(), + } diff --git a/computing/views.py b/computing/views.py index 88dd6982..1d22bb02 100644 --- a/computing/views.py +++ b/computing/views.py @@ -7,10 +7,18 @@ from computing.models import * from utilities.geoserver_utils import Geoserver import json -import os from django.conf import settings from pathlib import Path from utilities.constants import GEOSERVER_BASE +from utilities.logger import setup_logger +from concurrent.futures import ThreadPoolExecutor, as_completed +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +from rest_framework.response import Response +from rest_framework import status +from .utils import send_missing_layers_report, _is_cache_valid, _set_cache + +logger = setup_logger(__name__) def get_url(geoserver_url, workspace, layer_name): @@ -201,94 +209,201 @@ def get_layers_of_workspace(self, workspace): return [] -def check_missing_layers(workspace): - missing_layers = [] - print(f"{workspace=}") - workspace_config = load_workspace_config() - workspace_types = get_workspace_types(workspace) - print(f"Found types: {workspace_types}") - if not workspace_types: - print(f"No config found for workspace: {workspace}") - return {"no config found": []} - layer_config = get_layer_config_by_type( - workspace_config, workspace, workspace_types - ) - # fetch raster layers once - available_raster_layers = valid_raster_layers_for_workspace(workspace) - count = 0 - active_tehsils = TehsilSOI.objects.filter(active_status=True) - for tehsil in active_tehsils: - state = tehsil.district.state.state_name - district = tehsil.district.district_name - tehsil = tehsil.tehsil_name - district_name = valid_gee_text(district.lower()) - tehsil_name = valid_gee_text(tehsil.lower()) - for layer_type, config in layer_config.items(): - prefix = config.get("prefix") - suffix = config.get("suffix") - layer_name_parts = [ - prefix, - district_name, - tehsil_name, - suffix, - ] - layer_name = "_".join(part for part in layer_name_parts if part) - # raster check - if layer_type == "raster": - count += 1 - if layer_name not in available_raster_layers: - missing_layers.append(state + " " + layer_name) - - # vector check - elif layer_type == "vector": - count += 1 - is_valid = is_valid_vector_layer(workspace, layer_name) - - if not is_valid: - missing_layers.append(state + " " + layer_name) - - return {"missing_layers": missing_layers, "layer_count": count} - - -def valid_raster_layers_for_workspace(workspace): - """ +_raster_cache = {} +_vector_cache = {} - Args: - workspace: - Returns: - all valid (have data)layers for particular workspace - """ +def valid_raster_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_raster_cache, workspace): + logger.info(f"Cache hit for raster: {workspace}") + return _raster_cache[workspace]["data"] + + session = get_session_with_retry() capabilities_url = ( f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" ) - response = requests.get(capabilities_url) - if response.status_code != 200: - print(f"Failed to retrieve capabilities from {workspace}.") - return [] + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching raster capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed raster capabilities for {workspace}: {e} — not caching") + return set() + root = ET.fromstring(response.content) ns = {"wms": root.tag.split("}")[0].strip("{")} layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) - available_layers = [layer.text for layer in layers] - return available_layers + result = {layer.text for layer in layers} + _set_cache(_raster_cache, workspace, result) # cache with timestamp + logger.info(f"Cached raster layers for {workspace}: {len(result)} layers") + return result -def is_valid_vector_layer(workspace, layer_name): - """ - Args: - workspace: - layer_name: +def valid_vector_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_vector_cache, workspace): + logger.info(f"Cache hit for vector: {workspace}") + return _vector_cache[workspace]["data"] - Returns: - True if layer have data else False + session = get_session_with_retry() + capabilities_url = ( + f"{GEOSERVER_BASE}{workspace}/wfs" + f"?service=WFS&version=2.0.0&request=GetCapabilities" + ) + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching vector capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed vector capabilities for {workspace}: {e} — not caching") + return set() + + root = ET.fromstring(response.content) + ns = {"wfs": "http://www.opengis.net/wfs/2.0"} + names = root.findall(".//wfs:FeatureType/wfs:Name", namespaces=ns) + result = {name.text.split(":")[-1] for name in names} + + _set_cache(_vector_cache, workspace, result) # cache with timestamp + logger.info(f"Cached vector layers for {workspace}: {len(result)} layers") + return result + + +def is_valid_vector_layer(workspace: str, layer_name: str) -> bool: + """Used in parallel fallback only.""" + session = get_session_with_retry() + try: + layer_url = get_url(GEOSERVER_URL, workspace, layer_name) + res = session.get(layer_url, timeout=(5, 10)) # shorter timeout per layer + if res.status_code == 200: + root = ET.fromstring(res.text) + return int(root.attrib.get("numberOfFeatures", 0)) > 0 + except requests.exceptions.Timeout: + logger.warning(f"Timeout checking vector layer: {layer_name}") + except Exception as e: + logger.warning(f"Vector check failed for {layer_name}: {e}") + return False + + +def bulk_check_vector_layers( + workspace: str, + layer_names: list[str], + max_workers: int = 20, +) -> dict[str, bool]: """ - layer_url = get_url(GEOSERVER_URL, workspace, layer_name) - res_layer_url = requests.get(layer_url) - if res_layer_url.status_code == 200: - root = ET.fromstring(res_layer_url.text) - total_features = int(root.attrib.get("numberOfFeatures", 0)) - return True if total_features > 0 else False + Checks multiple vector layers concurrently using a thread pool. + Returns {layer_name: is_valid} mapping. + """ + results = {} + with ThreadPoolExecutor(max_workers=max_workers) as executor: + future_to_layer = { + executor.submit(is_valid_vector_layer, workspace, name): name + for name in layer_names + } + for future in as_completed(future_to_layer): + layer_name = future_to_layer[future] + try: + results[layer_name] = future.result() + except Exception as e: + logger.warning(f"Failed check for {layer_name}: {e}") + results[layer_name] = False + return results + + +@app.task(bind=True) +def missing_layer_for_all_workspace(self): + workspaces = ( + Dataset.objects.filter(workspace__isnull=False) + .values_list("workspace", flat=True) + .distinct() + ) + workspaces = [w.strip() for w in workspaces if w and w.strip()] + result = {} + for workspace in workspaces: + result[workspace] = check_missing_layers(workspace) + send_missing_layers_report(result) + return result + + +def check_missing_layers(workspace: str) -> dict: + logger.info(f"{workspace=}") + workspace_config = load_workspace_config() + workspace_types = get_workspace_types(workspace) + logger.info(f"Found types: {workspace_types}") + + if not workspace_types: + logger.critical(f"No config found for workspace: {workspace}") + return {"no config found": []} + + layer_config = get_layer_config_by_type( + workspace_config, workspace, workspace_types + ) + + # ── Fetch all available layers ONCE (bulk, cached) ────────────────────── + available_raster_layers = valid_raster_layers_for_workspace(workspace) + available_vector_layers = valid_vector_layers_for_workspace(workspace) # bulk WFS` + use_bulk_vector = bool(available_vector_layers) # fallback if WFS unavailable + + # ── Pre-build all (tehsil, layer_type, layer_name, state) combos ──────── + active_tehsils = TehsilSOI.objects.select_related( + "district__state" # eliminates N+1 DB queries + ).filter(active_status=True) + + tasks = [] # (state, layer_type, layer_name) + for tehsil_obj in active_tehsils: + state = tehsil_obj.district.state.state_name + district_name = valid_gee_text(tehsil_obj.district.district_name.lower()) + tehsil_name = valid_gee_text(tehsil_obj.tehsil_name.lower()) + + for layer_type, configs in layer_config.items(): + for config in configs: + prefix = config.get("prefix") + suffix = config.get("suffix") + layer_name = "_".join( + p for p in [prefix, district_name, tehsil_name, suffix] if p + ) + tasks.append( + (state, district_name, tehsil_name, layer_type, layer_name) + ) + + # ── Check raster layers (pure set lookup, no HTTP) ─────────────────────── + missing_layers = [] + vector_tasks = [] # collect for batch/parallel processing + + for state, district_name, tehsil_name, layer_type, layer_name in tasks: + if layer_type == "raster": + if layer_name not in available_raster_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + elif layer_type == "vector": + if use_bulk_vector: + # O(1) set lookup — no HTTP call needed + if layer_name not in available_vector_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + else: + vector_tasks.append( + (state, district_name, tehsil_name, layer_name) + ) # queue for parallel check + + # ── Parallel fallback for vector layers (if WFS bulk fetch failed) ─────── + if vector_tasks: + layer_names = [ln for _, _, _, ln in vector_tasks] + info_by_name = {ln: (st, dn, tn) for st, dn, tn, ln in vector_tasks} + validity = bulk_check_vector_layers(workspace, layer_names) + + for layer_name, is_valid in validity.items(): + if not is_valid: + state, district_name, tehsil_name = info_by_name[layer_name] + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + return {"missing_layers": missing_layers} def get_workspace_types(workspace_name): @@ -305,7 +420,7 @@ def get_workspace_types(workspace_name): def get_layer_config_by_type(workspace_config, workspace_name, layer_types): """ - Return prefix and suffix for each layer type. + Return list of prefix/suffix configs for each layer type. Args: workspace_config (dict): config JSON @@ -313,7 +428,7 @@ def get_layer_config_by_type(workspace_config, workspace_name, layer_types): layer_types (list): ['raster', 'vector'] Returns: - dict + dict: {'raster': [{'prefix': ..., 'suffix': ...}, ...], 'vector': [...]} """ result = {} @@ -321,9 +436,44 @@ def get_layer_config_by_type(workspace_config, workspace_name, layer_types): if config.get("name") == workspace_name and config.get("type") in layer_types: layer_type = config["type"] - result[layer_type] = { - "prefix": config.get("prefix"), - "suffix": config.get("suffix"), - } + if layer_type not in result: + result[layer_type] = [] + + result[layer_type].append( + { + "prefix": config.get("prefix"), + "suffix": config.get("suffix"), + } + ) return result + + +def get_session_with_retry(): + """Creates a requests session with retry and timeout handling.""" + session = requests.Session() + retry = Retry( + total=3, + backoff_factor=1, # waits 1s, 2s, 4s between retries + status_forcelist=[500, 502, 503, 504], + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + + +def clear_layer_cache(workspace: str = None): + if workspace: + _raster_cache.pop(workspace, None) + _vector_cache.pop(workspace, None) + logger.info(f"Cleared cache for {workspace}") + else: + _raster_cache.clear() + _vector_cache.clear() + logger.info("Cleared all layer caches") + + +def refresh_layer_cache(request, workspace=None): + clear_layer_cache(workspace) + return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) diff --git a/dpr/api.py b/dpr/api.py index 87911e34..935d01de 100644 --- a/dpr/api.py +++ b/dpr/api.py @@ -4,6 +4,7 @@ from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import render +from django.templatetags.i18n import language from django.urls import reverse from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema @@ -138,6 +139,7 @@ def generate_dpr(request): try: plan_id = request.data.get("plan_id") email_id = request.data.get("email_id") + language = request.data.get("language") regenerate = request.data.get("regenerate", False) logger.info( @@ -161,7 +163,9 @@ def generate_dpr(request): {"error": "Plan not found"}, status=status.HTTP_404_NOT_FOUND ) - generate_dpr_task.apply_async(args=[plan_id, email_id, regenerate], queue="dpr") + generate_dpr_task.apply_async( + args=[plan_id, email_id, language, regenerate], queue="dpr" + ) return Response( { @@ -572,13 +576,15 @@ def generate_village_report(request): for key, value in params.items(): result[key] = value - + 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 + "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 } return render(request, "village-report.html", context) @@ -587,6 +593,7 @@ def generate_village_report(request): logger.exception("Exception in generate_village_report api :: ", e) return render(request, "error-page.html", {}) + # --------------------------------------------------------------------------- # DPR Data API # --------------------------------------------------------------------------- diff --git a/dpr/gen_dpr.py b/dpr/gen_dpr.py index 917ba55d..9aebfce0 100644 --- a/dpr/gen_dpr.py +++ b/dpr/gen_dpr.py @@ -190,9 +190,9 @@ def initialize_document(): doc = Document() heading = doc.add_heading("Detailed Project Report", 0) heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER - doc.add_paragraph( - date.today().strftime("%B %d, %Y") - ).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + doc.add_paragraph(date.today().strftime("%B %d, %Y")).alignment = ( + WD_PARAGRAPH_ALIGNMENT.CENTER + ) return doc diff --git a/dpr/get_dpr_sectionwise_data.py b/dpr/get_dpr_sectionwise_data.py new file mode 100644 index 00000000..d47b2012 --- /dev/null +++ b/dpr/get_dpr_sectionwise_data.py @@ -0,0 +1,1291 @@ +import geopandas as gpd +from .gen_dpr import ( + get_settlement_coordinates_for_plan, + get_mws_uid_for_settlement_gdf, + get_data_for_settlement, + get_crops_data, + get_livestock_data, + get_all_wells_with_mws, + get_all_waterbodies_with_mws, + sort_key, +) +from .utils import to_utf8 +from collections import defaultdict +from dpr.utils import ensure_str, get_waterbody_repair_activities +from .mapping import populate_maintenance_from_waterbody +from .services import ( + get_nrm_works_data, +) +from .service.form_download_service import ( + load_form_labels, + translate_choice, + translate_multiple_choices, +) +from dpr.models import ( + GW_maintenance, + ODK_livelihood, + ODK_agrohorticulture, + Agri_maintenance, + SWB_maintenance, + SWB_RS_maintenance, + ODK_agri, + ODK_groundwater, +) + + +def get_section_b_data(plan, total_settlements, mws_fortnight): + + mws_gdf = gpd.GeoDataFrame.from_features(mws_fortnight["features"]) + + settlement_mws_ids = [] + settlement_coordinates = get_settlement_coordinates_for_plan(plan.id) + + for settlement_name, latitude, longitude in settlement_coordinates: + mws_uid = get_mws_uid_for_settlement_gdf(mws_gdf, latitude, longitude) + + if mws_uid: + settlement_mws_ids.append( + { + "settlement": settlement_name, + "mws_id": mws_uid, + } + ) + + centroid = None + + if settlement_mws_ids: + intersecting_mws = mws_gdf[ + mws_gdf["uid"].isin([item["mws_id"] for item in settlement_mws_ids]) + ] + + if not intersecting_mws.empty: + centroid = intersecting_mws.geometry.unary_union.centroid + + return ( + { + "village_name": to_utf8(plan.village_name), + "gram_panchayat": to_utf8(plan.gram_panchayat), + "tehsil": to_utf8(plan.tehsil_soi.tehsil_name), + "district": to_utf8(plan.district_soi.district_name), + "state": to_utf8(plan.state_soi.state_name), + "total_settlements": total_settlements, + "settlement_mws_pairs": settlement_mws_ids, + "village_coordinates": ( + f"{centroid.y:.8f}, {centroid.x:.8f}" if centroid else "Not available" + ), + }, + settlement_mws_ids, + mws_gdf, + ) + + +def get_section_c_data(plan, language): + settlement_data = get_data_for_settlement(plan.id) + labels = load_form_labels("Add_Settlements_form _V1.0.1") + crop_data = get_crops_data(plan.id) + crop_labels = load_form_labels("crop_form_V1.0.0") + for settlement in settlement_data: + + settlement.largest_caste_label = translate_choice( + labels, + "select_one_type", + settlement.largest_caste, + language, + ) + + if settlement.largest_caste.lower() == "single caste group": + + settlement.smallest_caste_label = translate_choice( + labels, + "caste_group_single", + settlement.smallest_caste, + language, + ) + + elif settlement.largest_caste.lower() == "mixed caste group": + + settlement.settlement_status_label = translate_multiple_choices( + labels, + "caste_group_single", + settlement.settlement_status, + language, + ) + settlement.nrega_past_work_label = translate_multiple_choices( + labels, + "work_demands", + clean_odk_value(settlement.nrega_past_work), + language, + ) + + settlement.nrega_demand_label = translate_choice( + labels, + "select_one_demands", + clean_odk_value(settlement.nrega_demand), + language, + ) + + settlement.nrega_issues_label = translate_multiple_choices( + labels, + "select_multiple_issues", + settlement.nrega_issues, + language, + ) + for crop in crop_data: + crop["irrigation_source"] = translate_multiple_choices( + crop_labels, + "select_multiple_widgets", + clean_odk_value(crop["irrigation_source"]), + language, + ) + + crop["land_classification"] = translate_choice( + crop_labels, + "select_one_classified", + clean_odk_value(crop["land_classification"]), + language, + ) + + crop["kharif_crops"] = translate_multiple_choices( + crop_labels, + "select_multiple_cropping_kharif", + clean_odk_value(crop["kharif_crops"]), + language, + ) + + crop["rabi_crops"] = translate_multiple_choices( + crop_labels, + "select_multiple_cropping_Rabi", + clean_odk_value(crop["rabi_crops"]), + language, + ) + + crop["zaid_crops"] = translate_multiple_choices( + crop_labels, + "select_multiple_cropping_Zaid", + clean_odk_value(crop["zaid_crops"]), + language, + ) + + crop["cropping_intensity"] = translate_choice( + crop_labels, + "select_one_productivity", + clean_odk_value(crop["cropping_intensity"]), + language, + ) + return { + "socio_eco": settlement_data, + "mgnrega": settlement_data, + "crop_info": crop_data, + "livestock_info": get_livestock_data(plan.id), + } + + +def get_section_d_data(plan, settlement_mws_ids, mws_gdf, language): + unique_mws_ids = sorted({item["mws_id"] for item in settlement_mws_ids}) + + all_wells_with_mws = get_all_wells_with_mws( + plan, + unique_mws_ids, + mws_gdf, + ) + + all_waterbodies_with_mws = get_all_waterbodies_with_mws( + plan, + unique_mws_ids, + mws_gdf, + ) + + well_labels = load_form_labels("Add_well_form_V1.0.1") + water_body_labels_repair = load_form_labels( + "NRM_form_NRM_form_Waterbody_Screen_V1.0.0" + ) + water_body_labels = load_form_labels("Add_Waterbodies_Form_V1.0.3") + return { + "mws": get_mws_table_data(unique_mws_ids, mws_gdf), + "well_summary": get_well_summary_data(all_wells_with_mws), + "wells": get_detailed_well_data(all_wells_with_mws, well_labels, language), + "water_summary": get_waterbody_summary_data( + all_waterbodies_with_mws, water_body_labels, language + ), + "water_structures": get_detailed_waterbody_data( + all_waterbodies_with_mws, + water_body_labels, + language, + water_body_labels_repair, + ), + } + + +def get_mws_table_data(unique_mws_ids, mws_gdf): + + data = [] + + for mws_id in unique_mws_ids: + + matching_feature = mws_gdf[mws_gdf["uid"] == mws_id] + + centroid = None + + if not matching_feature.empty: + c = matching_feature.geometry.centroid.iloc[0] + centroid = f"{c.y:.8f}, {c.x:.8f}" + + data.append( + { + "mws_id": mws_id, + "centroid": centroid, + } + ) + + return data + + +def get_well_summary_data(all_wells_with_mws): + + wells_count = defaultdict(int) + households_count = defaultdict(int) + + for well, _ in all_wells_with_mws: + + wells_count[well.beneficiary_settlement] += 1 + + households_count[well.beneficiary_settlement] += int( + well.households_benefitted or 0 + ) + + rows = [] + + for settlement in sorted(wells_count.keys(), key=sort_key): + + rows.append( + { + "settlement": settlement, + "num_wells": wells_count[settlement], + "households": households_count[settlement], + } + ) + + return rows + + +def get_detailed_well_data(all_wells_with_mws, labels, language): + + rows = [] + + all_wells_with_mws_sorted = sorted( + all_wells_with_mws, + key=lambda x: ( + not x[0].beneficiary_settlement or x[0].beneficiary_settlement == "NA", + (x[0].beneficiary_settlement or "").lower(), + ), + ) + + for well, mws_id in all_wells_with_mws_sorted: + + well_usage = None + + if well.data_well and "Well_usage" in well.data_well: + + usage = well.data_well["Well_usage"] + + used = ensure_str(usage.get("select_one_well_used")) + + other = usage.get("select_one_well_used_other") + + if used and used.lower() == "other" and other: + well_usage = f"Other: {other}" + + elif used: + well_usage = translate_choice( + labels, + "select_one_well_used", + used, + language, + ) + + repair_activities = None + + if well.data_well and "Well_usage" in well.data_well: + + usage = well.data_well["Well_usage"] + + repairs = ensure_str(usage.get("repairs_type")) + repairs_other = usage.get("repairs_type_other") + + if repairs and repairs.lower() == "other" and repairs_other: + repair_activities = repairs_other + + elif repairs: + repair_activities = translate_multiple_choices( + labels, + "repairs_type", + repairs, + language, + ) + rows.append( + { + "mws_id": mws_id, + "settlement": well.beneficiary_settlement, + "well_type": translate_choice( + labels, + "select_one_well_type", + well.data_well.get("select_one_well_type"), + language, + ), + "owner": translate_choice( + labels, + "select_one_owns", + well.owner, + language, + ), + "beneficiary_name": well.data_well.get("Beneficiary_name") or None, + "father_name": well.data_well.get("ben_father") or None, + "water_availability": translate_choice( + labels, + "select_one_year", + well.data_well.get("select_one_year"), + language, + ), + "households_benefitted": well.households_benefitted, + "caste_uses": translate_multiple_choices( + labels, + "select_multiple_caste_use", + well.caste_uses, + language, + ), + "well_usage": well_usage, + "need_maintenance": translate_choice( + labels, + "is_maintenance_required", + well.need_maintenance, + language, + ), + "repair_activities": repair_activities, + "latitude": well.latitude, + "longitude": well.longitude, + } + ) + + return rows + + +def get_waterbody_summary_data(all_waterbodies_with_mws, water_body_labels, language): + + waterbody_count = defaultdict(int) + + households_count = defaultdict(int) + + for waterbody, _ in all_waterbodies_with_mws: + + structure_type = waterbody.water_structure_type + + key = ( + waterbody.beneficiary_settlement, + structure_type, + ) + + waterbody_count[key] += 1 + + households_count[key] += int(waterbody.household_benefitted or 0) + + rows = [] + + for ( + settlement, + structure_type, + ) in sorted( + waterbody_count.keys(), + key=lambda x: sort_key(x[0]), + ): + + rows.append( + { + "settlement": settlement, + "structure_type": translate_choice( + water_body_labels, + "select_one_water_structure", + structure_type, + language, + ), + "count": waterbody_count[(settlement, structure_type)], + "households": households_count[(settlement, structure_type)], + } + ) + + return rows + + +def get_detailed_waterbody_data( + all_waterbodies_with_mws, waterbody_labels, language, water_body_labels_repair +): + + rows = [] + + for ( + waterbody, + mws_id, + ) in sorted( + all_waterbodies_with_mws, + key=lambda x: sort_key(x[0].beneficiary_settlement), + ): + + who_manages = waterbody.who_manages or None + + if who_manages: + + if who_manages.lower() == "other": + who_manages = "Other: " + (waterbody.specify_other_manager or "") + + else: + who_manages = translate_choice( + waterbody_labels, + "select_one_manages", + who_manages.lower(), + language, + ) + + structure_type = waterbody.water_structure_type or None + structure_type_eng = structure_type + + if structure_type: + + if structure_type.lower() == "other": + structure_type = "Other: " + (waterbody.water_structure_other or "") + structure_type_eng = structure_type + + else: + structure_type = translate_choice( + waterbody_labels, + "select_one_water_structure", + structure_type, + language, + ) + repair_activities = get_waterbody_repair_activities( + waterbody.data_waterbody, + structure_type_eng, + ) + repair_label_key = REPAIR_LABEL_MAPPING.get( + str(waterbody.water_structure_type).strip().lower() + ) + wb_owner = waterbody.owner + wb_owner = classify_demand_type(wb_owner.lower()) + rows.append( + { + "mws_id": mws_id, + "settlement": waterbody.beneficiary_settlement, + "owner": translate_choice( + water_body_labels_repair, + "demand_type", + wb_owner, + language, + ), + "beneficiary_name": waterbody.data_waterbody.get("Beneficiary_name") + or None, + "father_name": waterbody.data_waterbody.get("ben_father") or None, + "who_manages": who_manages, + "caste_uses": translate_multiple_choices( + waterbody_labels, + "select_multiple_caste_use", + waterbody.caste_who_uses, + language, + ), + "households_benefitted": waterbody.household_benefitted, + "structure_type": structure_type, + "usage": translate_multiple_choices( + waterbody_labels, + "select_multiple_uses_structure", + waterbody.data_waterbody.get("select_multiple_uses_structure"), + language, + ), + "need_maintenance": translate_choice( + waterbody_labels, + "select_one_maintenance", + waterbody.need_maintenance, + language, + ), + "repair_activities": translate_multiple_choices( + water_body_labels_repair, + repair_label_key, + repair_activities, + language, + ), + "latitude": waterbody.latitude, + "longitude": waterbody.longitude, + } + ) + + return rows + + +def get_section_e_data(plan, language): + populate_maintenance_from_waterbody(plan) + gw_data = get_maintenance_data(plan.id, "gw") + agri_data = get_maintenance_data(plan.id, "agri") + swb_data = get_maintenance_data(plan.id, "swb") + swb_rs_data = get_maintenance_data(plan.id, "swb_rs") + gw_label = load_form_labels( + "Propose_Maintenance_on_Existing_Water_Recharge_Structures_V1.1.1" + ) + agri_label = load_form_labels( + "Propose_Maintenance_on_Existing_Irrigation_Structures_V1.1.1" + ) + swb_rs_label = load_form_labels("PM_Remote_Sensed_Surface_Water_structure_V1.0.0") + swb_label = load_form_labels("NRM_form_NRM_form_Waterbody_Screen_V1.0.0") + + for row in gw_data: + + row["demand_type"] = translate_choice( + gw_label, + "demand_type", + row["demand_type"], + language, + ) + + row["structure_type"] = translate_choice( + gw_label, + "select_one_recharge_structure", + row["structure_type"], + language, + ) + for row in agri_data: + demand_type = classify_demand_type(row["demand_type"].lower()) + row["demand_type"] = translate_choice( + agri_label, + "demand_type".lower().replace(" ", "_"), + demand_type, + language, + ) + row["structure_type"] = translate_choice( + agri_label, + "select_one_irrigation_structure", + row["structure_type"], + language, + ) + for row in swb_data: + row["demand_type"] = translate_choice( + swb_label, + "demand_type", + row["demand_type"], + language, + ) + row["structure_type"] = translate_choice( + swb_rs_label, + "TYPE_OF_WORK", + row["structure_type"], + language, + ) + for row in swb_rs_data: + row["demand_type"] = translate_choice( + swb_rs_label, + "demand_type", + row["demand_type"], + language, + ) + + original_structure = row["structure_type"] + original_structure = str(original_structure).strip().lower() + repair_key = RS_WATER_STRUCTIRE_REVERSE_MAPPING.get(original_structure) + if repair_key and row.get("repair_activities"): + row["repair_activities"] = translate_choice( + swb_rs_label, + repair_key, + clean_odk_value(row["repair_activities"]), + language, + ) + row["structure_type"] = translate_choice( + swb_rs_label, + "TYPE_OF_WORK", + row["structure_type"], + language, + ) + return { + "gw": gw_data, + "agri": agri_data, + "swb": swb_data, + "swb_rs": swb_rs_data, + } + + +def get_section_f_data(plan, language): + gw_labels = load_form_labels("NRM_form_propose_new_recharge_structure_V1.0.0") + agri_labels = load_form_labels("NRM_form_Agri_Screen_V1.0.0") + works = get_nrm_works_data(plan.id) + for row in works: + if row["work_category"] == "Recharge Structure": + row["demand_type"] = translate_choice( + gw_labels, + "demand_type", + row["demand_type"], + language, + ) + row["work_demand"] = translate_choice( + gw_labels, + "TYPE_OF_WORK_ID", + row["work_demand"], + language, + ) + row["gender"] = translate_choice( + gw_labels, + "select_gender", + row["gender"], + language, + ) + row["work_category"] = translate_work_category( + row["work_category"], + language, + ) + elif row["work_category"] == "Irrigation Work": + row["demand_type"] = translate_choice( + agri_labels, + "demand_type_irrigation", + row["demand_type"], + language, + ) + row["work_demand"] = translate_choice( + agri_labels, + "TYPE_OF_WORK_ID", + row["work_demand"], + language, + ) + row["gender"] = translate_choice( + agri_labels, + "gender", + row["gender"], + language, + ) + row["work_category"] = translate_work_category( + row["work_category"], + language, + ) + return {"works": works} + + +def get_section_g_data(plan, language): + all_livelihood = get_livelihood_data(plan.id) + agro_labels = load_form_labels("Agrohorticulture") + livelihood_labels = load_form_labels("NRM Livelihood Form") + + livestock_fisheries = [ + r for r in all_livelihood if r["livelihood_work"] in ("Livestock", "Fisheries") + ] + for row in livestock_fisheries: + livelihood_work = row.get("livelihood_work") + + if livelihood_work == "Livestock": + row["demand_type"] = translate_choice( + livelihood_labels, + "livestock_demand", + row.get("demand_type"), + language, + ) + + row["work_demand"] = translate_choice( + livelihood_labels, + "demands_promoting_livestock", + row.get("work_demand"), + language, + ) + row["gender"] = translate_choice( + livelihood_labels, + "gender_livestock", + row.get("gender"), + language, + ) + + elif livelihood_work == "Fisheries": + row["demand_type"] = translate_choice( + livelihood_labels, + "demand_type_fisheries", + row.get("demand_type"), + language, + ) + + row["work_demand"] = translate_choice( + livelihood_labels, + "select_one_promoting_fisheries", + row.get("work_demand"), + language, + ) + row["gender"] = translate_choice( + livelihood_labels, + "gender_fisheries", + row.get("gender"), + language, + ) + + plantations_etc = [ + r + for r in all_livelihood + if r["livelihood_work"] not in ("Livestock", "Fisheries") + ] + for row in plantations_etc: + livelihood_work = row.get("livelihood_work") + + if livelihood_work == "Plantations": + + row["demand_type"] = translate_choice( + agro_labels, + "demand_type_plantations", + row.get("demand_type"), + language, + ) + row["gender"] = translate_choice( + agro_labels, + "gender", + row.get("gender"), + language, + ) + + species = row.get("work_demand") + + if species: + translated_species = [] + + for item in species.split(): + translated_species.append( + translate_choice( + agro_labels, + "select_multiple_species", + item.strip().lower(), + language, + ) + ) + + row["work_demand"] = ", ".join(translated_species) + + elif livelihood_work == "Kitchen Garden": + + row["demand_type"] = translate_choice( + livelihood_labels, + "demand_type_kitchen_garden", + row.get("demand_type"), + language, + ) + row["gender"] = translate_choice( + livelihood_labels, + "gender_kitchen_gardens", + row.get("gender"), + language, + ) + for row in livestock_fisheries: + row["livelihood_work"] = translate_livelihood_work( + row["livelihood_work"], + language, + ) + + for row in plantations_etc: + row["livelihood_work"] = translate_livelihood_work( + row["livelihood_work"], + language, + ) + return { + "livestock_fisheries": livestock_fisheries, + "plantations": plantations_etc, + } + + +def clean_odk_value(value): + if value is None: + return None + + value = str(value).strip() + + if value.upper() == "NA": + return None + + return value + + +REPAIR_LABEL_MAPPING = { + "community pond": "select_one_community_pond", + "large water body": "select_one_repair_large_water_body", + "farm pond": "select_one_farm_pond", + "canal": "select_one_repair_canal", + "check dam": "select_one_check_dam", + "percolation tank": "select_one_percolation_tank", + "rock fill dam": "select_one_rock_fill_dam", + "loose boulder structure": "select_one_loose_boulder_structure", + "5% model structure": "select_one_model5_structure", + "30-40 model structure": "select_one_model30_40_structure", +} +REPAIR_ACTIVITY_MAPPING = { + "check dam": "select_one_check_dam", + "percolation tank": "select_one_percolation_tank", + "earthen gully plug": "select_one_earthen_gully_plug", + "drainage/soakage channels": "select_one_drainage_soakage_channels", + "recharge pits": "select_one_recharge_pits", + "sokage pits": "select_one_sokage_pits", + "trench cum bund network": "select_one_trench_cum_bund_network", + "continuous contour trenches (cct)": "select_one_continuous_contour_trenches", + "staggered contour trenches(sct)": "select_one_staggered_contour_trenches", + "water absorption trenches(wat)": "select_one_water_absorption_trenches", + "loose boulder structure": "select_one_loose_boulder_structure", + "rock fill dam": "select_one_rock_fill_dam", + "stone bunding": "select_one_stone_bunding", + "diversion drains": "select_one_diversion_drains", + "bunding:contour bunds/ graded bunds": "select_one_bunding", + "5% model structure": "select_one_model5_structure", + "30-40 model structure": "select_one_model30_40_structure", +} + + +def get_maintenance_data(plan_id, maintenance_type): + pid = str(plan_id) + result = [] + + if maintenance_type == "gw": + for m in GW_maintenance.objects.filter(plan_id=pid).exclude(is_deleted=True): + d = m.data_gw_maintenance or {} + structure_type = d.get("select_one_recharge_structure") or None + repair = _resolve_repair_activity( + d, + structure_type, + RECHARGE_STRUCTURE_MAPPING, + ) + result.append( + { + "id": m.gw_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "gender": d.get("select_gender"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + elif maintenance_type == "agri": + for m in Agri_maintenance.objects.filter(plan_id=pid).exclude(is_deleted=True): + d = m.data_agri_maintenance or {} + structure_type = ( + d.get("select_one_water_structure") + or d.get("select_one_irrigation_structure") + or "NA" + ) + repair = _resolve_repair_activity( + d, structure_type, IRRIGATION_STRUCTURE_REVERSE_MAPPING + ) + result.append( + { + "id": m.agri_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + elif maintenance_type == "swb": + for m in SWB_maintenance.objects.filter(plan_id=pid).exclude(is_deleted=True): + d = m.data_swb_maintenance or {} + structure_type = ( + d.get("TYPE_OF_WORK") or d.get("select_one_water_structure") or "NA" + ) + repair = _resolve_repair_activity( + d, structure_type, WATER_STRUCTURE_REVERSE_MAPPING + ) + result.append( + { + "id": m.swb_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "gender": d.get("select_gender"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + elif maintenance_type == "swb_rs": + for m in SWB_RS_maintenance.objects.filter(plan_id=pid).exclude( + is_deleted=True + ): + d = m.data_swb_rs_maintenance or {} + structure_type = d.get("TYPE_OF_WORK") or "NA" + repair = _resolve_repair_activity( + d, structure_type, RS_WATER_STRUCTIRE_REVERSE_MAPPING + ) + + result.append( + { + "id": m.swb_rs_maintenance_id, + "demand_type": d.get("demand_type"), + "beneficiary_settlement": d.get("beneficiary_settlement"), + "beneficiary_name": d.get("Beneficiary_Name"), + "gender": d.get("select_gender"), + "beneficiary_father_name": d.get("ben_father"), + "structure_type": structure_type, + "repair_activities": repair, + "latitude": m.latitude, + "longitude": m.longitude, + } + ) + + return result + + +def _resolve_repair_activity( + data, structure_type, mapping, fallback_key="select_one_activities" +): + repair_activities = None + + structure_key = str(structure_type).strip().lower() + + repair_key = mapping.get(structure_key) + + if repair_key: + repair_activities = data.get(repair_key) + + if repair_activities == "other": + repair_activities = data.get(f"{repair_key}_other") + + if not repair_activities: + repair_activities = data.get(fallback_key) + + return repair_activities + + +RECHARGE_STRUCTURE_MAPPING = { + "Check dam": "select_one_check_dam", + "Percolation tank": "select_one_percolation_tank", + "Earthen gully plug": "select_one_earthen_gully_plug", + "Drainage/soakage channels": "select_one_drainage_soakage_channels", + "Recharge pits": "select_one_recharge_pits", + "Sokage pits": "select_one_sokage_pits", + "Trench cum bund network": "select_one_trench_cum_bund_network", + "Continuous contour trenches (CCT)": "select_one_continuous_contour_trenches", + "Staggered Contour trenches(SCT)": "select_one_staggered_contour_trenches", + "Water absorption trenches(WAT)": "select_one_water_absorption_trenches", + "Loose boulder structure": "select_one_loose_boulder_structure", + "Rock fill dam": "select_one_rock_fill_dam", + "Stone bunding": "select_one_stone_bunding", + "Diversion drains": "select_one_diversion_drains", + "Bunding:Contour bunds/ graded bunds": "select_one_bunding", + "5% model structure": "select_one_model5_structure", + "30-40 model structure": "select_one_model30_40_structure", +} + + +def get_livelihood_data(plan_id): + pid = str(plan_id) + result = [] + + for record in ( + ODK_livelihood.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + dl = record.data_livelihood or {} + + livestock_group = dl.get("Livestock") or {} + fisheries_group = dl.get("fisheries") or {} + plantation_group = dl.get("plantations") or {} + kitchen_garden_group = dl.get("kitchen_gardens") or {} + + is_livestock = ( + ensure_str(livestock_group.get("is_demand_livestock", "")).lower() == "yes" + or ensure_str(dl.get("select_one_demand_promoting_livestock", "")).lower() + == "yes" + ) + if is_livestock: + demands = ensure_str(livestock_group.get("demands_promoting_livestock")) + if demands and demands.lower() == "other": + demands = livestock_group.get("demands_promoting_livestock_other") + if not demands: + demands = ensure_str(dl.get("select_one_promoting_livestock")) + if demands and demands.lower() == "other": + demands = dl.get("select_one_promoting_livestock_other") + result.append( + { + "livelihood_work": "Livestock", + "demand_type": livestock_group.get("livestock_demand"), + "work_demand": demands, + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or livestock_group.get("ben_livestock"), + "gender": livestock_group.get("gender_livestock"), + "beneficiary_father_name": livestock_group.get( + "ben_father_livestock" + ), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + is_fisheries = ( + ensure_str(fisheries_group.get("is_demand_fisheris", "")).lower() == "yes" + or ensure_str(dl.get("select_one_demand_promoting_fisheries", "")).lower() + == "yes" + ) + if is_fisheries: + demands = ensure_str(fisheries_group.get("select_one_promoting_fisheries")) + if demands and demands.lower() == "other": + demands = fisheries_group.get("select_one_promoting_fisheries_other") + if not demands: + demands = ensure_str(dl.get("select_one_promoting_fisheries")) + if demands and demands.lower() == "other": + demands = dl.get("select_one_promoting_fisheries_other") + result.append( + { + "livelihood_work": "Fisheries", + "demand_type": fisheries_group.get("demand_type_fisheries"), + "work_demand": demands, + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or fisheries_group.get("ben_fisheries"), + "gender": fisheries_group.get("gender_fisheries"), + "beneficiary_father_name": fisheries_group.get( + "ben_father_fisheries" + ), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + is_plantation = ( + ensure_str(dl.get("select_one_demand_plantation", "")).lower() == "yes" + or ensure_str(plantation_group.get("select_plantation_demands", "")).lower() + == "yes" + ) + if is_plantation: + result.append( + { + "livelihood_work": "Plantations", + "demand_type": plantation_group.get("demand_type_plantations"), + "work_demand": dl.get("Plantation") + or plantation_group.get("crop_name"), + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or plantation_group.get("ben_plantation"), + "gender": plantation_group.get("gender"), + "beneficiary_father_name": plantation_group.get("ben_father"), + "total_acres": dl.get("Plantation_crop") + or plantation_group.get("crop_area"), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + is_kitchen_garden = ( + ensure_str(dl.get("indi_assets", "")).lower() == "yes" + or ensure_str(kitchen_garden_group.get("assets_kg", "")).lower() == "yes" + ) + if is_kitchen_garden: + result.append( + { + "livelihood_work": "Kitchen Garden", + "demand_type": kitchen_garden_group.get( + "demand_type_kitchen_garden" + ), + "work_demand": dl.get("Plantation"), + "beneficiary_settlement": record.beneficiary_settlement, + "beneficiary_name": dl.get("beneficiary_name") + or kitchen_garden_group.get("ben_kitchen_gardens"), + "gender": kitchen_garden_group.get("gender_kitchen_gardens"), + "beneficiary_father_name": kitchen_garden_group.get( + "ben_father_kitchen_gardens" + ), + "total_acres": dl.get("area_didi_badi") + or kitchen_garden_group.get("area_kg"), + "latitude": record.latitude, + "longitude": record.longitude, + } + ) + + for agrohorti in ( + ODK_agrohorticulture.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + data = agrohorti.data_agohorticulture or {} + species_parts = filter( + None, + [ + data.get("select_multiple_species"), + data.get("select_multiple_species_other"), + ], + ) + species = " ".join(species_parts) or None + result.append( + { + "livelihood_work": "Plantations", + "demand_type": data.get("demand_type_plantations"), + "work_demand": species, + "beneficiary_settlement": data.get("beneficiary_settlement"), + "beneficiary_name": data.get("beneficiary_name"), + "gender": data.get("gender"), + "beneficiary_father_name": data.get("ben_father"), + "total_acres": data.get("crop_area"), + "latitude": agrohorti.latitude, + "longitude": agrohorti.longitude, + } + ) + + return result + + +def translate_livelihood_work(value, language): + translations = { + "hi": { + "Livestock": "पशुपालन", + "Fisheries": "मत्स्य पालन", + "Plantations": "पौधारोपण", + "Kitchen Garden": "रसोई बाड़ी", + }, + "od": { + "Livestock": "ପଶୁପାଳନ", + "Fisheries": "ମତ୍ସ୍ୟଚାଷ", + "Plantations": "ବୃକ୍ଷରୋପଣ", + "Kitchen Garden": "ରୋଷେଇ ବଗିଚା", + }, + } + + return translations.get(language, {}).get(value, value) + + +def get_nrm_works_data( + plan_id, +): + pid = str(plan_id) + result = [] + + for structure in ( + ODK_groundwater.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + dg = structure.data_groundwater or {} + result.append( + { + "work_category": "Recharge Structure", + "demand_type": dg.get("demand_type"), + "work_demand": structure.work_type, + "beneficiary_settlement": structure.beneficiary_settlement, + "beneficiary_name": dg.get("Beneficiary_Name"), + "gender": dg.get("select_gender"), + "beneficiary_father_name": dg.get("ben_father"), + "latitude": structure.latitude, + "longitude": structure.longitude, + } + ) + + for irr in ( + ODK_agri.objects.filter(plan_id=pid) + .exclude(status_re="rejected") + .exclude(is_deleted=True) + ): + da = irr.data_agri or {} + work_demand = irr.work_type + if (irr.work_type or "").lower() == "other": + work_demand = da.get("TYPE_OF_WORK_ID_other") or "Other (unspecified)" + result.append( + { + "work_category": "Irrigation Work", + "demand_type": da.get("demand_type_irrigation"), + "work_demand": work_demand, + "beneficiary_settlement": irr.beneficiary_settlement, + "beneficiary_name": da.get("Beneficiary_Name"), + "gender": da.get("gender"), + "beneficiary_father_name": da.get("ben_father"), + "latitude": irr.latitude, + "longitude": irr.longitude, + } + ) + + return result + + +def translate_work_category(value, language): + + translations = { + "hi": { + "Recharge Structure": "पुनर्भरण संरचना", + "Irrigation Work": "सिंचाई कार्य", + }, + "od": { + "Recharge Structure": "ପୁନର୍ଭରଣ ସଂରଚନା", + "Irrigation Work": "ସିଚାଇ କାର୍ଯ୍ୟ", + }, + } + + return translations.get(language, {}).get(value, value) + + +_COMMUNITY_DEMAND_VALUES = { + "community", + "community well", + "community demand", + "public", + "public well", + "shared among families", +} +_INDIVIDUAL_DEMAND_VALUES = {"private", "privately owned", "individual demand"} + + +def classify_demand_type(raw_value): + if not raw_value: + return raw_value + normalized = raw_value.strip().lower().replace("_", " ") + if normalized in _COMMUNITY_DEMAND_VALUES: + return "community_demand" + if normalized in _INDIVIDUAL_DEMAND_VALUES: + return "individual_demand" + return raw_value + + +IRRIGATION_STRUCTURE_MAPPING = { + "select_one_farm_pond": "Farm pond", + "select_one_community_pond": "Community Pond", + "select_one_well": "Well", + "select_one_canal": "Canal", + "select_one_farm_bund": "Farm bund", +} + +IRRIGATION_STRUCTURE_REVERSE_MAPPING = { + v.lower(): k for k, v in IRRIGATION_STRUCTURE_MAPPING.items() +} + +RS_WATER_STRUCTURE_MAPPING = { + "select_one_farm_pond": "Farm pond", + "select_one_community_pond": "Community Pond", + "select_one_repair_large_water_body": "Large water body", + "select_one_repair_canal": "Canal", + "select_one_check_dam": "Check dam", + "select_one_percolation_tank": "Percolation tank", + "select_one_rock_fill_dam": "Rock fill dam", + "select_one_loose_boulder_structure": "Loose boulder structure", + "select_one_model5_structure": "5% Model structure", + "select_one_Model30_40_structure": "30-40 Model structure", +} + +RS_WATER_STRUCTIRE_REVERSE_MAPPING = { + v.lower(): k for k, v in RS_WATER_STRUCTURE_MAPPING.items() +} + + +WATER_STRUCTURE_MAPPING = { + "select_one_farm_pond": "Farm pond", + "select_one_community_pond": "Community Pond", + "select_one_repair_large_water_body": "Large water body", + "select_one_repair_canal": "Canal", + "select_one_check_dam": "Check dam", + "select_one_percolation_tank": "Percolation tank", + "select_one_rock_fill_dam": "Rock fill dam", + "select_one_loose_boulder_structure": "Loose boulder structure", + "select_one_model5_structure": "5% Model structure", + "select_one_Model30_40_structure": "30-40 Model structure", +} + +WATER_STRUCTURE_REVERSE_MAPPING = { + v.lower(): k for k, v in WATER_STRUCTURE_MAPPING.items() +} diff --git a/dpr/models.py b/dpr/models.py index 83f7b33e..2959b52d 100644 --- a/dpr/models.py +++ b/dpr/models.py @@ -4,6 +4,7 @@ from django.db.models import Max from django.db.models.functions import Greatest +from django.utils import timezone DPR_STATUS_CHOICES = [ ("PENDING", "PENDING"), @@ -664,8 +665,9 @@ def get_latest_change_time(plan_id): latest_moderation=Max("moderated_at"), ) for key in ("latest_submission", "latest_deletion", "latest_moderation"): - if agg[key]: - times.append(agg[key]) + dt = normalize_datetime(agg[key]) + if dt: + times.append(dt) return max(times) if times else None def needs_regeneration(self): @@ -675,3 +677,13 @@ def needs_regeneration(self): if not latest_change: return False return latest_change > self.dpr_generated_at + + +def normalize_datetime(dt): + if not dt: + return None + + if timezone.is_naive(dt): + return timezone.make_aware(dt, timezone.get_current_timezone()) + + return dt diff --git a/dpr/service/__init__.py b/dpr/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dpr/service/form_download_service.py b/dpr/service/form_download_service.py new file mode 100644 index 00000000..4d2db4c7 --- /dev/null +++ b/dpr/service/form_download_service.py @@ -0,0 +1,249 @@ +import json +from pathlib import Path +import requests +from utilities.constants import ODK_BASE_URL, ODK_PROJECT_ID +from django.conf import settings +from plans.utils import fetch_bearer_token +from nrm_app.settings import ODK_USERNAME, ODK_PASSWORD +import pandas as pd +from utilities.logger import setup_logger +import re + +logger = setup_logger(__name__) + + +def sync_odk_forms(): + """ + Downloads ODK XLSX forms only when: + 1. Form version has changed, OR + 2. XLSX file is missing locally. + + Returns: + dict: Summary of downloaded and skipped forms. + """ + + token = fetch_bearer_token(ODK_USERNAME, ODK_PASSWORD) + headers = {"Authorization": f"Bearer {token}"} + + forms_dir = Path(settings.BASE_DIR) / "data" / "dpr" / "forms" + label_dirs = Path(settings.BASE_DIR) / "data" / "dpr" / "labels" + versions_file = Path(settings.BASE_DIR) / "data" / "odk" / "form_version.json" + + forms_dir.mkdir(parents=True, exist_ok=True) + versions_file.parent.mkdir(parents=True, exist_ok=True) + + if versions_file.exists(): + with open(versions_file, "r") as f: + local_versions = json.load(f) + else: + local_versions = {} + + response = requests.get( + f"{ODK_BASE_URL}{ODK_PROJECT_ID}/forms", + headers=headers, + ) + response.raise_for_status() + + forms = response.json() + + downloaded = [] + skipped = [] + + for form in forms: + form_id = form["xmlFormId"] + current_version = form.get("version") + + saved_version = local_versions.get(form_id) + file_path = forms_dir / f"{form_id}.xlsx" + label_path = label_dirs / f"{form_id}.json" + if ( + saved_version == current_version + and file_path.exists() + and label_path.exists() + ): + skipped.append(form_id) + continue + + print(f"Downloading {form_id} " f"(old={saved_version}, new={current_version})") + + file_response = requests.get( + f"{ODK_BASE_URL}{ODK_PROJECT_ID}/forms/{form_id}.xlsx", + headers=headers, + ) + file_response.raise_for_status() + + with open(file_path, "wb") as f: + f.write(file_response.content) + + local_versions[form_id] = current_version + downloaded.append(form_id) + try: + generate_labels_json( + file_path, + label_path, + ) + except Exception: + logger.exception(f"Failed to generate labels for {form_id}") + with open(versions_file, "w") as f: + json.dump(local_versions, f, indent=2) + + return { + "downloaded": downloaded, + "skipped": skipped, + "total_forms": len(forms), + } + + +def get_select_question_mapping(survey_df): + """ + Returns: + { + "gender": "gender", + "caste_group": "caste" + } + """ + + mapping = {} + + for _, row in survey_df.iterrows(): + + question_type = str(row.get("type", "")).strip() + question_name = row.get("name") + + if pd.isna(question_name): + continue + + question_name = str(question_name).strip() + + parts = question_type.split() + + if len(parts) < 2: + continue + + if parts[0] in ["select_one", "select_multiple"]: + mapping[question_name] = str(parts[1]).strip() + + return mapping + + +def generate_labels_json(form_path, output_path): + """ + Generate labels JSON from an XLSForm. + """ + + survey_df = pd.read_excel(form_path, sheet_name="survey") + choices_df = pd.read_excel(form_path, sheet_name="choices") + + # normalize list_name column + choices_df["list_name"] = choices_df["list_name"].astype(str).str.strip() + + question_mapping = get_select_question_mapping(survey_df) + + language_columns = [ + column for column in choices_df.columns if str(column).startswith("label::") + ] + + labels = {} + + for question_name, list_name in question_mapping.items(): + + question_name = str(question_name).strip() + list_name = str(list_name).strip() + + labels[question_name] = {} + + choice_rows = choices_df[choices_df["list_name"] == list_name] + + for _, row in choice_rows.iterrows(): + + choice_value = row.get("name") + + if pd.isna(choice_value): + continue + + choice_value = str(choice_value).strip().lower() + + labels[question_name][choice_value] = {} + + for column in language_columns: + + language = str(column).replace("label::", "").split("(")[0].strip() + + value = row.get(column) + + labels[question_name][choice_value][language] = ( + None if pd.isna(value) else str(value).strip() + ) + + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, "w", encoding="utf-8") as f: + json.dump(labels, f, ensure_ascii=False, indent=2) + + return labels + + +LANGUAGE_MAP = { + "en": "English", + "hi": "Hindi", + "gu": "Gujarati", + "kn": "Kannada", + "or": "Odia", +} + + +def load_form_labels(form_id): + label_file = Path(settings.BASE_DIR) / "data" / "dpr" / "labels" / f"{form_id}.json" + + if not label_file.exists(): + return {} + + with open(label_file, encoding="utf-8") as f: + return json.load(f) + + +def translate_choice(labels, field_name, value, language="en"): + if not value: + return value + + language_name = LANGUAGE_MAP.get(language, "English") + + normalized_labels = {str(key).strip(): val for key, val in labels.items()} + + field_labels = normalized_labels.get( + str(field_name).strip(), + {}, + ) + + value_normalized = str(value).strip().lower().replace("'", "’") + + translations = field_labels.get(value_normalized) + + if translations: + return translations.get(language_name, value) + + return value + + +def translate_multiple_choices( + labels, + field_name, + value, + language="en", +): + if not value: + return value + + values = [v.strip() for v in re.split(r"[,\s]+", str(value).strip()) if v.strip()] + + translated = [ + translate_choice( + labels, + field_name, + item, + language, + ) + for item in values + ] + + return ", ".join(translated) diff --git a/dpr/service/translation_service.py b/dpr/service/translation_service.py new file mode 100644 index 00000000..cb76f32a --- /dev/null +++ b/dpr/service/translation_service.py @@ -0,0 +1,16 @@ +import json +from pathlib import Path +from django.conf import settings + +TRANSLATION_DIR = Path(settings.BASE_DIR) / "data" / "dpr" / "translations" + + +def load_translations(language="en"): + + file_path = TRANSLATION_DIR / f"{language}.json" + + if not file_path.exists(): + file_path = TRANSLATION_DIR / "en.json" + + with open(file_path, "r", encoding="utf-8") as file: + return json.load(file) diff --git a/dpr/tasks.py b/dpr/tasks.py index b9082826..229a6075 100644 --- a/dpr/tasks.py +++ b/dpr/tasks.py @@ -1,3 +1,4 @@ +from django.templatetags.i18n import language from django.utils import timezone from nrm_app.celery import app from utilities.logger import setup_logger @@ -15,49 +16,61 @@ upload_dpr_to_s3, check_dpr_exists_on_s3, ) +from .views import generate_dpr_pdf logger = setup_logger(__name__) -def get_or_generate_dpr(plan, regenerate=False): +def get_or_generate_dpr(plan, regenerate=False, language="en"): dpr_report, created = DPR_Report.objects.get_or_create( plan_id=plan, - defaults={"plan_name": plan.plan, "status": "PENDING"} + defaults={"plan_name": plan.plan, "status": "PENDING"}, ) - + dpr_report_s3_url = f"https://dpr-resources.s3.ap-south-1.amazonaws.com/dpr-reports/{plan.id}_{plan.plan}_{language}.pdf" if not regenerate: - s3_exists = check_dpr_exists_on_s3(dpr_report.dpr_report_s3_url) + s3_exists = check_dpr_exists_on_s3(dpr_report_s3_url) if not created and not dpr_report.needs_regeneration() and s3_exists: - logger.info(f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}") + logger.info( + f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}" + ) return dpr_report, False - + logger.info(f"Generating new DPR for plan {plan.id}") dpr_report.status = "GENERATING" dpr_report.save(update_fields=["status"]) - - doc = create_dpr_document(plan) - s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan) - + + doc = generate_dpr_pdf(plan, language=language) + s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan, language) + dpr_report.dpr_report_s3_url = s3_url dpr_report.dpr_generated_at = timezone.now() dpr_report.status = "COMPLETED" dpr_report.last_updated_at = timezone.now() - dpr_report.save(update_fields=[ - "dpr_report_s3_url", "dpr_generated_at", "status", "last_updated_at" - ]) - + dpr_report.save( + update_fields=[ + "dpr_report_s3_url", + "dpr_generated_at", + "status", + "last_updated_at", + ] + ) + logger.info(f"DPR generated and saved to S3: {s3_url}") return dpr_report, True @app.task(bind=True, name="dpr.generate_dpr_task") -def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = False): +def generate_dpr_task( + self, plan_id: int, email_id: str, language: str, regenerate: bool = False +): plan = get_plan_details(plan_id) if plan is None: logger.error(f"Plan not found for ID: {plan_id}") return {"error": "Plan not found"} - dpr_report, was_regenerated = get_or_generate_dpr(plan, regenerate=regenerate) + dpr_report, was_regenerated = get_or_generate_dpr( + plan, regenerate=regenerate, language=language + ) mws_Ids = get_mws_ids_for_report(plan) mws_reports = [] @@ -83,7 +96,7 @@ def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = Fals f"https://geoserver.core-stack.org/api/v1/download_report/" f"?report_type=resource&district={district}&block={block}&plan_id={plan_id}&plan_name={plan.plan}" ) - + resource_report = None try: response = requests.get(resource_report_url, timeout=30) diff --git a/dpr/templatetags/__init__.py b/dpr/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dpr/templatetags/custom_filters.py b/dpr/templatetags/custom_filters.py new file mode 100644 index 00000000..2d191c3f --- /dev/null +++ b/dpr/templatetags/custom_filters.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + + +@register.filter +def format_text(value): + if not value: + return "NA" + + return str(value).replace("_", " ") diff --git a/dpr/urls.py b/dpr/urls.py index 2b2e51a0..8cbc6779 100644 --- a/dpr/urls.py +++ b/dpr/urls.py @@ -1,6 +1,7 @@ from django.urls import path from . import api +from . import views urlpatterns = [ path( @@ -21,9 +22,16 @@ name="generate_resource_report", ), path("download_report/", api.download_report, name="download_report"), - path("generate_tehsil_report/", api.generate_tehsil_report, name="generate_tehsil_report"), - path("generate_village_report/", api.generate_village_report, name="generate_village_report"), - + path( + "generate_tehsil_report/", + api.generate_tehsil_report, + name="generate_tehsil_report", + ), + path( + "generate_village_report/", + api.generate_village_report, + name="generate_village_report", + ), # DPR Data API path("dpr_data//summary/", api.dpr_summary, name="dpr_summary"), path( diff --git a/dpr/utils.py b/dpr/utils.py index 07fe2529..5a3acae7 100644 --- a/dpr/utils.py +++ b/dpr/utils.py @@ -14,7 +14,16 @@ from django.core.mail.backends.smtp import EmailBackend from docx import Document -from nrm_app.settings import EMAIL_HOST, EMAIL_HOST_PASSWORD, EMAIL_HOST_USER, EMAIL_PORT, EMAIL_TIMEOUT, EMAIL_USE_SSL, ODK_PASSWORD, ODK_USERNAME +from nrm_app.settings import ( + EMAIL_HOST, + EMAIL_HOST_PASSWORD, + EMAIL_HOST_USER, + EMAIL_PORT, + EMAIL_TIMEOUT, + EMAIL_USE_SSL, + ODK_PASSWORD, + ODK_USERNAME, +) from utilities.constants import ( ODK_URL_AGRI_MAINTENANCE, ODK_URL_GW_MAINTENANCE, @@ -45,7 +54,13 @@ ) import boto3 -from nrm_app.settings import DPR_S3_ACCESS_KEY, DPR_S3_SECRET_KEY, DPR_S3_REGION, DPR_S3_BUCKET, DPR_S3_FOLDER +from nrm_app.settings import ( + DPR_S3_ACCESS_KEY, + DPR_S3_SECRET_KEY, + DPR_S3_REGION, + DPR_S3_BUCKET, + DPR_S3_FOLDER, +) from botocore.exceptions import ClientError warnings.filterwarnings("ignore") @@ -265,8 +280,8 @@ def get_waterbody_repair_activities(data_waterbody, water_structure_type): and data_waterbody.get(other_field) ): return f"Other: {data_waterbody.get(other_field)}" - elif repair_value: - return repair_value.replace("_", " ").title() + # elif repair_value: + # return repair_value.replace("_", " ").title() return "NA" repair_field = structure_type_mapping.get(structure_type_lower) @@ -287,7 +302,7 @@ def get_waterbody_repair_activities(data_waterbody, water_structure_type): else: return "Other" - return repair_activity.replace("_", " ").title() + return repair_activity def sort_key(settlement): @@ -304,9 +319,10 @@ def transform_name(name): name = re.sub(r"^_|_$", "", name) return name.lower() + def to_utf8(value): """Ensure value is a properly encoded UTF-8 string for Word document. - + Handles cases where UTF-8 text was incorrectly decoded as Latin-1, resulting in garbled characters like 'ಪಾà²...' for Kannada/Hindi text. """ @@ -316,13 +332,13 @@ def to_utf8(value): value = " ".join(str(v) for v in value) if isinstance(value, bytes): try: - return value.decode('utf-8') + return value.decode("utf-8") except UnicodeDecodeError: - return value.decode('latin-1') + return value.decode("latin-1") if not isinstance(value, str): value = str(value) try: - return value.encode('latin-1').decode('utf-8') + return value.encode("latin-1").decode("utf-8") except (UnicodeDecodeError, UnicodeEncodeError): return value @@ -443,40 +459,40 @@ def send_dpr_email( logger.error(f"Failed to send email: {e}") -def upload_dpr_to_s3(doc, plan_id, plan_name): - doc_bytes = BytesIO() - doc.save(doc_bytes) - doc_bytes.seek(0) - +def upload_dpr_to_s3(pdf_bytes, plan_id, plan_name, language="en"): + + doc_bytes = BytesIO(pdf_bytes) + safe_plan_name = transform_name(plan_name) - s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}.docx" - + s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}_{language}.pdf" + s3_client = boto3.client( "s3", aws_access_key_id=DPR_S3_ACCESS_KEY, aws_secret_access_key=DPR_S3_SECRET_KEY, region_name=DPR_S3_REGION, ) - + s3_client.upload_fileobj( doc_bytes, DPR_S3_BUCKET, s3_key, ExtraArgs={ - "ContentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.docx"', + "ContentType": "application/pdf", + "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.pdf"', "CacheControl": "no-cache, no-store, must-revalidate", - } + }, ) - + ts = int(time.time()) s3_url = f"https://{DPR_S3_BUCKET}.s3.{DPR_S3_REGION}.amazonaws.com/{s3_key}?v={ts}" + logger.info(f"DPR uploaded to S3: {s3_url}") return s3_url def _extract_s3_key(s3_url): - + parsed = urlparse(s3_url) return parsed.path.lstrip("/") @@ -484,19 +500,19 @@ def _extract_s3_key(s3_url): def check_dpr_exists_on_s3(s3_url): if not s3_url: return False - + try: s3_key = _extract_s3_key(s3_url) except (IndexError, AttributeError): return False - + s3_client = boto3.client( "s3", aws_access_key_id=DPR_S3_ACCESS_KEY, aws_secret_access_key=DPR_S3_SECRET_KEY, region_name=DPR_S3_REGION, ) - + try: s3_client.head_object(Bucket=DPR_S3_BUCKET, Key=s3_key) return True @@ -507,18 +523,18 @@ def check_dpr_exists_on_s3(s3_url): def download_dpr_from_s3(s3_url): s3_key = _extract_s3_key(s3_url) - + s3_client = boto3.client( "s3", aws_access_key_id=DPR_S3_ACCESS_KEY, aws_secret_access_key=DPR_S3_SECRET_KEY, region_name=DPR_S3_REGION, ) - + doc_bytes = BytesIO() s3_client.download_fileobj(DPR_S3_BUCKET, s3_key, doc_bytes) doc_bytes.seek(0) - + doc = Document(doc_bytes) logger.info(f"DPR downloaded from S3: {s3_url}") - return doc \ No newline at end of file + return doc diff --git a/dpr/views.py b/dpr/views.py index 91ea44a2..7c0c9013 100644 --- a/dpr/views.py +++ b/dpr/views.py @@ -1,3 +1,63 @@ -from django.shortcuts import render +from datetime import date +from django.template.loader import render_to_string +from dpr.service.translation_service import load_translations +from weasyprint import HTML +from .gen_dpr import get_settlement_count_for_plan +from .utils import get_vector_layer_geoserver, transform_name +from nrm_app.settings import GEOSERVER_URL +from .get_dpr_sectionwise_data import ( + get_section_b_data, + get_section_c_data, + get_section_d_data, + get_section_e_data, + get_section_f_data, + get_section_g_data, +) +from .service.form_download_service import sync_odk_forms -# Create your views here. + +def generate_dpr_html(plan, language="en"): + translations = load_translations(language) + total_settlements = get_settlement_count_for_plan(plan.id) + mws_fortnight = get_vector_layer_geoserver( + geoserver_url=GEOSERVER_URL, + workspace="mws_layers", + layer_name="deltaG_fortnight_" + + transform_name(str(plan.district_soi.district_name)) + + "_" + + transform_name(str(plan.tehsil_soi.tehsil_name)), + ) + section_b_data, settlement_mws_ids, mws_gdf = get_section_b_data( + plan, total_settlements, mws_fortnight + ) + section_c_data = get_section_c_data(plan, language) + section_d_data = get_section_d_data(plan, settlement_mws_ids, mws_gdf, language) + section_e_data = get_section_e_data(plan, language) + section_f_data = get_section_f_data(plan, language) + section_g_data = get_section_g_data(plan, language) + html = render_to_string( + "dpr/base.html", + { + "t": translations, + "current_date": date.today().strftime("%B %d, %Y"), + "section_a": plan, + "section_b": section_b_data, + "section_c": section_c_data, + "section_d": section_d_data, + "section_e": section_e_data, + "section_f": section_f_data, + "section_g": section_g_data, + "footnote": f"DPR supported by {plan.organization.name} in {date.today().year}", + }, + ) + + return html + + +def generate_dpr_pdf(plan, language="en"): + sync_odk_forms() + html = generate_dpr_html(plan, language) + + pdf = HTML(string=html).write_pdf() + + return pdf diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 19a80786..725c6040 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -352,7 +352,8 @@ def resolve_env_path(name, default="", *, trailing_sep=False): EMAIL_USE_TLS = False EMAIL_HOST_USER = env("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") -EMAIL_TIMEOUT = 30 +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="") diff --git a/plans/api.py b/plans/api.py index 533a26f3..58bd4fc1 100644 --- a/plans/api.py +++ b/plans/api.py @@ -36,9 +36,11 @@ logger = logging.getLogger(__name__) from .build_layer import build_layer -from .models import ODKSyncLog, Plan +from .models import ODKSyncLog, PlanApp, Plan from .serializers import PlanAppSerializer from .utils import fetch_bearer_token, fetch_db_data +from geoadmin.models import GramPanchayat +from django.db.models import Q _COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( "layer_name", @@ -756,3 +758,32 @@ def map_plan_to_gp(request): }, } ) + + +@api_view(["GET"]) +@schema(None) +def plan_count(request): + """ + gives plan count on the basis of org_id or project_id and filter + """ + org_id = request.query_params.get("org_id") + project_id = request.query_params.get("project_id") + is_completed = request.query_params.get("is_completed") + + queryset = PlanApp.objects.filter(enabled=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if is_completed: + queryset = queryset.filter(is_completed=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if org_id: + plan_count = queryset.filter(organization=org_id).count() + elif project_id: + plan_count = queryset.filter(project=project_id).count() + else: + plan_count = 0 + + return Response({"plan_count": plan_count}) diff --git a/plans/urls.py b/plans/urls.py index 9beec875..675bc64a 100644 --- a/plans/urls.py +++ b/plans/urls.py @@ -46,4 +46,5 @@ path("", include(org_watershed_router.urls)), path("", include(global_router.urls)), path("map_plan_to_gp/", api.map_plan_to_gp, name="map_plan_to_gp"), + path("plan_count/", api.plan_count, name="plan_count"), ] diff --git a/stats_generator/mws_indicators.py b/stats_generator/mws_indicators.py index bf8f311b..49cedf07 100644 --- a/stats_generator/mws_indicators.py +++ b/stats_generator/mws_indicators.py @@ -143,7 +143,7 @@ def generate_mws_data_for_kyl_filters( 0 ] # terrain except: - terrainCluster_ID = "" + terrainCluster_ID = -9999 try: df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ @@ -256,13 +256,15 @@ def sens_slope(data): except Exception as e: # Handle exception and ensure all variables are set - cropping_intensity_avg = 0 - cropping_intensity_trend = "" - avg_single_cropped = 0 - avg_double_cropped = 0 - avg_triply_cropped = 0 + cropping_intensity_avg = -9999 + cropping_intensity_trend = -9999 + avg_single_cropped = -9999 + avg_double_cropped = -9999 + avg_triply_cropped = -9999 print(f"Error occurred: {e}") + + ##################### SWB ##################### try: df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id @@ -351,21 +353,22 @@ def sens_slope(data): ) except Exception as e: - avg_wsr_ratio_kharif = 0 - avg_wsr_ratio_rabi = 0 - avg_wsr_ratio_zaid = 0 + avg_wsr_ratio_kharif = -9999 + avg_wsr_ratio_rabi = -9999 + avg_wsr_ratio_zaid = -9999 print(f"Error occurred: {e}") ############ Swb_average - avg_kharif_surface_water_mws = 0 - avg_rabi_surface_water_mws = 0 - avg_zaid_surface_water_mws = 0 - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - if not df_swb_annual_mws_data.empty: - total_swb_area = df_swb_annual_mws_data.iloc[0][ - "total_swb_area_in_ha" + avg_kharif_surface_water_mws = -9999 + avg_rabi_surface_water_mws = -9999 + avg_zaid_surface_water_mws = -9999 + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + if not df_swb_annual_mws_data.empty: + total_swb_area = df_swb_annual_mws_data.iloc[0][ + "total_swb_area_in_ha" ] if total_swb_area != 0: # Check if total_swb_area is not zero @@ -420,11 +423,11 @@ def sens_slope(data): avg_perc_kharif_surface_water_mws = ( avg_perc_rabi_surface_water_mws ) = avg_perc_zaid_surface_water_mws = 0 - else: - print("DataFrame is empty. No data to process.") + + except: avg_perc_kharif_surface_water_mws = ( avg_perc_rabi_surface_water_mws - ) = avg_perc_zaid_surface_water_mws = 0 + ) = avg_perc_zaid_surface_water_mws = -9999 ################# SWB Trend ###################### try: @@ -445,33 +448,36 @@ def sens_slope(data): else: trend_swb = "-1" except: - trend_swb = "-1" + trend_swb = -9999 ######### G Trend ################# - G_Trend = ( - hydro_annual_mws_data.filter(like="G") - .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) - .dropna() - ) - G_Trend = G_Trend.squeeze().tolist() - result = mk.original_test(G_Trend) - - def sens_slope(data): - slopes = [] - for i in range(len(data) - 1): - for j in range(i + 1, len(data)): - s = (data[j] - data[i]) / (j - i) - slopes.append(s) - return np.median(slopes) - - trend_g_value = sens_slope(G_Trend) - trend_g = None - if result.trend == "no trend": - trend_g = "0" - elif result.trend == "increasing": - trend_g = "1" - else: - trend_g = "-1" + try: + G_Trend = ( + hydro_annual_mws_data.filter(like="G") + .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) + .dropna() + ) + G_Trend = G_Trend.squeeze().tolist() + result = mk.original_test(G_Trend) + + def sens_slope(data): + slopes = [] + for i in range(len(data) - 1): + for j in range(i + 1, len(data)): + s = (data[j] - data[i]) / (j - i) + slopes.append(s) + return np.median(slopes) + + trend_g_value = sens_slope(G_Trend) + trend_g = None + if result.trend == "no trend": + trend_g = "0" + elif result.trend == "increasing": + trend_g = "1" + else: + trend_g = "-1" + except: + trend_g = -9999 ######### drought_category ############## try: @@ -526,8 +532,8 @@ def sens_slope(data): 4, ) except: - drought_category = 0 - avg_dry_spell_in_weeks = 0 + drought_category = -9999 + avg_dry_spell_in_weeks = -9999 ################# avg_runoff runoff_columns = hydro_annual_mws_data.filter( @@ -549,7 +555,7 @@ def sens_slope(data): .sum() ) except: - nrega_assets_sum = 0 + nrega_assets_sum = -9999 ############ MWS Intersect Villages ######################## try: @@ -603,8 +609,8 @@ def sens_slope(data): ) except: - degradation_land_area = 0 - change_in_cropping_intensity_area = 0 + degradation_land_area = -9999 + change_in_cropping_intensity_area = -9999 ############ Change Detection Afforestation ################### try: @@ -622,7 +628,7 @@ def sens_slope(data): "total_afforestation_area_in_ha", None ).iloc[0] except: - afforestation_land_area = 0 + afforestation_land_area = -9999 ############ Change Detection Deforestation ################### try: @@ -636,7 +642,7 @@ def sens_slope(data): "total_deforestation_area_in_ha", None ).iloc[0] except: - deforestation_land_area = 0 + deforestation_land_area = -9999 ############ Change Detection Urbanization ################### try: @@ -647,7 +653,7 @@ def sens_slope(data): "total_urbanization_area_in_ha", None ).iloc[0] except: - urbanization_land_area = 0 + urbanization_land_area = -9999 ############# Terrain lulc slope / plain ##################### try: @@ -661,7 +667,7 @@ def sens_slope(data): ) except: - lulc_slope_category = "" + lulc_slope_category = -9999 try: df_lulc_plain_mws_data = sheets["terrain_lulc_plain"][ @@ -674,7 +680,7 @@ def sens_slope(data): ) except: - lulc_plain_category = "" + lulc_plain_category = -9999 ################# Restoration Vector ######################### try: @@ -688,8 +694,8 @@ def sens_slope(data): "protection_area_in_ha", None ).iloc[0] except: - wide_scale_restoration = 0 - area_protection = 0 + wide_scale_restoration = -9999 + area_protection = -9999 ################# Aquifer Vector ######################### aquifer_class_map = {0: "Hard Rock", 1: "Alluvial"} @@ -706,7 +712,7 @@ def sens_slope(data): aquifer_class_name = "Alluvial" aquifer_class = int(class_to_id.get(aquifer_class_name, "")) except Exception: - aquifer_class = "" + aquifer_class = -9999 ################# SOGE Vector ######################### Soge_class = { @@ -729,7 +735,7 @@ def sens_slope(data): class_to_id.get(soge_class_name, "") ) # Returns None if not found except Exception: - soge_class = 4 + soge_class = -9999 ################## LCW Conflict ###################### ## if count is 0 then Areas with no conflicts else Areas with conflicts @@ -742,7 +748,7 @@ def sens_slope(data): else: lcw_conflict = 1 except Exception as e: - lcw_conflict = 0 + lcw_conflict = -9999 ################## mining ###################### ## if count is 0 then Areas with no mining else Areas with mining @@ -755,7 +761,7 @@ def sens_slope(data): else: mining = 1 except Exception as e: - mining = 0 + mining = -9999 ################## green credit ###################### ## if count is 0 then Areas with no green credit else Areas with green credit @@ -768,7 +774,7 @@ def sens_slope(data): else: green_credit = 1 except Exception as e: - green_credit = 0 + green_credit = -9999 ################## factory csr ###################### ## if count is 0 then Areas with no factory else Areas with factory @@ -781,7 +787,7 @@ def sens_slope(data): else: factory_csr = 1 except Exception as e: - factory_csr = 0 + factory_csr = -9999 ############ MWS Intersect Swb ######################## try: @@ -825,13 +831,13 @@ def sens_slope(data): mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] # Average of all UID mean elevations - overall_mean_elevation = dem_df["mean_elevation"].mean() + overall_mean_elevation = dem_df["mean_elevation_in_m"].mean() if not mws_dem_data.empty: row = mws_dem_data.iloc[0] relief = round( - row["max_elevation"] - row["min_elevation"], 2 + row["max_elevation_in_m"] - row["min_elevation_in_m"], 2 ) - mean_elevation = round(row["mean_elevation"], 2) + mean_elevation = round(row["mean_elevation_in_m"], 2) # Relative mean elevation if overall_mean_elevation != 0: @@ -847,15 +853,15 @@ def sens_slope(data): relative_mean_elevation = 0 else: - relief = 0 - mean_elevation = 0 - relative_mean_elevation = 0 + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 except Exception as e: print(f"Error in getting DEM data: {e}") - relief = 0 - mean_elevation = 0 - relative_mean_elevation = 0 + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 ############ Canal ######################## try: @@ -872,7 +878,7 @@ def sens_slope(data): except Exception as e: print(f"Error in getting canal data: {e}") - canal_available = False + canal_available = -9999 ############ Canal ######################## try: @@ -889,13 +895,13 @@ def sens_slope(data): except Exception as e: print(f"Error in getting canal data: {e}") - river_available = False + river_available = -9999 ############ lulc vector ######################## try: - lulc_shrub_percent = 0 - lulc_forest_percent = 0 - lulc_crop_percent = 0 + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 lulc_df = sheets["lulc_vector"] @@ -954,12 +960,16 @@ def sens_slope(data): (cropped_area_in_ha / area_in_ha) * 100, 2 ) + else: + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 except Exception as e: print(f"Error in LULC vector: {e}") - lulc_shrub_percent = 0 - lulc_forest_percent = 0 - lulc_crop_percent = 0 + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 ############ Canal ######################## try: @@ -970,16 +980,17 @@ def sens_slope(data): ] if not mws_drainage_density_data.empty: row = mws_drainage_density_data.iloc[0] - drainage_density = round(row["drainage_density"], 2) + drainage_density = round(row["drainage_density_std_in_km_per_km2"], 2) else: drainage_density = 0 else: - drainage_density = 0 + drainage_density = -9999 + except Exception as e: print(f"Error in getting drainage_density data: {e}") - drainage_density = 0 + drainage_density = -9999 results.append( { diff --git a/stats_generator/utils.py b/stats_generator/utils.py index 6a38a648..bcc2c694 100644 --- a/stats_generator/utils.py +++ b/stats_generator/utils.py @@ -244,8 +244,10 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) elif workspace == "drainage_density": create_excel_for_drainage_density(geojson_data, writer) - elif workspace == "antyodaya_analysis": + elif workspace == "antyodaya_2020": create_excel_for_antyodaya_20(geojson_data, writer) + elif workspace == "livestocks": + create_excel_for_livestock(geojson_data, writer) results.append( {"layer": layer_name, "status": "success", "workspace": workspace} @@ -254,24 +256,49 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): return results +def create_excel_for_livestock(data, writer): + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Columns to exclude + exclude_cols = ["state_name","district_name","TEHSIL"] + df = df.drop(columns=exclude_cols, errors="ignore") + + # Keep important columns first if they exist + first_cols = [c for c in ["pc11_village_id", "NAME"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + df.to_excel(writer, sheet_name="livestock", index=False) + print("Excel file created for livestock") + except Exception as e: + print(f"Error in getting livestock data: {e}") + + def create_excel_for_antyodaya_20(data, writer): - features = data.get("features", []) - df_data = [feature.get("properties", {}) for feature in features] - df = pd.DataFrame(df_data) + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) - # Keep important columns first if they exist - first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] + # Keep important columns first if they exist + first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] - # Round numeric columns - numeric_cols = df.select_dtypes(include=["number"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="antyodaya", index=False) - print("Excel file created for antyodaya") + # Round numeric columns + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="antyodaya", index=False) + print("Excel file created for antyodaya") + except Exception as e: + print(f"Error in getting antyodaya data: {e}") def create_excel_for_drainage_density(data, writer): + import ast print("Inside create_excel_for Drainage Density") df_data = [] features = data["features"] @@ -281,7 +308,9 @@ def create_excel_for_drainage_density(data, writer): row = { "UID": properties.get("uid", ""), "area_in_ha": properties.get("area_in_ha", ""), - "drainage_density": properties.get("drainage_density", ""), + "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), + "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), + "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), } df_data.append(row) @@ -346,44 +375,50 @@ def get_key(base_key, trunc_prefix, idx): def create_excel_for_canal(data, writer): print("Inside create_excel_for Canal") - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "project_name": properties.get("prjname", ""), - "canal_code": properties.get("cancode", ""), - "canal_name": properties.get("canname", ""), - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "project_name": properties.get("prjname", ""), + "canal_code": properties.get("cancode", ""), + "canal_name": properties.get("canname", ""), + } - df_data.append(row) + df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="canal", index=False) - print("Excel file created for canal") + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="canal", index=False) + print("Excel file created for canal") + except Exception as e: + print("Canal Layer not found :: ", e) def create_excel_for_river(data, writer): print("Inside create_excel_for River") - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "river_name": properties.get("rivname", ""), - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "river_name": properties.get("rivname", ""), + } - df_data.append(row) + df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="river", index=False) - print("Excel file created for river") + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="river", index=False) + print("Excel file created for river") + except Exception as e: + print("River Layer not found :: ", e) def create_excel_for_dem(data, writer): @@ -397,9 +432,9 @@ def create_excel_for_dem(data, writer): row = { "UID": properties.get("uid", ""), - "min_elevation": properties.get("min_elevation", ""), - "max_elevation": properties.get("max_elevation", ""), - "mean_elevation": properties.get("mean_elevation", ""), + "min_elevation_in_m": properties.get("min_elevation", ""), + "max_elevation_in_m": properties.get("max_elevation", ""), + "mean_elevation_in_m": properties.get("mean_elevation", ""), } df_data.append(row) @@ -470,19 +505,36 @@ def calculate_intersection_area(geom1, geom2): def create_excel_for_facilities(data, writer): - features = data["features"] - df_data = [feature["properties"] for feature in features] + try: + features = data["features"] + df_data = [feature["properties"] for feature in features] - df = pd.DataFrame(df_data) + df = pd.DataFrame(df_data) - # keep first columns - first_cols = ["censuscode2011", "censusname"] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="facilities_proximity", index=False) - print("Excel file created for facilities_proximity") + first_cols = ["censuscode2011", "censusname"] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] + df.rename( + columns={ + col: f"{col}_in_km" + for col in df.columns + if col not in exclude_cols + }, + inplace=True + ) + + # Write to Excel + df.to_excel(writer, sheet_name="facilities_proximity", index=False) + + print("Excel file created for facilities_proximity") + except Exception as e: + print("facilities_proximity Layer not found :: ", e) + def create_excel_for_mws(data, writer): @@ -571,108 +623,123 @@ def create_excel_for_stream_order(data, writer): def create_excel_for_mining(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["company_na"], - "proposal": properties["proposal"], - "sector_moefcc": properties["sector_moe"], - "village": properties["village"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["company_na"], + "proposal": properties["proposal"], + "sector_moefcc": properties["sector_moe"], + "village": properties["village"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mining", index=False) - print("Excel file created for mining") + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mining", index=False) + print("Excel file created for mining") + except Exception as e: + print("Mining Layer not found :: ", e) def create_excel_for_green_credit(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["division"], - # "parcel_id": properties["parcel_id"], - "land_info": properties["land_info"], - "kml_url": properties["kml_url"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["division"], + # "parcel_id": properties["parcel_id"], + "land_info": properties["land_info"], + "kml_url": properties["kml_url"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="green_credit", index=False) - print("Excel file created for green_credit") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="green_credit", index=False) + print("Excel file created for green_credit") + except Exception as e: + print("green credit Layer not found :: ", e) def create_excel_for_factory_csr(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "Company_Name": properties["COMPANY NA"], - "ADDRESS": properties["ADDRESS"], - "LOCATION T": properties["LOCATION T"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "Company_Name": properties["COMPANY NA"], + "ADDRESS": properties["ADDRESS"], + "LOCATION T": properties["LOCATION T"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="factory_csr", index=False) - print("Excel file created for factory_csr") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="factory_csr", index=False) + print("Excel file created for factory_csr") + except Exception as e: + print("factory csr Layer not found :: ", e) def create_excel_for_agroecological(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "organization_name": properties["organization_name"], - "organization_type": properties["organization_type"], - "created_at": properties["created_at"], - "contact_person": properties["contact_person"], - "email": properties["email"], - "domains": properties["domains"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "organization_name": properties["organization_name"], + "organization_type": properties["organization_type"], + "created_at": properties["created_at"], + "contact_person": properties["contact_person"], + "email": properties["email"], + "domains": properties["domains"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="agroecological", index=False) - print("Excel file created for agroecological") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="agroecological", index=False) + print("Excel file created for agroecological") + except Exception as e: + print("agroecological Layer not found :: ", e) def create_excel_for_lcw(data, writer): - df_data = [] - features = data["features"] + try: + df_data = [] + features = data["features"] - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "title_of_conflict": properties["Title of Conflict"], - "link_to_conflict": properties["Link to conflict"], - } + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "title_of_conflict": properties["Title of Conflict"], + "link_to_conflict": properties["Link to conflict"], + } - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="lcw_conflict", index=False) - print("Excel file created for lcw_conflict") + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="lcw_conflict", index=False) + print("Excel file created for lcw_conflict") + except Exception as e: + print("lcw Layer not found :: ", e) def create_excel_for_soge(data, xlsx_file, writer): diff --git a/stats_generator/village_indicators.py b/stats_generator/village_indicators.py index b3bbdee7..e33942c2 100644 --- a/stats_generator/village_indicators.py +++ b/stats_generator/village_indicators.py @@ -51,66 +51,66 @@ def safe_val(v): result = { "essential_education_infra": get_max( [ - row.get("school_primary_distance", -1), - row.get("school_upper_primary_distance", -1), - row.get("school_secondary_distance", -1), + row.get("school_primary_distance_in_km", -1), + row.get("school_upper_primary_distance_in_km", -1), + row.get("school_secondary_distance_in_km", -1), ] ), "higher_education_infra": get_min( [ - row.get("school_higher_secondary_distance", -1), - row.get("college_distance", -1), - row.get("universities_distance", -1), + row.get("school_higher_secondary_distance_in_km", -1), + row.get("college_distance_in_km", -1), + row.get("universities_distance_in_km", -1), ] ), "essential_health_services": get_max( [ - row.get("health_sub_cen_distance", -1), - row.get("health_phc_distance", -1), + row.get("health_sub_cen_distance_in_km", -1), + row.get("health_phc_distance_in_km", -1), ] ), "advanced_health_services": get_min( [ - row.get("health_chc_distance", -1), - row.get("health_dis_h_distance", -1), - row.get("health_s_t_h_distance", -1), + row.get("health_chc_distance_in_km", -1), + row.get("health_dis_h_distance_in_km", -1), + row.get("health_s_t_h_distance_in_km", -1), ] ), "public_distribution_system": get_max( [ - row.get("pds_distance", -1), + row.get("pds_distance_in_km", -1), ] ), "financial_inclusion": get_max( [ - row.get("csc_distance", -1), - row.get("bank_mitra_distance", -1), - row.get("bank_branch_distance", -1), - row.get("bank_atm_distance", -1), + row.get("csc_distance_in_km", -1), + row.get("bank_mitra_distance_in_km", -1), + row.get("bank_branch_distance_in_km", -1), + row.get("bank_atm_distance_in_km", -1), ] ), "agri_market_access": get_min( [ - row.get("apmc_distance", -1), - row.get("agri_industry_markets_trading_distance", -1), + row.get("apmc_distance_in_km", -1), + row.get("agri_industry_markets_trading_distance_in_km", -1), ] ), "post_harvest_infra": get_min( [ - row.get("agri_industry_storage_warehousing_distance", -1), - row.get("agri_industry_distribution_utilities_distance", -1), - row.get("agri_industry_agri_processing_distance", -1), - row.get("agri_industry_industrial_manufacturing_distance", -1), + row.get("agri_industry_storage_warehousing_distance_in_km", -1), + row.get("agri_industry_distribution_utilities_distance_in_km", -1), + row.get("agri_industry_agri_processing_distance_in_km", -1), + row.get("agri_industry_industrial_manufacturing_distance_in_km", -1), ] ), "farmer_cooperatives_access": safe_val( - row.get("agri_industry_co_operatives_societies_distance", -1) + row.get("agri_industry_co_operatives_societies_distance_in_km", -1) ), "livestock_management_centers": safe_val( - row.get("agri_industry_dairy_animal_husbandry_distance", -1) + row.get("agri_industry_dairy_animal_husbandry_distance_in_km", -1) ), "agricultural_support_infrastructure": safe_val( - row.get("agri_industry_agri_support_infrastructure_distance", -1) + row.get("agri_industry_agri_support_infrastructure_distance_in_km", -1) ), } diff --git a/templates/dpr/base.html b/templates/dpr/base.html new file mode 100644 index 00000000..d1eb68e2 --- /dev/null +++ b/templates/dpr/base.html @@ -0,0 +1,147 @@ + + + + + + + + + + +

      + {{ t.common.dpr_title }} +

      + +
      + {{ current_date }} +
      + + {% include "dpr/section_a.html" %} + {% include "dpr/section_b.html" %} + {% include "dpr/section_c_socio_economic.html" %} + {% include "dpr/section_c_mgnrega.html" %} + {% include "dpr/section_c_crop.html" %} + {% include "dpr/section_c_livelihood.html" %} + {% include "dpr/section_d_base.html" %} + {% include "dpr/section_d_mws_information.html" %} + {% include "dpr/section_d_well_summary.html" %} + {% include "dpr/section_d_detail_well_info.html" %} + {% include "dpr/section_d_water_structure_summary.html" %} + {% include "dpr/section_d_detail_water_structure.html" %} + {% include "dpr/section_e_base.html" %} + {% include "dpr/section_e_maintenance_work_by_asset_type.html" %} + {% include "dpr/section_e_recharge_structure.html" %} + {% include "dpr/section_e_irrigation_structure.html" %} + {% include "dpr/section_e_water_structure.html" %} + {% include "dpr/section_e_rs_swb.html" %} + {% include "dpr/section_f.html" %} + {% include "dpr/section_g.html" %} + +
      +
      +

      {{ footnote }}

      +
      + + + + \ No newline at end of file diff --git a/templates/dpr/section_a.html b/templates/dpr/section_a.html new file mode 100644 index 00000000..7b6a804c --- /dev/null +++ b/templates/dpr/section_a.html @@ -0,0 +1,61 @@ +
      + +

      + {{ t.section_a.title }} +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {{ t.section_a.organization }} + + {{ section_a.organization.name|default:t.common.na }} +
      + {{ t.section_a.project }} + + {{ section_a.project.name|default:t.common.na }} +
      + {{ t.section_a.plan }} + + {{ section_a.plan|default:t.common.na }} +
      + {{ t.section_a.facilitator }} + + {{ section_a.facilitator_name|default:t.common.na }} +
      + {{ t.section_a.process_preparation }} + + {{ t.section_a.process_preparation_value }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_b.html b/templates/dpr/section_b.html new file mode 100644 index 00000000..0be2ac45 --- /dev/null +++ b/templates/dpr/section_b.html @@ -0,0 +1,117 @@ +
      + +

      + {{ t.section_b.title }} +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + {{ t.section_b.village }} + + {{ section_b.village_name|default:t.common.na }} +
      + {{ t.section_b.gram_panchayat }} + + {{ section_b.gram_panchayat|default:t.common.na }} +
      + {{ t.section_b.tehsil }} + + {{ section_b.tehsil|default:t.common.na }} +
      + {{ t.section_b.district }} + + {{ section_b.district|default:t.common.na }} +
      + {{ t.section_b.state }} + + {{ section_b.state|default:t.common.na }} +
      + {{ t.section_b.number_of_settlements }} + + {{ section_b.total_settlements|default:t.common.na }} +
      + {{ t.section_b.intersecting_mws }} + + + {% if section_b.settlement_mws_pairs %} + + + + + + + + {% for item in section_b.settlement_mws_pairs %} + + + + + {% endfor %} + +
      + {{ t.section_b.intersecting_mws_inner_settlement }} + + {{ t.section_b.intersecting_mws_inner_mws }} +
      {{ item.settlement }}{{ item.mws_id }}
      + + {% else %} + No intersecting watersheds + {% endif %} + +
      + {{ t.section_b.latitude_and_longitude }} + + {{ section_b.village_coordinates|default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_c_crop.html b/templates/dpr/section_c_crop.html new file mode 100644 index 00000000..84e00c09 --- /dev/null +++ b/templates/dpr/section_c_crop.html @@ -0,0 +1,72 @@ +
      + +

      + {{ t.section_c_crop.title }} +

      + + + + + + + + + + + + + + + + + + {% for item in section_c.crop_info %} + + + + + + + + + + + + + + + + + + + + + + + + {% endfor %} +
      {{ t.section_c_crop.settlement_name }}{{ t.section_c_crop.irrigation_source }}{{ t.section_c_crop.crop_in_kharif }}{{ t.section_c_crop.kharif_acerage }}{{ t.section_c_crop.crop_in_rabi }}{{ t.section_c_crop.rabi_acerage }}{{ t.section_c_crop.crop_in_zaid }}{{ t.section_c_crop.zaid_acerage }}{{ t.section_c_crop.crop_intensity }}{{ t.section_c_crop.land_classification }}
      + {{ item.beneficiary_settlement|default:t.common.na }} + + {{ item.irrigation_source|default:t.common.na }} + + + {{ item.kharif_crops|default:t.common.na }} + + + {{ item.kharif_acres |default:t.common.na }} + + {{ item.rabi_crops |default:t.common.na}} + + {{ item.rabi_acres |default:t.common.na }} + + {{ item.zaid_crops |default:t.common.na }} + + {{ item.zaid_acres |default:t.common.na }} + + {{ item.cropping_intensity |default:t.common.na }} + + {{ item.land_classification |default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_c_livelihood.html b/templates/dpr/section_c_livelihood.html new file mode 100644 index 00000000..8bab15de --- /dev/null +++ b/templates/dpr/section_c_livelihood.html @@ -0,0 +1,52 @@ + +
      + +

      + {{ t.section_c_livelihood.title }} +

      + + + + + + + + + + + + + {% for item in section_c.livestock_info %} + + + + + + + + + + + + + + + + {% endfor %} +
      {{ t.section_c_livelihood.settlement_name }}{{ t.section_c_livelihood.goat }}{{ t.section_c_livelihood.sheep }}{{ t.section_c_livelihood.cattle }}{{ t.section_c_livelihood.piggery }}{{ t.section_c_livelihood.poultary }}
      + {{ item.settlement_name|default:t.common.na }} + + {{ item.goats|default:t.common.na }} + + + {{ item.sheep|default:t.common.na }} + + + {{ item.cattle |default:t.common.na }} + + {{ item.piggery |default:t.common.na}} + + {{ item.poultry |default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_c_mgnrega.html b/templates/dpr/section_c_mgnrega.html new file mode 100644 index 00000000..040bd3e6 --- /dev/null +++ b/templates/dpr/section_c_mgnrega.html @@ -0,0 +1,66 @@ +
      + +

      + {{ t.section_c_mgnrega.title }} +

      + + + + + + + + + + + + + {% for item in section_c.mgnrega %} + + + + + + + + + + + + + + + + {% endfor %} +
      {{ t.section_c_mgnrega.settlement_name }}{{ t.section_c_mgnrega.total_household_applied_job_card }}{{ t.section_c_mgnrega.work_days_previous_year }}{{ t.section_c_mgnrega.demand_made_in_previous_year }}{{ t.section_c_mgnrega.involved_in_village_planing }}{{ t.section_c_mgnrega.issues }}
      + {{ item.settlement_name|default:t.common.na }} + +

      {{ t.section_c_mgnrega.applied }}: + {% if item.nrega_job_applied == 0 %} + {{ t.common.na }} + {% else %} + {{ item.nrega_job_applied }} + {% endif %} +

      +

      {{ t.section_c_mgnrega.having }}: + {% if item.nrega_job_card == 0 %} + {{ t.common.na }} + {% else %} + {{ item.nrega_job_card }} + {% endif %} +

      +
      + {% if item.nrega_work_days == 0 %} + {{ t.common.na }} + {% else %} + {{ item.nrega_work_days }} + {% endif %} + + {{ item.nrega_past_work_label|default:t.common.na }} + + {{ item.nrega_demand_label|default:t.common.na}} + + {{ item.nrega_issues_label|default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_c_socio_economic.html b/templates/dpr/section_c_socio_economic.html new file mode 100644 index 00000000..341a8a77 --- /dev/null +++ b/templates/dpr/section_c_socio_economic.html @@ -0,0 +1,82 @@ +
      + +

      + {{ t.section_c_socio_economic.title }} +

      + +

      {{ t.section_c_socio_economic.description }}

      + + + + + + + + + + + + + + + + {% for item in section_c.socio_eco %} + + + + + + + + + + + + + + + + {% endfor %} + + +
      {{ t.section_c_socio_economic.name_of_the_settlement }}{{ t.section_c_socio_economic.number_of_households }}{{ t.section_c_socio_economic.settlement_type }}{{ t.section_c_socio_economic.caste_group }}{{ t.section_c_socio_economic.total_households }}{{ t.section_c_socio_economic.marginal_farmer }}
      + {{ item.settlement_name|default:t.common.na }} + + {{ item.number_of_households|default:t.common.na }} + + {{ item.largest_caste_label|default:t.common.na }} + + {% if item.largest_caste|lower == "single caste group" %} + {{ item.smallest_caste_label|default:t.common.na }} + {% elif item.largest_caste|lower == "mixed caste group" %} + {{ item.settlement_status_label|default:t.common.na }} + {% else %} + {{ t.common.na }} + {% endif %} + + + + + + + + + + + + + + + + + + + + + +
      {{ t.section_c_socio_economic.sc }}{{ item.data_settlement.count_sc|default:t.common.na }}
      {{ t.section_c_socio_economic.st }}{{ item.data_settlement.count_st|default:t.common.na }}
      {{ t.section_c_socio_economic.obc }}{{ item.data_settlement.count_obc|default:t.common.na }}
      {{ t.section_c_socio_economic.gen }}{{ item.data_settlement.count_general|default:t.common.na }}
      +
      + {{ item.farmer_family.marginal_farmers|default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_d_base.html b/templates/dpr/section_d_base.html new file mode 100644 index 00000000..64ef8085 --- /dev/null +++ b/templates/dpr/section_d_base.html @@ -0,0 +1,7 @@ +
      + +

      {{ t.section_d_base.title }}

      + +

      {{ t.section_d_base.description }}

      + +
      \ No newline at end of file diff --git a/templates/dpr/section_d_detail_water_structure.html b/templates/dpr/section_d_detail_water_structure.html new file mode 100644 index 00000000..db7b7df7 --- /dev/null +++ b/templates/dpr/section_d_detail_water_structure.html @@ -0,0 +1,87 @@ +
      + +

      {{ t.section_d_detail_water_structure.title }}

      + + {% for waterbody in section_d.water_structures %} + +

      + {{ waterbody.settlement|default:t.common.na }} +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {{ t.section_d_detail_water_structure.mws_id }}{{ waterbody.mws_id|default:t.common.na }}
      {{ t.section_d_detail_water_structure.beneficiary_settlement }}{{ waterbody.settlement|default:t.common.na }}
      {{ t.section_d_detail_water_structure.water_structure_owner }}{{ waterbody.owner|default:t.common.na }}
      {{ t.section_d_detail_water_structure.beneficiary_name }}{{ waterbody.beneficiary_name|default:t.common.na }}
      {{ t.section_d_detail_water_structure.beneficiary_father_name }}{{ waterbody.father_name|default:t.common.na }}
      {{ t.section_d_detail_water_structure.who_manage }}{{ waterbody.who_manages|default:t.common.na }}
      {{ t.section_d_detail_water_structure.caste_use }}{{ waterbody.caste_uses|default:t.common.na }}
      {{ t.section_d_detail_water_structure.benefitted_household }}{{ waterbody.households_benefitted|default:t.common.na }}
      {{ t.section_d_detail_water_structure.water_structure_type }}{{ waterbody.structure_type|default:t.common.na }}
      {{ t.section_d_detail_water_structure.usage }}{{ waterbody.usage|default:t.common.na }}
      {{ t.section_d_detail_water_structure.maintenance }}{{ waterbody.need_maintenance|default:t.common.na }}
      {{ t.section_d_detail_water_structure.repair_activities }}{{ waterbody.repair_activities|default:t.common.na }}
      {{ t.section_d_detail_water_structure.latitude }}{{ waterbody.latitude|default:t.common.na }}
      {{ t.section_d_detail_water_structure.longitude }}{{ waterbody.longitude|default:t.common.na }}
      + + {% endfor %} + +
      \ No newline at end of file diff --git a/templates/dpr/section_d_detail_well_info.html b/templates/dpr/section_d_detail_well_info.html new file mode 100644 index 00000000..378d25a4 --- /dev/null +++ b/templates/dpr/section_d_detail_well_info.html @@ -0,0 +1,87 @@ +
      + +

      {{ t.section_d_detail_well.title }}

      + + {% for well in section_d.wells %} + +

      + {{ well.settlement|default:t.common.na }} +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {{ t.section_d_detail_well.mws_id }}{{ well.mws_id|default:t.common.na }}
      {{ t.section_d_detail_well.beneficiary_settlement }}{{ well.settlement|default:t.common.na }}
      {{ t.section_d_detail_well.well_type }}{{ well.well_type|default:t.common.na }}
      {{ t.section_d_detail_well.well_owner }}{{ well.owner|default:t.common.na }}
      {{ t.section_d_detail_well.beneficiary_name }}{{ well.beneficiary_name|default:t.common.na }}
      {{ t.section_d_detail_well.beneficiary_father_name }}{{ well.father_name|default:t.common.na }}
      {{ t.section_d_detail_well.water_availability }}{{ well.water_availability|default:t.common.na }}
      {{ t.section_d_detail_well.households_benefitted }}{{ well.households_benefitted|default:t.common.na }}
      {{ t.section_d_detail_well.caste_use }}{{ well.caste_uses|default:t.common.na }}
      {{ t.section_d_detail_well.well_usage }}{{ well.well_usage|default:t.common.na }}
      {{ t.section_d_detail_well.need_maintenance }}{{ well.need_maintenance|default:t.common.na }}
      {{ t.section_d_detail_well.repair_activities }}{{ well.repair_activities|default:t.common.na }}
      {{ t.section_d_detail_well.latitude }}{{ well.latitude|default:t.common.na }}
      {{ t.section_d_detail_well.longitude }}{{ well.longitude|default:t.common.na }}
      + + {% endfor %} + +
      \ No newline at end of file diff --git a/templates/dpr/section_d_mws_information.html b/templates/dpr/section_d_mws_information.html new file mode 100644 index 00000000..24f14d7a --- /dev/null +++ b/templates/dpr/section_d_mws_information.html @@ -0,0 +1,31 @@ +
      + + + + + + + + + + + + {% for item in section_d.mws %} + + + + + + + + {% endfor %} + + + +
      {{ t.section_d_mws_info.mws_id }}{{ t.section_d_mws_info.lat_lon }}
      + {{ item.mws_id|default:t.common.na }} + + {{ item.centroid|default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_d_water_structure_summary.html b/templates/dpr/section_d_water_structure_summary.html new file mode 100644 index 00000000..f98e2798 --- /dev/null +++ b/templates/dpr/section_d_water_structure_summary.html @@ -0,0 +1,36 @@ +
      + +

      {{ t.section_d_water_structure_summary.title }}

      + + + + + + + + + + + + + + + {% for item in section_d.water_summary %} + + + + + + + + + + + + {% endfor %} + + + +
      {{ t.section_d_water_structure_summary.beneficiary_settlement }}{{ t.section_d_water_structure_summary.water_structure_type }}{{ t.section_d_water_structure_summary.waterbodies_number }}{{ t.section_d_water_structure_summary.household_benefitted }}
      {{ item.settlement|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.count|default:t.common.na }}{{ item.households|default:t.common.na }}
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_d_well_summary.html b/templates/dpr/section_d_well_summary.html new file mode 100644 index 00000000..907ed544 --- /dev/null +++ b/templates/dpr/section_d_well_summary.html @@ -0,0 +1,39 @@ +
      + +

      {{ t.section_d_well_summary.title }}

      + + + + + + + + + + + + + + {% for item in section_d.well_summary %} + + + + + + + + + + {% endfor %} + + + +
      {{ t.section_d_well_summary.beneficiary_settlement }}{{ t.section_d_well_summary.no_of_wells }}{{ t.section_d_well_summary.household_benefitted }}
      + {{ item.settlement|default:t.common.na }} + + {{ item.num_wells|default:t.common.na }} + + {{ item.households|default:t.common.na }} +
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_e_base.html b/templates/dpr/section_e_base.html new file mode 100644 index 00000000..8af3e81f --- /dev/null +++ b/templates/dpr/section_e_base.html @@ -0,0 +1,7 @@ +
      + +

      {{ t.section_e.title }}

      + +

      {{ t.section_e.description }}

      + +
      \ No newline at end of file diff --git a/templates/dpr/section_e_irrigation_structure.html b/templates/dpr/section_e_irrigation_structure.html new file mode 100644 index 00000000..ce811c3f --- /dev/null +++ b/templates/dpr/section_e_irrigation_structure.html @@ -0,0 +1,48 @@ +

      {{ t.section_e.irrigation_structures }}

      + + + + + + + + + + + + + + + + + + + {% for item in section_e.agri %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.irrigation_structure }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
      {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities }}{{ item.latitude }}{{ item.longitude }}
      {{ t.common.na }}
      \ No newline at end of file diff --git a/templates/dpr/section_e_maintenance_work_by_asset_type.html b/templates/dpr/section_e_maintenance_work_by_asset_type.html new file mode 100644 index 00000000..ec7e649f --- /dev/null +++ b/templates/dpr/section_e_maintenance_work_by_asset_type.html @@ -0,0 +1,26 @@ +

      {{ t.section_e.asset_type_heading }}

      + + + + + + + + + + + + + + + + + + + + + + + + +
      {{ t.section_e.asset_type }}
      {{ t.section_e.recharge_structure_asset }}
      {{ t.section_e.irrigation_structure_asset }}
      {{ t.section_e.water_structure_asset }}
      {{ t.section_e.rs_water_structure_asset }}
      \ No newline at end of file diff --git a/templates/dpr/section_e_recharge_structure.html b/templates/dpr/section_e_recharge_structure.html new file mode 100644 index 00000000..0fcb52c4 --- /dev/null +++ b/templates/dpr/section_e_recharge_structure.html @@ -0,0 +1,48 @@ +

      {{ t.section_e.water_recharge_structures }}

      + + + + + + + + + + + + + + + + + + + {% for item in section_e.gw %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.recharge_structure }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
      {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities|default:t.common.na}}{{ item.latitude }}{{ item.longitude }}
      {{ t.common.na }}
      \ No newline at end of file diff --git a/templates/dpr/section_e_rs_swb.html b/templates/dpr/section_e_rs_swb.html new file mode 100644 index 00000000..fb77f219 --- /dev/null +++ b/templates/dpr/section_e_rs_swb.html @@ -0,0 +1,48 @@ +

      {{ t.section_e.remote_sensed_surface_water_structures }}

      + + + + + + + + + + + + + + + + + + + {% for item in section_e.swb_rs %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.type_of_work }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
      {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities | default:t.common.na}}{{ item.latitude }}{{ item.longitude }}
      {{ t.common.na }}
      \ No newline at end of file diff --git a/templates/dpr/section_e_water_structure.html b/templates/dpr/section_e_water_structure.html new file mode 100644 index 00000000..7f8d994b --- /dev/null +++ b/templates/dpr/section_e_water_structure.html @@ -0,0 +1,48 @@ +

      {{ t.section_e.surface_water_structures }}

      + + + + + + + + + + + + + + + + + + + {% for item in section_e.swb %} + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_e.type_of_demand }}{{ t.section_e.beneficiary_settlement }}{{ t.section_e.beneficiary_name }}{{ t.section_e.father_name }}{{ t.section_e.type_of_work }}{{ t.section_e.repair_activities }}{{ t.section_e.latitude }}{{ t.section_e.longitude }}
      {{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.structure_type|default:t.common.na }}{{ item.repair_activities }}{{ item.latitude }}{{ item.longitude }}
      {{ t.common.na }}
      \ No newline at end of file diff --git a/templates/dpr/section_f.html b/templates/dpr/section_f.html new file mode 100644 index 00000000..3305f872 --- /dev/null +++ b/templates/dpr/section_f.html @@ -0,0 +1,64 @@ +
      + +

      + {{ t.section_f.title }} +

      + +

      + {{ t.section_f.description }} +

      + + + + + + + + + + + + + + + + + + + + + {% for item in section_f.works %} + + + + + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_f.serial_no }}{{ t.section_f.work_category }}{{ t.section_f.type_of_demand }}{{ t.section_f.work_demand }}{{ t.section_f.beneficiary_settlement }}{{ t.section_f.beneficiary_name }}{{ t.section_f.gender }}{{ t.section_f.father_name }}{{ t.section_f.latitude }}{{ t.section_f.longitude }}
      {{ forloop.counter }}{{ item.work_category|default:t.common.na }}{{ item.demand_type|default:t.common.na }}{{ item.work_demand|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.gender|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.latitude|default:t.common.na }}{{ item.longitude|default:t.common.na }}
      {{ t.common.na }}
      + +
      \ No newline at end of file diff --git a/templates/dpr/section_g.html b/templates/dpr/section_g.html new file mode 100644 index 00000000..7895c2d6 --- /dev/null +++ b/templates/dpr/section_g.html @@ -0,0 +1,115 @@ + +
      + +

      {{ t.section_g.title }}

      + + +

      {{ t.section_g.livestock_and_fisheries }}

      + + + + + + + + + + + + + + + + + + + + {% for item in section_g.livestock_fisheries %} + + + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_g.livelihood_work }}{{ t.section_g.type_of_demand }}{{ t.section_g.work_demand }}{{ t.section_g.beneficiary_settlement }}{{ t.section_g.beneficiary_name }}{{ t.section_g.gender }}{{ t.section_g.father_name }}{{ t.section_g.latitude }}{{ t.section_g.longitude }}
      {{ item.livelihood_work|default:t.common.na }}{{ item.demand_type|default:t.common.na }}{{ item.work_demand|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.gender|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.latitude|default:t.common.na }}{{ item.longitude|default:t.common.na }}
      {{ t.common.na }}
      + + +

      {{ t.section_g.plantations_and_kitchen_gardens }}

      + + + + + + + + + + + + + + + + + + + + + {% for item in section_g.plantations %} + + + + + + + + + + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + + +
      {{ t.section_g.livelihood_work }}{{ t.section_g.type_of_demand }}{{ t.section_g.beneficiary_settlement }}{{ t.section_g.beneficiary_name }}{{ t.section_g.gender }}{{ t.section_g.father_name }}{{ t.section_g.plantation_crop }}{{ t.section_g.total_acres }}{{ t.section_g.latitude }}{{ t.section_g.longitude }}
      {{ item.livelihood_work|default:t.common.na }}{{ item.demand_type|default:t.common.na }}{{ item.beneficiary_settlement|default:t.common.na }}{{ item.beneficiary_name|default:t.common.na }}{{ item.gender|default:t.common.na }}{{ item.beneficiary_father_name|default:t.common.na }}{{ item.work_demand|default:t.common.na }}{{ item.total_acres|default:t.common.na }}{{ item.latitude|default:t.common.na }}{{ item.longitude|default:t.common.na }}
      {{ t.common.na }}
      + +
      \ No newline at end of file diff --git a/users/signals.py b/users/signals.py index 55d4d1d1..34550fc9 100644 --- a/users/signals.py +++ b/users/signals.py @@ -45,7 +45,7 @@ def send_email_to_org_admin(sender, instance, created, **kwargs):

      Please take the following action:

      1. - Assign user to a project @@ -57,7 +57,7 @@ def send_email_to_org_admin(sender, instance, created, **kwargs): - © 2025 CoRE Stack. All rights reserved. + © 2025 CoRE Stack. All rights reserved. diff --git a/utilities/scripts/tree_health/README.md b/utilities/scripts/tree_health/README.md new file mode 100644 index 00000000..804f110f --- /dev/null +++ b/utilities/scripts/tree_health/README.md @@ -0,0 +1,116 @@ +# Annual Tree Health Layer Generation (Pan-India) + +This guide walks you through the step-by-step process of generating the annual tree health layers for Pan-India. Follow the pipeline carefully to ensure consistency and accuracy across all datasets. + +## Pipeline Overview + +### 1. Download the Data (Colab Script) + +Start by running the Colab download script `IS_DW_sentinel_data_export_new_year.ipynb` to fetch the latest data. This script gathers all the necessary information required to initiate the analysis. + +> **Estimated Runtime:** 3–8 hours to download and export all data to Google Drive, depending on the size of the ACZ. + +> **Note:** Complete this step for all ACZs before moving to the next step. + +--- + +### 2. Predict Results (Colab Script) + +Once the data is ready, use the Colab prediction script `predict_ccd_ch_results.ipynb` to generate prediction outputs. + +The script produces a CSV file containing the predicted values. +m +--- + +### 3. Upload Predictions to GEE (Colab Script) + +Upload the prediction CSV to Google Earth Engine (GEE) using the Colab upload script `uploadAssets.ipynb`. + +--- + +### 4. Convert Feature Collections to Images (GEE Script) + +Run the `fc_to_image.js` script in GEE to convert the uploaded feature collection into an image layer. + +> **Important:** If the target image collection does not already exist (for example, `ccd_2020` or `ch_2020`), create it before proceeding. + +--- + +### 5. Apply Corrections (Colab Script) + +Run the Colab correction script `trees_corrections.ipynb` to make the necessary adjustments to the predictions. + +The script generates a new CSV file containing corrected values. + +> **Important:** This script corrects the data for the current year -1 and current year -2. Therefore, make sure to re-run **Steps 6, 7, and 8** for both years. + +--- + +### 6. Upload Corrected Data to GEE (Colab Script) + +Use the Colab upload corrections script `uploadAssets_correction.ipynb` to upload the correction CSV to GEE. + +--- + +### 7. Convert Corrected Data to Images (GEE Script) + +Run the `fc_to_image_corrections.js` script to convert the corrected data points into image layers. + +> **Important:** Create new image collections if required (for example, `corrections_ccd_2020` or `corrections_ch_2020`). + +> **Note:** Complete this step for all ACZs before moving to the next step. + +--- + +### 8. Run the Modal Change Analysis (GEE Scripts) + +After generating the modal outputs, run the modal change analysis scripts to calculate changes between two years based on the modal values of three consecutive years. + +#### `modal_change_analysis_ccd.js` + +Performs Canopy Cover Density (CCD) change analysis between two years using modal CCD outputs. + +For example, to compute the change between `year1` and `year2`, the script compares: + +- `mode(year1 - 1, year1, year1 + 1)` +- `mode(year2 - 1, year2, year2 + 1)` + +This approach helps reduce year-to-year noise and provides more stable change detection results. + +#### `modal_change_analysis_ch.js` + +Performs Canopy Height (CH) change analysis between two years using modal CH outputs derived from three consecutive years. + +--- + +### 9. Run the Overall Change Analysis (GEE Scripts) + +To generate the overall tree health change between two years, execute the following scripts: + +#### `ccd_change.js` + +Calculates the change in modal Canopy Cover Density (CCD) between the selected years. + +#### `ch_change.js` + +Calculates the change in modal Canopy Height (CH) between the selected years. + +#### `modal_overall_change.js` + +Combines the CCD and CH changes generated by the above scripts to perform the overall tree health change analysis between the selected years. + +--- + +## Workflow Summary + +1. Download data for all ACZs. +2. Generate prediction outputs. +3. Upload predictions to GEE. +4. Convert uploaded feature collections to image layers. +5. Apply corrections to the prediction outputs. +6. Upload corrected data to GEE. +7. Convert corrected feature collections to image layers. +8. Generate modal CCD and CH outputs. +9. Run modal change analysis to calculate CCD and CH changes between years using three-year modal windows. +10. Generate CCD and CH change layers. +11. Run the overall change analysis to combine CCD and CH changes into the final tree health change layer. \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb b/utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb new file mode 100644 index 00000000..adf409ba --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/IS_DW_sentinel_data_export_new_year.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":11689,"status":"ok","timestamp":1775322096195,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"jteYmnZOnFje"},"outputs":[],"source":["import pandas as pd\n","from glob import glob\n","import re\n","import matplotlib.pyplot as plt\n","import json\n","import numpy as np\n","from scipy import stats as st\n","import ee\n","import shapely.geometry\n","from shapely.geometry import Point, Polygon\n","import geopandas as gpd\n","from math import sqrt\n","from shapely import wkt\n","import os\n","import time\n","import geemap"]},{"cell_type":"code","execution_count":20,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":1536,"status":"ok","timestamp":1775323431299,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"5s4VjzimnVwY","outputId":"c4cedd6c-0576-48d5-bfc3-3afc0134bbce"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["ee.Authenticate()\n","ee.Initialize(project='ext-datasets')"]},{"cell_type":"code","execution_count":4,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":44067,"status":"ok","timestamp":1775322195865,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ESTyH3bjXEMp","outputId":"8d2baa0d-9f83-41d5-e5a5-dee64cdc44bb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":18,"status":"ok","timestamp":1775322209346,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"Ktrj5HIGncZt","outputId":"a9205a41-8e79-4fb5-9080-dd14de322179"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["year = 2017"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775322216118,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"VqMB2CcvnfO4","outputId":"a969f5c8-80c1-4854-e1b6-da7d594c8c65"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Choose appropriate ACZ\n","\n","# agroclimatic_zone = \"Eastern Plateau & Hills Region\"\n","agroclimatic_zone = \"Southern Plateau and Hills Region\"\n","# agroclimatic_zone = \"East Coast Plains & Hills Region\"\n","# agroclimatic_zone = \"Western Plateau and Hills Region\"\n","# agroclimatic_zone = \"Central Plateau & Hills Region\"\n","# agroclimatic_zone = \"Lower Gangetic Plain Region\"\n","# agroclimatic_zone = \"Middle Gangetic Plain Region\"\n","# agroclimatic_zone = \"Eastern Himalayan Region\"\n","#agroclimatic_zone = \"Western Himalayan Region\"\n","# agroclimatic_zone = \"Upper Gangetic Plain Region\"\n","# agroclimatic_zone = \"Trans Gangetic Plain Region\"\n","# agroclimatic_zone = \"West Coast Plains & Ghat Region\" ## # Model not available\n","# agroclimatic_zone = \"Gujarat Plains & Hills Region\" ## # Model not available\n","# agroclimatic_zone = \"Western Dry Region\" ## # Model not available"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":4,"status":"ok","timestamp":1775322219096,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"c7iMtt-CniYQ","outputId":"13d24f8f-2ab2-4149-a3a6-a587cb238acb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775322221411,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"3npVzImWnlH0","outputId":"d443d000-eda0-4c52-9946-944002d4562e"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["india_boundary = ee.FeatureCollection(\"projects/ee-mtpictd/assets/harsh/Agroclimatic_regions\")\n","agrozone = india_boundary.filter(ee.Filter.eq('regionname', agroclimatic_zone)).geometry()\n","india_district_boundary = ee.FeatureCollection(\"projects/ee-indiasat/assets/india_district_boundaries\")"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775322226455,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"-kXRm3DJnnlU","outputId":"fcd76c20-eb44-43d9-e278-824978c6fd92"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["s1_bands = ['VV', 'VH', 'angle']\n","s2_bands = ['B1','B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B10', 'B11','B12']"]},{"cell_type":"code","execution_count":21,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775323441748,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"HyTMlhh7nqOD","outputId":"9399a761-e6af-4cb1-83ed-b61ee81ae395"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["START_DATE = {year-2: {'kharif': str(year-2)+'-07-01', 'rabi': str(year-2)+'-11-01', 'zaid': str(year-1)+'-03-01'},\n"," year-1: {'kharif': str(year-1)+'-07-01', 'rabi': str(year-1)+'-11-01', 'zaid': str(year)+'-03-01'},\n"," year: {'kharif': str(year)+'-07-01', 'rabi': str(year)+'-11-01', 'zaid': str(year+1)+'-03-01'}}\n","\n","END_DATE = {year-2: {'kharif': str(year-2)+'-10-31', 'rabi': str(year-1)+'-02-28', 'zaid': str(year-1)+'-06-30'},\n"," year-1: {'kharif': str(year-1)+'-10-31', 'rabi': str(year)+'-02-28', 'zaid': str(year)+'-06-30'},\n"," year: {'kharif': str(year)+'-10-31', 'rabi': str(year+1)+'-02-28', 'zaid': str(year+1)+'-06-30'}}"]},{"cell_type":"code","execution_count":22,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":73},"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775323443285,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"c8Me4AlontBi","outputId":"918d5170-1440-41f1-8236-578730308d7f"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["{2015: {'kharif': '2015-07-01', 'rabi': '2015-11-01', 'zaid': '2016-03-01'}, 2016: {'kharif': '2016-07-01', 'rabi': '2016-11-01', 'zaid': '2017-03-01'}, 2017: {'kharif': '2017-07-01', 'rabi': '2017-11-01', 'zaid': '2018-03-01'}}\n","{2015: {'kharif': '2015-10-31', 'rabi': '2016-02-28', 'zaid': '2016-06-30'}, 2016: {'kharif': '2016-10-31', 'rabi': '2017-02-28', 'zaid': '2017-06-30'}, 2017: {'kharif': '2017-10-31', 'rabi': '2018-02-28', 'zaid': '2018-06-30'}}\n"]}],"source":["print(START_DATE)\n","print(END_DATE)"]},{"cell_type":"code","execution_count":12,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":10,"status":"ok","timestamp":1775322231788,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9zuvGfH4nwHB","outputId":"6eba4155-87dc-4a8b-ecdc-416edc167bd8"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Will take an AOI geometry as input and return the 10km x 10km grids in it as a list\n","def createGrids(aoi):\n"," proj = ee.Projection('EPSG:4326')\n"," gridSize = 10000\n"," grid = aoi.coveringGrid(proj, gridSize)\n"," features = grid.getInfo()['features']\n"," return features"]},{"cell_type":"code","execution_count":13,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":46,"status":"ok","timestamp":1775322233759,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"cHhG3vDgnzmb","outputId":"ed3905c8-8e3b-4179-c065-1b597e8d7750"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def s2_mask(image):\n"," \"\"\"\n"," Getting a cloud-free Sentinel-2 imagery.\n"," \"\"\"\n"," quality_band = image.select('QA60')\n"," # Using the bit mask for clouds (bit 10) and cirrus clouds (bit 11) respectively.\n"," cloudmask = 1 << 10\n"," cirrusmask = 1 << 11\n"," # Both flags should be set to zero, indicating clear conditions of sky.\n"," mask = quality_band.bitwiseAnd(cloudmask).eq(0) and (quality_band.bitwiseAnd(cirrusmask).eq(0))\n"," return image.updateMask(mask)\n","\n","def get_s2_image(aoi, start_date, end_date):\n"," # s2_bands_season = [band + '_med_' + season for band in s2_bands]\n"," return ee.ImageCollection('COPERNICUS/S2_HARMONIZED').filterDate(\n"," start_date , end_date).filterBounds(aoi).filter(\n"," ee.Filter.lt(\"CLOUDY_PIXEL_PERCENTAGE\", 20)).sort('CLOUD_COVER').map(\n"," s2_mask).select(s2_bands).median().divide(10000).clip(aoi)\n","\n","def get_s1_image(aoi, start_date, end_date):\n"," # s1_bands_season = [band + '_' + season for band in s1_bands]\n"," return ee.ImageCollection('COPERNICUS/S1_GRD').filterDate(start_date , end_date).filterBounds(aoi).filter(\n"," ee.Filter.listContains('transmitterReceiverPolarisation', 'VV')).filter(\n"," ee.Filter.listContains('transmitterReceiverPolarisation', 'VH')).filter(\n"," ee.Filter.eq('instrumentMode', 'IW')).select(s1_bands).median().clip(aoi)"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775322235773,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"KD4RzXrTn2CK","outputId":"e402cf2f-b589-4e45-dc83-92db5bf7d468"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def save_data_csv(data_points, img_name, district, year):\n"," print(\"Saving data for\", district, year)\n"," new_img_name = img_name.replace('&', 'and')\n"," new_img_name = new_img_name.replace('(', '')\n"," new_img_name = new_img_name.replace(')', '')\n"," task = ee.batch.Export.table.toDrive(\n"," collection = data_points,\n"," description = new_img_name,\n"," folder = f\"{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{year}\", # f\"{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{district}_{year}\",\n"," fileNamePrefix = new_img_name,\n"," fileFormat = 'CSV'\n"," )\n"," task.start()\n"," print(\"Task Started\",task.status())\n"," return task"]},{"cell_type":"code","execution_count":23,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":53},"executionInfo":{"elapsed":19,"status":"ok","timestamp":1775323448485,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"X0eH_LS-n4j0","outputId":"c1686218-9aa1-4297-9d43-84fe8b1bc7f6"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["1\n","['Y.S.R.']\n"]}],"source":["df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = ['Y.S.R.']#list(df['Name'])\n","print(len(dist_list))\n","print(dist_list)"]},{"cell_type":"code","execution_count":24,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775323451067,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"pPZhyHc1n6zG","outputId":"fdc3a9aa-5b8c-4ebc-f944-9be9efa0e8bc"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["['2015', '2016', '2017']\n"]}],"source":["years = [str(int(year)-2), str(int(year)-1), str(year)]\n","year_0 = years[0]\n","year_1 = years[1]\n","year_2 = years[2]\n","year_suffix = {year_0: year_0[-2:], year_1: year_1[-2:], year_2: year_2[-2:]}\n","print(years)"]},{"cell_type":"code","execution_count":17,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775322268138,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"QWVDbWyDn9NZ","outputId":"38eafe48-4777-45d0-9fdf-726318b7c2e8"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def get_dw_tree_cover(aoi, start_date, end_date, scale = 25):\n"," tree_cover_dw = ee.ImageCollection(\"GOOGLE/DYNAMICWORLD/V1\").filterDate(start_date, end_date) \\\n"," .filterBounds(aoi).select('label').mode().clip(aoi)\n"," return tree_cover_dw.updateMask(tree_cover_dw.eq(1)).reproject(crs='EPSG:4326', scale=scale)\n","\n","def get_is_tree_cover(aoi, curr_year, scale = 25):\n"," curr_year = int(curr_year)\n"," indiasat_asset = f\"projects/corestack-datasets/assets/datasets/LULC_v3_river_basin/pan_india_lulc_v3_{curr_year}_{curr_year+1}\"\n"," lulc_image = ee.Image(indiasat_asset).select(\"predicted_label\").clip(aoi)\n"," return lulc_image.updateMask(lulc_image.eq(6)).reproject(crs='EPSG:4326', scale=scale)\n","\n","def get_tree_cover(aoi, curr_year, scale = 25):\n"," curr_year = int(curr_year)\n"," start_date = ee.Date(f'{curr_year}-07-01')\n"," end_date = ee.Date(f'{curr_year+1}-06-30')\n"," print(\"curr_year\", curr_year)\n"," tree_cover_is = get_is_tree_cover(aoi, curr_year, scale) if curr_year > 2016 else None\n"," tree_cover_dw = get_dw_tree_cover(aoi, start_date, end_date, scale)\n"," if tree_cover_is:\n"," tree_cover = tree_cover_is.mask().Or(tree_cover_dw.mask())\n"," else:\n"," print(\"=================Only DW\")\n"," tree_cover = tree_cover_dw.mask()\n"," # tree_cover = tree_cover_is.mask().Or(tree_cover_dw.mask()) if tree_cover_is else tree_cover_dw.mask()\n"," tree_cover = tree_cover.updateMask(tree_cover)\n"," return tree_cover.reproject(crs='EPSG:4326', scale=scale)"]},{"cell_type":"markdown","metadata":{"id":"LJEejVAPnvGR"},"source":["# Combined Grid"]},{"cell_type":"code","execution_count":25,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"EhNLoTWcN3Ws","executionInfo":{"status":"ok","timestamp":1775323714437,"user_tz":-330,"elapsed":249336,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"cff2690a-c434-4c34-9ec2-af6aea4c75f7"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Year 2017, District 0: Y.S.R., grids: 201\n","curr_year 2017\n","Grid 0\n","Grid 1\n","Grid 2\n","Grid 3\n","Grid 4\n","Grid 5\n","Grid 6\n","Grid 7\n","Grid 8\n","Grid 9\n","Grid 10\n","Grid 11\n","Grid 12\n","Grid 13\n","Grid 14\n","Grid 15\n","Grid 16\n","Grid 17\n","Grid 18\n","Grid 19\n","Grid 20\n","Grid 21\n","Grid 22\n","Grid 23\n","Grid 24\n","Grid 25\n","Grid 26\n","Grid 27\n","Grid 28\n","Grid 29\n","Grid 30\n","Grid 31\n","Grid 32\n","Grid 33\n","Grid 34\n","Grid 35\n","Grid 36\n","Grid 37\n","Grid 38\n","Grid 39\n","Grid 40\n","Grid 41\n","Grid 42\n","Grid 43\n","Grid 44\n","Grid 45\n","Grid 46\n","Grid 47\n","Grid 48\n","Grid 49\n","Grid 50\n","Grid 51\n","Grid 52\n","Grid 53\n","Grid 54\n","Grid 55\n","Grid 56\n","Grid 57\n","Grid 58\n","Grid 59\n","Grid 60\n","Grid 61\n","Grid 62\n","Grid 63\n","Grid 64\n","Grid 65\n","Grid 66\n","Grid 67\n","Grid 68\n","Grid 69\n","Grid 70\n","Grid 71\n","Grid 72\n","Grid 73\n","Grid 74\n","Grid 75\n","Grid 76\n","Grid 77\n","Grid 78\n","Grid 79\n","Grid 80\n","Grid 81\n","Grid 82\n","Grid 83\n","Grid 84\n","Grid 85\n","Grid 86\n","Grid 87\n","Grid 88\n","Grid 89\n","Grid 90\n","Grid 91\n","Grid 92\n","Grid 93\n","Grid 94\n","Grid 95\n","Grid 96\n","Grid 97\n","Grid 98\n","Grid 99\n","Grid 100\n","Grid 101\n","Grid 102\n","Grid 103\n","Grid 104\n","Grid 105\n","Grid 106\n","Grid 107\n","Grid 108\n","Grid 109\n","Grid 110\n","Grid 111\n","Grid 112\n","Grid 113\n","Grid 114\n","Grid 115\n","Grid 116\n","Grid 117\n","Grid 118\n","Grid 119\n","Grid 120\n","Grid 121\n","Grid 122\n","Grid 123\n","Grid 124\n","Grid 125\n","Grid 126\n","Grid 127\n","Grid 128\n","Grid 129\n","Grid 130\n","Grid 131\n","Grid 132\n","Grid 133\n","Grid 134\n","Grid 135\n","Grid 136\n","Grid 137\n","Grid 138\n","Grid 139\n","Grid 140\n","Grid 141\n","Grid 142\n","Grid 143\n","Grid 144\n","Grid 145\n","Grid 146\n","Grid 147\n","Grid 148\n","Grid 149\n","Grid 150\n","Grid 151\n","Grid 152\n","Grid 153\n","Grid 154\n","Grid 155\n","Grid 156\n","Grid 157\n","Grid 158\n","Grid 159\n","Grid 160\n","Grid 161\n","Grid 162\n","Grid 163\n","Grid 164\n","Grid 165\n","Grid 166\n","Grid 167\n","Grid 168\n","Grid 169\n","Grid 170\n","Grid 171\n","Grid 172\n","Grid 173\n","Grid 174\n","Grid 175\n","Grid 176\n","Grid 177\n","Grid 178\n","Grid 179\n","Grid 180\n","Grid 181\n","Grid 182\n","Grid 183\n","Grid 184\n","Grid 185\n","Grid 186\n","Grid 187\n","Grid 188\n","Grid 189\n","Grid 190\n","Grid 191\n","Grid 192\n","Grid 193\n","Grid 194\n","Grid 195\n","Grid 196\n","Grid 197\n","Grid 198\n","Grid 199\n","Grid 200\n","[, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ]\n","Saving data for Y.S.R. 2017\n","Task Started {'state': 'READY', 'description': 'Y.S.R._2017_all_grids', 'priority': 100, 'creation_timestamp_ms': 1775323714046, 'update_timestamp_ms': 1775323714046, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K7HMZ3QXN7RJ4OVN5RGYFIAY', 'name': 'projects/ext-datasets/operations/K7HMZ3QXN7RJ4OVN5RGYFIAY'}\n","249.2729194164276\n"]}],"source":["# Combined tiles and optimized\n","import sys\n","sys.setrecursionlimit(6000)\n","for curr_year in ['2017']:#years:\n","\n"," total_time = 0\n","\n"," dist_cnt = 0\n"," for district in dist_list:\n","\n"," start_time = time.time()\n","\n"," district_aoi = india_district_boundary.filter(ee.Filter.eq('Name', district)).geometry()\n"," district_aoi = district_aoi.intersection(agrozone)\n"," features = createGrids(district_aoi)\n","\n"," print(f'Year {curr_year}, District {dist_cnt}: {district}, grids: {len(features)}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{curr_year}/'\n"," os.makedirs(path, exist_ok=True)\n","\n"," # df = pd.DataFrame()\n"," # df['grid_num'] = list(range(len(features)))\n"," valid_grid_indices = []\n"," tree_points_list = []\n","\n"," # Precompute tree cover for the whole district\n"," tree_cover_district = get_tree_cover(district_aoi, curr_year, scale=25)\n","\n"," # Precompute S1 and S2 images for the whole district for each season\n"," start_date = START_DATE[int(curr_year)]\n"," end_date = END_DATE[int(curr_year)]\n"," s1_images = {}\n"," s2_images = {}\n"," for season in ['kharif', 'rabi', 'zaid']:\n"," try:\n"," s1_images[season] = get_s1_image(district_aoi, start_date[season], end_date[season]).updateMask(tree_cover_district)\n"," except Exception as exp:\n"," print(f\"S1 Error occured: {season}\", exp)\n"," s1_images[season] = None\n"," try:\n"," s2_images[season] = get_s2_image(district_aoi, start_date[season], end_date[season]).updateMask(tree_cover_district)\n"," except Exception as exp:\n"," print(f\"S2 Error occured: {season}\", exp)\n"," s2_images[season] = None\n","\n"," # List to hold all sample points\n"," all_sample_points = []\n","\n"," i = 0\n"," for feature in features:\n"," print(f'Grid {i}')\n","\n"," coord = feature['geometry']['coordinates'][0]\n"," aoi = ee.Geometry.Polygon(coord)\n"," aoi = aoi.intersection(district_aoi)\n"," # Get the coordinates of the geometry\n"," coordinates = aoi.coordinates()\n"," # Check if coordinates list is empty (i.e. geometry is empty)\n"," is_empty = coordinates.length().eq(0)\n","\n"," # print('Is geometry empty?', is_empty.getInfo())\n"," if not is_empty.getInfo():\n"," valid_grid_indices.append(i) # Only add if valid\n"," img_name = district + \"_\" + str(i) + \"_\" + str(curr_year)\n","\n"," # Clip tree cover to grid\n"," tree_cover = tree_cover_district.clip(aoi)\n","\n"," # Compose image for all seasons using precomputed images\n"," image = None\n"," if s1_images['kharif'] is not None:\n"," image = s1_images['kharif'].clip(aoi)\n"," if s2_images['kharif'] is not None and image is not None:\n"," s2_data = s2_images['kharif'].clip(aoi)\n"," image = image.addBands(s2_data).select(s1_bands + s2_bands)\n"," image = image.rename([band + '_kharif' for band in s1_bands + s2_bands])\n","\n"," for season in ['rabi', 'zaid']:\n"," s1_data = s1_images[season]\n"," s2_data = s2_images[season]\n"," if s1_data is not None:\n"," s1_data = s1_data.clip(aoi)\n"," if s2_data is not None and s1_data is not None:\n"," s2_data = s2_data.clip(aoi)\n"," image_merged = s1_data.addBands(s2_data).select(s1_bands + s2_bands)\n"," image_merged = image_merged.rename([band + '_' + season for band in s1_bands + s2_bands])\n"," image = image.addBands(image_merged) if image is not None else image_merged\n","\n"," # Sample points only if tree cover exists\n"," sample_tree_cover = tree_cover.sample(\n"," region=aoi,\n"," scale=25,\n"," factor=1,\n"," tileScale=10,\n"," geometries=True\n"," )\n"," try:\n"," tree_points = sample_tree_cover.size().getInfo()\n"," except:\n"," tree_points = 0\n"," tree_points_list.append(tree_points)\n","\n"," # if tree_points > 0 and image is not None:\n"," sample_points = image.sample(\n"," region=aoi,\n"," scale=25,\n"," factor=1,\n"," tileScale=10,\n"," geometries=True\n"," )\n"," all_sample_points.append(sample_points)\n","\n"," i += 1\n"," print(all_sample_points)\n"," # Merge all sample points into a single FeatureCollection\n"," if all_sample_points:\n"," merged_sample_points = all_sample_points[0]\n"," for sp in all_sample_points[1:]:\n"," merged_sample_points = merged_sample_points.merge(sp)\n","\n"," # Export the merged FeatureCollection\n"," try:\n"," img_name = district + \"_\" + str(curr_year) + \"_all_grids\"\n"," task = save_data_csv(merged_sample_points, img_name, district, curr_year)\n"," prev_task = task\n"," except Exception as e:\n"," print(e)\n"," while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," continue\n"," task = save_data_csv(merged_sample_points, img_name, path, curr_year)\n"," prev_task = task\n","\n"," df = pd.DataFrame()\n"," df['grid_num'] = valid_grid_indices\n"," df['tree_points'] = tree_points_list\n"," df.to_csv(path + 'tree_cover_points.csv', index=False)\n","\n"," dist_cnt += 1\n"," end_time = time.time()\n","\n"," total_time += (end_time - start_time)\n"," print(total_time)\n","\n"," # print(\"Waiting for last task to be completed...\")\n"," # while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," # continue\n"," # print(\"Last task completed!\")\n","\n"," # total_time += (time.time() - end_time)\n"," # print(\"Total Time Taken:\", total_time)"]},{"cell_type":"markdown","metadata":{"id":"snxgyeoRno-K"},"source":["# Separate Grid (Older version - Do not run this)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"P1WY_Cb7oAgz","outputId":"a7c02335-532e-4b97-820f-6361ad46ffb9"},"outputs":[{"data":{"text/html":["\n"," \n"," "],"text/plain":[""]},"metadata":{},"output_type":"display_data"},{"name":"stdout","output_type":"stream","text":["\u001b[1;30;43mStreaming output truncated to the last 5000 lines.\u001b[0m\n","Task Started {'state': 'READY', 'description': 'Haora_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762652761544, 'update_timestamp_ms': 1762652761544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JPA4UPIX3U5VCIKQ2SWCQECH', 'name': 'projects/ext-datasets/operations/JPA4UPIX3U5VCIKQ2SWCQECH'}\n","Grid 16\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762652766812, 'update_timestamp_ms': 1762652766812, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EAMYVBQXNMMEY4FOMWM43BNC', 'name': 'projects/ext-datasets/operations/EAMYVBQXNMMEY4FOMWM43BNC'}\n","Grid 17\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762652773310, 'update_timestamp_ms': 1762652773310, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XJ6UW3VLPSK622S7N3GTPBDE', 'name': 'projects/ext-datasets/operations/XJ6UW3VLPSK622S7N3GTPBDE'}\n","Grid 18\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762652779471, 'update_timestamp_ms': 1762652779471, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UQF6ZXFYNJIJDDAVOJAADO4Q', 'name': 'projects/ext-datasets/operations/UQF6ZXFYNJIJDDAVOJAADO4Q'}\n","Grid 19\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762652788203, 'update_timestamp_ms': 1762652788203, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V6CMFXQIIPPHMDY7G7RSBHXO', 'name': 'projects/ext-datasets/operations/V6CMFXQIIPPHMDY7G7RSBHXO'}\n","Grid 20\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762652795968, 'update_timestamp_ms': 1762652795968, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IEIRR7HN3564PNRHXV3B7LBO', 'name': 'projects/ext-datasets/operations/IEIRR7HN3564PNRHXV3B7LBO'}\n","Grid 21\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762652804392, 'update_timestamp_ms': 1762652804392, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OJ627QLSFNOELKQGFC4XWVXJ', 'name': 'projects/ext-datasets/operations/OJ627QLSFNOELKQGFC4XWVXJ'}\n","Grid 22\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762652812461, 'update_timestamp_ms': 1762652812461, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HVAIQPOGNWFJXL4P2GKTW6CP', 'name': 'projects/ext-datasets/operations/HVAIQPOGNWFJXL4P2GKTW6CP'}\n","Grid 23\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762652821455, 'update_timestamp_ms': 1762652821455, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7Z6YDDABUKVAF6WCB57HEL67', 'name': 'projects/ext-datasets/operations/7Z6YDDABUKVAF6WCB57HEL67'}\n","Grid 24\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762652829407, 'update_timestamp_ms': 1762652829407, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OMGDWCBDMII3R7MTIP5A46NM', 'name': 'projects/ext-datasets/operations/OMGDWCBDMII3R7MTIP5A46NM'}\n","Grid 25\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762652837188, 'update_timestamp_ms': 1762652837188, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZGIKQF5DUYPD5IC6PHGBDRZ4', 'name': 'projects/ext-datasets/operations/ZGIKQF5DUYPD5IC6PHGBDRZ4'}\n","Grid 26\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762652841386, 'update_timestamp_ms': 1762652841386, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P6D6LB352XNF2GB6FND34XKL', 'name': 'projects/ext-datasets/operations/P6D6LB352XNF2GB6FND34XKL'}\n","Grid 27\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762652847414, 'update_timestamp_ms': 1762652847414, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XU2OBEAFI2SP46DPZFU2Q724', 'name': 'projects/ext-datasets/operations/XU2OBEAFI2SP46DPZFU2Q724'}\n","Grid 28\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762652853805, 'update_timestamp_ms': 1762652853805, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R26FGY4WBDEUAU5ISWRZ6XQH', 'name': 'projects/ext-datasets/operations/R26FGY4WBDEUAU5ISWRZ6XQH'}\n","Grid 29\n","curr_year 2017\n","Saving data for Haora 2017\n","Task Started {'state': 'READY', 'description': 'Haora_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762652861699, 'update_timestamp_ms': 1762652861699, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N33BZBXVFCNZQAF6T2NMZDY7', 'name': 'projects/ext-datasets/operations/N33BZBXVFCNZQAF6T2NMZDY7'}\n","3315.372728586197\n","Year 2017, District 17: Hugli, grids: 55\n","Grid 0\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762652873437, 'update_timestamp_ms': 1762652873437, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '67WLQUZFYL335CGJ7LLBYJLI', 'name': 'projects/ext-datasets/operations/67WLQUZFYL335CGJ7LLBYJLI'}\n","Grid 1\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762652879133, 'update_timestamp_ms': 1762652879133, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HW36MCMET6I4Y5UNARSAIH3V', 'name': 'projects/ext-datasets/operations/HW36MCMET6I4Y5UNARSAIH3V'}\n","Grid 2\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762652886436, 'update_timestamp_ms': 1762652886436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IWZBIZESVKRK7M56GH3YEWY6', 'name': 'projects/ext-datasets/operations/IWZBIZESVKRK7M56GH3YEWY6'}\n","Grid 3\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762652894869, 'update_timestamp_ms': 1762652894869, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLIX56WRXKWEHW44N55XWRKV', 'name': 'projects/ext-datasets/operations/JLIX56WRXKWEHW44N55XWRKV'}\n","Grid 4\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762652902789, 'update_timestamp_ms': 1762652902789, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MM5SISFPENEPZOVVAFMJUVU5', 'name': 'projects/ext-datasets/operations/MM5SISFPENEPZOVVAFMJUVU5'}\n","Grid 5\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762652910673, 'update_timestamp_ms': 1762652910673, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KQIK775U7IYSBN5LZJYJD5IS', 'name': 'projects/ext-datasets/operations/KQIK775U7IYSBN5LZJYJD5IS'}\n","Grid 6\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762652917226, 'update_timestamp_ms': 1762652917226, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCVEK3WW2CGFFIV7FKQHXJNN', 'name': 'projects/ext-datasets/operations/DCVEK3WW2CGFFIV7FKQHXJNN'}\n","Grid 7\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762652924007, 'update_timestamp_ms': 1762652924007, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YEYMUF2WW3TGZHOQRWRAFT54', 'name': 'projects/ext-datasets/operations/YEYMUF2WW3TGZHOQRWRAFT54'}\n","Grid 8\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762652931314, 'update_timestamp_ms': 1762652931314, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BDZGPIYOXGCCFOWADOVDSNHC', 'name': 'projects/ext-datasets/operations/BDZGPIYOXGCCFOWADOVDSNHC'}\n","Grid 9\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762652937745, 'update_timestamp_ms': 1762652937745, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WUG6PNYVFDMWPJSYSVORFPFD', 'name': 'projects/ext-datasets/operations/WUG6PNYVFDMWPJSYSVORFPFD'}\n","Grid 10\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762652945240, 'update_timestamp_ms': 1762652945240, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7MHCLX37JCBBJX7QP2MRKO2X', 'name': 'projects/ext-datasets/operations/7MHCLX37JCBBJX7QP2MRKO2X'}\n","Grid 11\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762652952393, 'update_timestamp_ms': 1762652952393, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RCXTUBPSVBBZTZDUILO7SCOE', 'name': 'projects/ext-datasets/operations/RCXTUBPSVBBZTZDUILO7SCOE'}\n","Grid 12\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762652959856, 'update_timestamp_ms': 1762652959856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UU4US5LIPZLFY3HCI3XRKRSG', 'name': 'projects/ext-datasets/operations/UU4US5LIPZLFY3HCI3XRKRSG'}\n","Grid 13\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762652967985, 'update_timestamp_ms': 1762652967985, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CEHVDOTQNQOWS7Z36OTLHHIB', 'name': 'projects/ext-datasets/operations/CEHVDOTQNQOWS7Z36OTLHHIB'}\n","Grid 14\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762652975340, 'update_timestamp_ms': 1762652975340, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ND7ZK45V2GPRHW572HNNOUNP', 'name': 'projects/ext-datasets/operations/ND7ZK45V2GPRHW572HNNOUNP'}\n","Grid 15\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762652983473, 'update_timestamp_ms': 1762652983473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N4DYL3R2WRHR6VK7LGYGAHAE', 'name': 'projects/ext-datasets/operations/N4DYL3R2WRHR6VK7LGYGAHAE'}\n","Grid 16\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762652990247, 'update_timestamp_ms': 1762652990247, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ILZGJKW7WCP6GQPD46H65GKL', 'name': 'projects/ext-datasets/operations/ILZGJKW7WCP6GQPD46H65GKL'}\n","Grid 17\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762652996402, 'update_timestamp_ms': 1762652996402, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S3O4QNBG6YIUR7IVRGMUA2UI', 'name': 'projects/ext-datasets/operations/S3O4QNBG6YIUR7IVRGMUA2UI'}\n","Grid 18\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762653004523, 'update_timestamp_ms': 1762653004523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7WEN5MKBMTCYRO73BRTBNMLD', 'name': 'projects/ext-datasets/operations/7WEN5MKBMTCYRO73BRTBNMLD'}\n","Grid 19\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762653010473, 'update_timestamp_ms': 1762653010473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OFXUFGKTKWLLC2U3ACD4V4PE', 'name': 'projects/ext-datasets/operations/OFXUFGKTKWLLC2U3ACD4V4PE'}\n","Grid 20\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762653019182, 'update_timestamp_ms': 1762653019182, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VUAUUFFV2SAVVOEXQLALAWXR', 'name': 'projects/ext-datasets/operations/VUAUUFFV2SAVVOEXQLALAWXR'}\n","Grid 21\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762653024959, 'update_timestamp_ms': 1762653024959, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IH2FDQWZWDCAGW6JRZTZP7QI', 'name': 'projects/ext-datasets/operations/IH2FDQWZWDCAGW6JRZTZP7QI'}\n","Grid 22\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762653032654, 'update_timestamp_ms': 1762653032654, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HE5ZQHMEEPRCZZ2QCXUN752R', 'name': 'projects/ext-datasets/operations/HE5ZQHMEEPRCZZ2QCXUN752R'}\n","Grid 23\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762653039615, 'update_timestamp_ms': 1762653039615, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHSIU2LEF6WYMG6VW3ZCDMIX', 'name': 'projects/ext-datasets/operations/YHSIU2LEF6WYMG6VW3ZCDMIX'}\n","Grid 24\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762653046808, 'update_timestamp_ms': 1762653046808, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KVZWQHJVKSAWJ3AHZMNXIJOV', 'name': 'projects/ext-datasets/operations/KVZWQHJVKSAWJ3AHZMNXIJOV'}\n","Grid 25\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762653052031, 'update_timestamp_ms': 1762653052031, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SVCS6CUVAMGD3BIXWJ2O4DFP', 'name': 'projects/ext-datasets/operations/SVCS6CUVAMGD3BIXWJ2O4DFP'}\n","Grid 26\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762653060075, 'update_timestamp_ms': 1762653060075, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZHOWVC7Q2MNYEQWLZBH7RWTQ', 'name': 'projects/ext-datasets/operations/ZHOWVC7Q2MNYEQWLZBH7RWTQ'}\n","Grid 27\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762653068196, 'update_timestamp_ms': 1762653068196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LVE2GEXSXRTJGXVWGNFWRXBZ', 'name': 'projects/ext-datasets/operations/LVE2GEXSXRTJGXVWGNFWRXBZ'}\n","Grid 28\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762653075759, 'update_timestamp_ms': 1762653075759, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PQUFVZ6HJLZ3KKTMJVX7MYEI', 'name': 'projects/ext-datasets/operations/PQUFVZ6HJLZ3KKTMJVX7MYEI'}\n","Grid 29\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762653083680, 'update_timestamp_ms': 1762653083680, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MWXGACU43LGFK4XXLAPTLJXX', 'name': 'projects/ext-datasets/operations/MWXGACU43LGFK4XXLAPTLJXX'}\n","Grid 30\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762653087724, 'update_timestamp_ms': 1762653087724, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FYBTXGKGIUVM2BP3FFGZZCNO', 'name': 'projects/ext-datasets/operations/FYBTXGKGIUVM2BP3FFGZZCNO'}\n","Grid 31\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762653094613, 'update_timestamp_ms': 1762653094613, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IWA6Q2SLUATTOPA5Z3QCXMNP', 'name': 'projects/ext-datasets/operations/IWA6Q2SLUATTOPA5Z3QCXMNP'}\n","Grid 32\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762653098875, 'update_timestamp_ms': 1762653098875, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZAH4CPIQF53TIOOR25RQY3UE', 'name': 'projects/ext-datasets/operations/ZAH4CPIQF53TIOOR25RQY3UE'}\n","Grid 33\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762653106035, 'update_timestamp_ms': 1762653106035, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EU4I3HM7SH64GU5IVPGJQBFM', 'name': 'projects/ext-datasets/operations/EU4I3HM7SH64GU5IVPGJQBFM'}\n","Grid 34\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762653113073, 'update_timestamp_ms': 1762653113073, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PWUBHH3Q7PPTLFBFLKRYSYUC', 'name': 'projects/ext-datasets/operations/PWUBHH3Q7PPTLFBFLKRYSYUC'}\n","Grid 35\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762653120645, 'update_timestamp_ms': 1762653120645, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H7T2JZOKUCFFV2NW2RIJ56II', 'name': 'projects/ext-datasets/operations/H7T2JZOKUCFFV2NW2RIJ56II'}\n","Grid 36\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762653126866, 'update_timestamp_ms': 1762653126866, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VNCXOPJMS62XSDOAGVHP4O73', 'name': 'projects/ext-datasets/operations/VNCXOPJMS62XSDOAGVHP4O73'}\n","Grid 37\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762653133620, 'update_timestamp_ms': 1762653133620, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UDONOS7G2HIGSIVAC2GP4ME7', 'name': 'projects/ext-datasets/operations/UDONOS7G2HIGSIVAC2GP4ME7'}\n","Grid 38\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762653138488, 'update_timestamp_ms': 1762653138488, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G5ELJ4O53QEHVQY2Y7WNKXNM', 'name': 'projects/ext-datasets/operations/G5ELJ4O53QEHVQY2Y7WNKXNM'}\n","Grid 39\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762653144956, 'update_timestamp_ms': 1762653144956, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V665E4OPIGTCWB5FO4K43HNC', 'name': 'projects/ext-datasets/operations/V665E4OPIGTCWB5FO4K43HNC'}\n","Grid 40\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762653151869, 'update_timestamp_ms': 1762653151869, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ATIMLT6LUYHI6PNG4EVNNSNG', 'name': 'projects/ext-datasets/operations/ATIMLT6LUYHI6PNG4EVNNSNG'}\n","Grid 41\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762653159135, 'update_timestamp_ms': 1762653159135, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PFM3F32LPGML42REWDXLJ5YQ', 'name': 'projects/ext-datasets/operations/PFM3F32LPGML42REWDXLJ5YQ'}\n","Grid 42\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762653163523, 'update_timestamp_ms': 1762653163523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OOX27I5RUBHMDS3ZKCZKDHJM', 'name': 'projects/ext-datasets/operations/OOX27I5RUBHMDS3ZKCZKDHJM'}\n","Grid 43\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762653167557, 'update_timestamp_ms': 1762653167557, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LD4RYM7I2ZWAREDALCSB5SVH', 'name': 'projects/ext-datasets/operations/LD4RYM7I2ZWAREDALCSB5SVH'}\n","Grid 44\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762653171076, 'update_timestamp_ms': 1762653171076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UBAJS5UUTC7LA7XQWDPYGEDV', 'name': 'projects/ext-datasets/operations/UBAJS5UUTC7LA7XQWDPYGEDV'}\n","Grid 45\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762653177390, 'update_timestamp_ms': 1762653177390, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BAXAKZQKWVWRIMXKNU5NGIGI', 'name': 'projects/ext-datasets/operations/BAXAKZQKWVWRIMXKNU5NGIGI'}\n","Grid 46\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762653187101, 'update_timestamp_ms': 1762653187101, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62JU7POKQPOCQ6ECPZBRZOJK', 'name': 'projects/ext-datasets/operations/62JU7POKQPOCQ6ECPZBRZOJK'}\n","Grid 47\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762653195208, 'update_timestamp_ms': 1762653195208, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BBM5LULHPY57D26QENGBFZEA', 'name': 'projects/ext-datasets/operations/BBM5LULHPY57D26QENGBFZEA'}\n","Grid 48\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762653202094, 'update_timestamp_ms': 1762653202094, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OTJUT7NPE4XH3B7X227CQC2E', 'name': 'projects/ext-datasets/operations/OTJUT7NPE4XH3B7X227CQC2E'}\n","Grid 49\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762653207724, 'update_timestamp_ms': 1762653207724, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EJCTVJWVA35JNT7WY7MS2PEZ', 'name': 'projects/ext-datasets/operations/EJCTVJWVA35JNT7WY7MS2PEZ'}\n","Grid 50\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762653215082, 'update_timestamp_ms': 1762653215082, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5Q476RLEGWQK22ZGZJVBQNIJ', 'name': 'projects/ext-datasets/operations/5Q476RLEGWQK22ZGZJVBQNIJ'}\n","Grid 51\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762653222507, 'update_timestamp_ms': 1762653222507, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FEX2WGIRLJCE7IBH74CDGLSO', 'name': 'projects/ext-datasets/operations/FEX2WGIRLJCE7IBH74CDGLSO'}\n","Grid 52\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762653231333, 'update_timestamp_ms': 1762653231333, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AVYNP6SEDV26DVLT6JEPVBQF', 'name': 'projects/ext-datasets/operations/AVYNP6SEDV26DVLT6JEPVBQF'}\n","Grid 53\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762653239636, 'update_timestamp_ms': 1762653239636, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZEQM4XXGVBBLIRHWMUBSV6Q5', 'name': 'projects/ext-datasets/operations/ZEQM4XXGVBBLIRHWMUBSV6Q5'}\n","Grid 54\n","curr_year 2017\n","Saving data for Hugli 2017\n","Task Started {'state': 'READY', 'description': 'Hugli_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762653243926, 'update_timestamp_ms': 1762653243926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHQUJTEO4DJPKTFAOIR4VWXA', 'name': 'projects/ext-datasets/operations/YHQUJTEO4DJPKTFAOIR4VWXA'}\n","3697.579036951065\n","Year 2017, District 18: Kolkata, grids: 6\n","Grid 0\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762653255739, 'update_timestamp_ms': 1762653255739, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UXUXC4D6QFYHE4VSN3ZUDSNP', 'name': 'projects/ext-datasets/operations/UXUXC4D6QFYHE4VSN3ZUDSNP'}\n","Grid 1\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762653263107, 'update_timestamp_ms': 1762653263107, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X4ISR3KUCH25N42LTY2QEIMB', 'name': 'projects/ext-datasets/operations/X4ISR3KUCH25N42LTY2QEIMB'}\n","Grid 2\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762653270940, 'update_timestamp_ms': 1762653270940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JIXZH7CTRXOXPECWCUEA25CB', 'name': 'projects/ext-datasets/operations/JIXZH7CTRXOXPECWCUEA25CB'}\n","Grid 3\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762653278573, 'update_timestamp_ms': 1762653278573, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RIHCWKL5W7PAS2F5ZERX2FLQ', 'name': 'projects/ext-datasets/operations/RIHCWKL5W7PAS2F5ZERX2FLQ'}\n","Grid 4\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762653285117, 'update_timestamp_ms': 1762653285117, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2MO4V3JQ5U5ZKUVWZWKVOOLC', 'name': 'projects/ext-datasets/operations/2MO4V3JQ5U5ZKUVWZWKVOOLC'}\n","Grid 5\n","curr_year 2017\n","Saving data for Kolkata 2017\n","Task Started {'state': 'READY', 'description': 'Kolkata_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762653292659, 'update_timestamp_ms': 1762653292659, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MEWVKX5M5UWFLXBD4N3KSMUS', 'name': 'projects/ext-datasets/operations/MEWVKX5M5UWFLXBD4N3KSMUS'}\n","3746.3626384735107\n","Year 2017, District 19: Maldah, grids: 61\n","Grid 0\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762653307026, 'update_timestamp_ms': 1762653307026, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QAPIPPIW66JXE4SHXRYGGATA', 'name': 'projects/ext-datasets/operations/QAPIPPIW66JXE4SHXRYGGATA'}\n","Grid 1\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762653314549, 'update_timestamp_ms': 1762653314549, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'II3RFO4GMMJQ7VVIITOZTRQ6', 'name': 'projects/ext-datasets/operations/II3RFO4GMMJQ7VVIITOZTRQ6'}\n","Grid 2\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762653322843, 'update_timestamp_ms': 1762653322843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GQPNIDVKNVFTOL76HN3PWZA5', 'name': 'projects/ext-datasets/operations/GQPNIDVKNVFTOL76HN3PWZA5'}\n","Grid 3\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762653330638, 'update_timestamp_ms': 1762653330638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MQG4FRBMMJL5FE6AU3Y3M35W', 'name': 'projects/ext-datasets/operations/MQG4FRBMMJL5FE6AU3Y3M35W'}\n","Grid 4\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762653337061, 'update_timestamp_ms': 1762653337061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RBIFNYSNTWMQIVHH7LF4RU5H', 'name': 'projects/ext-datasets/operations/RBIFNYSNTWMQIVHH7LF4RU5H'}\n","Grid 5\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762653344098, 'update_timestamp_ms': 1762653344098, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLYW2F2V5EDDC4WTNIZ7JC6Q', 'name': 'projects/ext-datasets/operations/XLYW2F2V5EDDC4WTNIZ7JC6Q'}\n","Grid 6\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762653352420, 'update_timestamp_ms': 1762653352420, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EUQV5UNADPFXYD65CQ64UN56', 'name': 'projects/ext-datasets/operations/EUQV5UNADPFXYD65CQ64UN56'}\n","Grid 7\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762653360453, 'update_timestamp_ms': 1762653360453, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CYJQMBWOLJUOWMBIX3IGGO4V', 'name': 'projects/ext-datasets/operations/CYJQMBWOLJUOWMBIX3IGGO4V'}\n","Grid 8\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762653369302, 'update_timestamp_ms': 1762653369302, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJXY66HWTEPRUWXHBVHCKHSU', 'name': 'projects/ext-datasets/operations/HJXY66HWTEPRUWXHBVHCKHSU'}\n","Grid 9\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762653376196, 'update_timestamp_ms': 1762653376196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KRCQLFABFHAXYCA27UAOMGQ3', 'name': 'projects/ext-datasets/operations/KRCQLFABFHAXYCA27UAOMGQ3'}\n","Grid 10\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762653383697, 'update_timestamp_ms': 1762653383697, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3FDGAB5FBCMXHBHYWDDZI62T', 'name': 'projects/ext-datasets/operations/3FDGAB5FBCMXHBHYWDDZI62T'}\n","Grid 11\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762653391609, 'update_timestamp_ms': 1762653391609, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PTT5B6F4TFKMLHHGA5KGVSHQ', 'name': 'projects/ext-datasets/operations/PTT5B6F4TFKMLHHGA5KGVSHQ'}\n","Grid 12\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762653401352, 'update_timestamp_ms': 1762653401352, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FWLOBKFWETXJ27PFYAYMBUI5', 'name': 'projects/ext-datasets/operations/FWLOBKFWETXJ27PFYAYMBUI5'}\n","Grid 13\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762653419468, 'update_timestamp_ms': 1762653419468, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '44LAF56HFWQPWWXW5CWUJ7IP', 'name': 'projects/ext-datasets/operations/44LAF56HFWQPWWXW5CWUJ7IP'}\n","Grid 14\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762653427261, 'update_timestamp_ms': 1762653427261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SZR5YDJO2YUZK3W55ACIBWTW', 'name': 'projects/ext-datasets/operations/SZR5YDJO2YUZK3W55ACIBWTW'}\n","Grid 15\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762653435984, 'update_timestamp_ms': 1762653435984, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DL5MAMXB4L3LLQXQ3HIHD6AI', 'name': 'projects/ext-datasets/operations/DL5MAMXB4L3LLQXQ3HIHD6AI'}\n","Grid 16\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762653443583, 'update_timestamp_ms': 1762653443583, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W4UMT7X5ZJ5L7OQXPK6FB4SD', 'name': 'projects/ext-datasets/operations/W4UMT7X5ZJ5L7OQXPK6FB4SD'}\n","Grid 17\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762653451043, 'update_timestamp_ms': 1762653451043, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DTPOZN2ZC5H65D7RYPIXZWH5', 'name': 'projects/ext-datasets/operations/DTPOZN2ZC5H65D7RYPIXZWH5'}\n","Grid 18\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762653458890, 'update_timestamp_ms': 1762653458890, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FVB5AVADD3JEATFLCFBBRKBT', 'name': 'projects/ext-datasets/operations/FVB5AVADD3JEATFLCFBBRKBT'}\n","Grid 19\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762653466409, 'update_timestamp_ms': 1762653466409, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WHDQ3GKZGMJS5MNJ6Y3H2JO3', 'name': 'projects/ext-datasets/operations/WHDQ3GKZGMJS5MNJ6Y3H2JO3'}\n","Grid 20\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762653474177, 'update_timestamp_ms': 1762653474177, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NXGV26SJ2FZHQVGAL3QG5PDJ', 'name': 'projects/ext-datasets/operations/NXGV26SJ2FZHQVGAL3QG5PDJ'}\n","Grid 21\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762653481511, 'update_timestamp_ms': 1762653481511, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QC4ETEY5YN4JWEJEZQH4NFLS', 'name': 'projects/ext-datasets/operations/QC4ETEY5YN4JWEJEZQH4NFLS'}\n","Grid 22\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762653488560, 'update_timestamp_ms': 1762653488560, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XKGFAZTVNOV7EY4OJRVIDZZV', 'name': 'projects/ext-datasets/operations/XKGFAZTVNOV7EY4OJRVIDZZV'}\n","Grid 23\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762653494949, 'update_timestamp_ms': 1762653494949, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JBWZZDVCJHEQDKTYUGPVH24N', 'name': 'projects/ext-datasets/operations/JBWZZDVCJHEQDKTYUGPVH24N'}\n","Grid 24\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762653501686, 'update_timestamp_ms': 1762653501686, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FZ27X6HFBSZWXMWLZ7W6PZSH', 'name': 'projects/ext-datasets/operations/FZ27X6HFBSZWXMWLZ7W6PZSH'}\n","Grid 25\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762653509420, 'update_timestamp_ms': 1762653509420, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QAMZUMFTIL6QO2CGJVMRXEDX', 'name': 'projects/ext-datasets/operations/QAMZUMFTIL6QO2CGJVMRXEDX'}\n","Grid 26\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762653515776, 'update_timestamp_ms': 1762653515776, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MAUX2TR736ZX4DRGTLLNIP7Y', 'name': 'projects/ext-datasets/operations/MAUX2TR736ZX4DRGTLLNIP7Y'}\n","Grid 27\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762653522065, 'update_timestamp_ms': 1762653522065, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QDPDGUGHELDOTRPE5CQP4XZN', 'name': 'projects/ext-datasets/operations/QDPDGUGHELDOTRPE5CQP4XZN'}\n","Grid 28\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762653530057, 'update_timestamp_ms': 1762653530057, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WZSXJF2ZQZUU4RODFFUFMM75', 'name': 'projects/ext-datasets/operations/WZSXJF2ZQZUU4RODFFUFMM75'}\n","Grid 29\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762653537639, 'update_timestamp_ms': 1762653537639, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZPNMLXGNND5IJKGKCJD6TOP', 'name': 'projects/ext-datasets/operations/KZPNMLXGNND5IJKGKCJD6TOP'}\n","Grid 30\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762653544926, 'update_timestamp_ms': 1762653544926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7DC5KUBNX2FF3SMR72W34ZHG', 'name': 'projects/ext-datasets/operations/7DC5KUBNX2FF3SMR72W34ZHG'}\n","Grid 31\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762653552527, 'update_timestamp_ms': 1762653552527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BLD33OJTOWGHCXTGVZXZIO2P', 'name': 'projects/ext-datasets/operations/BLD33OJTOWGHCXTGVZXZIO2P'}\n","Grid 32\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762653560390, 'update_timestamp_ms': 1762653560390, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B252XMEYH5O2ZA7L4SJWHFPX', 'name': 'projects/ext-datasets/operations/B252XMEYH5O2ZA7L4SJWHFPX'}\n","Grid 33\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762653563974, 'update_timestamp_ms': 1762653563974, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L377EDJ5RJO5JIGX32CUBHWC', 'name': 'projects/ext-datasets/operations/L377EDJ5RJO5JIGX32CUBHWC'}\n","Grid 34\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762653571157, 'update_timestamp_ms': 1762653571157, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VXAL2ERHA3YZGYJAJI6FJM4L', 'name': 'projects/ext-datasets/operations/VXAL2ERHA3YZGYJAJI6FJM4L'}\n","Grid 35\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762653579961, 'update_timestamp_ms': 1762653579961, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMP3FFN72TNUTT4J6Q7SRAPN', 'name': 'projects/ext-datasets/operations/BMP3FFN72TNUTT4J6Q7SRAPN'}\n","Grid 36\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762653586557, 'update_timestamp_ms': 1762653586557, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MR2ZM6CM5POJM2LCSE7NGTSY', 'name': 'projects/ext-datasets/operations/MR2ZM6CM5POJM2LCSE7NGTSY'}\n","Grid 37\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762653595043, 'update_timestamp_ms': 1762653595043, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZEAJ3NM2CDE5MNBAV5RXP7X', 'name': 'projects/ext-datasets/operations/XZEAJ3NM2CDE5MNBAV5RXP7X'}\n","Grid 38\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762653603189, 'update_timestamp_ms': 1762653603189, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SVQGTVZYFMJX56OO4LUGU6FZ', 'name': 'projects/ext-datasets/operations/SVQGTVZYFMJX56OO4LUGU6FZ'}\n","Grid 39\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762653610089, 'update_timestamp_ms': 1762653610089, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6R77UCAJUFCC3FTNEIWOV5KU', 'name': 'projects/ext-datasets/operations/6R77UCAJUFCC3FTNEIWOV5KU'}\n","Grid 40\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762653616912, 'update_timestamp_ms': 1762653616912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AHGO4Z6KJD2MADBE4HJRZXLO', 'name': 'projects/ext-datasets/operations/AHGO4Z6KJD2MADBE4HJRZXLO'}\n","Grid 41\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762653624910, 'update_timestamp_ms': 1762653624910, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4L6DLRUNVBFADPTVT4RWRAY3', 'name': 'projects/ext-datasets/operations/4L6DLRUNVBFADPTVT4RWRAY3'}\n","Grid 42\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762653631524, 'update_timestamp_ms': 1762653631524, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGWJG63I2NHNLEHSDS6BNXA6', 'name': 'projects/ext-datasets/operations/QGWJG63I2NHNLEHSDS6BNXA6'}\n","Grid 43\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762653639518, 'update_timestamp_ms': 1762653639518, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XE3NBLV25Z2I4Q3SQCP4MGAH', 'name': 'projects/ext-datasets/operations/XE3NBLV25Z2I4Q3SQCP4MGAH'}\n","Grid 44\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762653646429, 'update_timestamp_ms': 1762653646429, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HRAXBS7AIGEPBX4HP3ROTLVN', 'name': 'projects/ext-datasets/operations/HRAXBS7AIGEPBX4HP3ROTLVN'}\n","Grid 45\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762653654205, 'update_timestamp_ms': 1762653654205, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5NA5YFUVXGDNTNRCEZG7DIK2', 'name': 'projects/ext-datasets/operations/5NA5YFUVXGDNTNRCEZG7DIK2'}\n","Grid 46\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762653660439, 'update_timestamp_ms': 1762653660439, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CWFOGQOWCFIAKBBNAIVK5WM5', 'name': 'projects/ext-datasets/operations/CWFOGQOWCFIAKBBNAIVK5WM5'}\n","Grid 47\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762653669479, 'update_timestamp_ms': 1762653669479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2H22QVZXDN3LNHSUAC4UJCQ4', 'name': 'projects/ext-datasets/operations/2H22QVZXDN3LNHSUAC4UJCQ4'}\n","Grid 48\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762653673328, 'update_timestamp_ms': 1762653673328, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EMSW7CJWFFWQ6APGYTPECA2P', 'name': 'projects/ext-datasets/operations/EMSW7CJWFFWQ6APGYTPECA2P'}\n","Grid 49\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762653680761, 'update_timestamp_ms': 1762653680761, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KB6GXPQTM66LGRZUEVNBYHQY', 'name': 'projects/ext-datasets/operations/KB6GXPQTM66LGRZUEVNBYHQY'}\n","Grid 50\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762653687933, 'update_timestamp_ms': 1762653687933, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BIYFLVYMNTLUEBSHJVGBNJ7M', 'name': 'projects/ext-datasets/operations/BIYFLVYMNTLUEBSHJVGBNJ7M'}\n","Grid 51\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762653696412, 'update_timestamp_ms': 1762653696412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OHND56M62MWDUBE7JZMQFMHZ', 'name': 'projects/ext-datasets/operations/OHND56M62MWDUBE7JZMQFMHZ'}\n","Grid 52\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762653705129, 'update_timestamp_ms': 1762653705129, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AEPLV4U3722KMODMFNVOHHK6', 'name': 'projects/ext-datasets/operations/AEPLV4U3722KMODMFNVOHHK6'}\n","Grid 53\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762653713348, 'update_timestamp_ms': 1762653713348, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPCO5ZYVPYU3U4NPRNSBPLJL', 'name': 'projects/ext-datasets/operations/PPCO5ZYVPYU3U4NPRNSBPLJL'}\n","Grid 54\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762653721065, 'update_timestamp_ms': 1762653721065, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DGHL73DRSIR2MVY4CXWZK4YO', 'name': 'projects/ext-datasets/operations/DGHL73DRSIR2MVY4CXWZK4YO'}\n","Grid 55\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762653728737, 'update_timestamp_ms': 1762653728737, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DUECP6FOWWNQEQIQIFVN33DN', 'name': 'projects/ext-datasets/operations/DUECP6FOWWNQEQIQIFVN33DN'}\n","Grid 56\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762653736532, 'update_timestamp_ms': 1762653736532, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4I6S5YXZZT7EFL2DFDQ5AGF4', 'name': 'projects/ext-datasets/operations/4I6S5YXZZT7EFL2DFDQ5AGF4'}\n","Grid 57\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762653744879, 'update_timestamp_ms': 1762653744879, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TIQWF4ZWIORMW7BTWRGXPOBA', 'name': 'projects/ext-datasets/operations/TIQWF4ZWIORMW7BTWRGXPOBA'}\n","Grid 58\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762653750799, 'update_timestamp_ms': 1762653750799, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GNY6NKPSF53UNE6JEJEFLKQE', 'name': 'projects/ext-datasets/operations/GNY6NKPSF53UNE6JEJEFLKQE'}\n","Grid 59\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762653757995, 'update_timestamp_ms': 1762653757995, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BRH4LJTFLB5NIFYX23Z2D24K', 'name': 'projects/ext-datasets/operations/BRH4LJTFLB5NIFYX23Z2D24K'}\n","Grid 60\n","curr_year 2017\n","Saving data for Maldah 2017\n","Task Started {'state': 'READY', 'description': 'Maldah_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762653763814, 'update_timestamp_ms': 1762653763814, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCUJBEOPU66DWIDE63GS7TFK', 'name': 'projects/ext-datasets/operations/DCUJBEOPU66DWIDE63GS7TFK'}\n","4217.510969877243\n","Year 2017, District 20: Murshidabad, grids: 87\n","Grid 0\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762653777841, 'update_timestamp_ms': 1762653777841, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NX2VXZOCFQGCIMGOUVTSJTF4', 'name': 'projects/ext-datasets/operations/NX2VXZOCFQGCIMGOUVTSJTF4'}\n","Grid 1\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762653784734, 'update_timestamp_ms': 1762653784734, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZUAMH7MMA6G4MHBCMJKVFJ32', 'name': 'projects/ext-datasets/operations/ZUAMH7MMA6G4MHBCMJKVFJ32'}\n","Grid 2\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762653789468, 'update_timestamp_ms': 1762653789468, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EWKFB3TZCYETDAPV6JJOTKSO', 'name': 'projects/ext-datasets/operations/EWKFB3TZCYETDAPV6JJOTKSO'}\n","Grid 3\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762653797795, 'update_timestamp_ms': 1762653797795, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RNSUJECPKWVXFW5ZIMHJC3B7', 'name': 'projects/ext-datasets/operations/RNSUJECPKWVXFW5ZIMHJC3B7'}\n","Grid 4\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762653805593, 'update_timestamp_ms': 1762653805593, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YRKBCIIRDJNL2AA735MXNV3J', 'name': 'projects/ext-datasets/operations/YRKBCIIRDJNL2AA735MXNV3J'}\n","Grid 5\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762653811334, 'update_timestamp_ms': 1762653811334, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H4KZJ6RHG3I73I2VG3PGQP5Y', 'name': 'projects/ext-datasets/operations/H4KZJ6RHG3I73I2VG3PGQP5Y'}\n","Grid 6\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762653818350, 'update_timestamp_ms': 1762653818350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2AWVITKPMQ4CKI7PX7ISXONF', 'name': 'projects/ext-datasets/operations/2AWVITKPMQ4CKI7PX7ISXONF'}\n","Grid 7\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762653822840, 'update_timestamp_ms': 1762653822840, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BZ5VSADHRL67CKQ4NLWJTT2Q', 'name': 'projects/ext-datasets/operations/BZ5VSADHRL67CKQ4NLWJTT2Q'}\n","Grid 8\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762653830584, 'update_timestamp_ms': 1762653830584, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HFHFUCAEXLHFHUIP6YE754JP', 'name': 'projects/ext-datasets/operations/HFHFUCAEXLHFHUIP6YE754JP'}\n","Grid 9\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762653838966, 'update_timestamp_ms': 1762653838966, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6Y2G4VKF3KTUDO3YYLYMLEID', 'name': 'projects/ext-datasets/operations/6Y2G4VKF3KTUDO3YYLYMLEID'}\n","Grid 10\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762653846040, 'update_timestamp_ms': 1762653846040, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6NSOWXNHMOWNSV6NCTWXP5CB', 'name': 'projects/ext-datasets/operations/6NSOWXNHMOWNSV6NCTWXP5CB'}\n","Grid 11\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762653852887, 'update_timestamp_ms': 1762653852887, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5FDF4VYLPS5WXUJVDZFCKBYZ', 'name': 'projects/ext-datasets/operations/5FDF4VYLPS5WXUJVDZFCKBYZ'}\n","Grid 12\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762653860246, 'update_timestamp_ms': 1762653860246, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BF5RASHVLHR6PFDXNYEGFMCW', 'name': 'projects/ext-datasets/operations/BF5RASHVLHR6PFDXNYEGFMCW'}\n","Grid 13\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762653866589, 'update_timestamp_ms': 1762653866589, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E2CKWLS4IVVQ6MBWTFH3C3K4', 'name': 'projects/ext-datasets/operations/E2CKWLS4IVVQ6MBWTFH3C3K4'}\n","Grid 14\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762653873324, 'update_timestamp_ms': 1762653873324, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '46IQBJ7Q5WF4JA76HEXULARX', 'name': 'projects/ext-datasets/operations/46IQBJ7Q5WF4JA76HEXULARX'}\n","Grid 15\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762653881176, 'update_timestamp_ms': 1762653881176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NQJKDXO4OVFAK7MXPFEDZH6', 'name': 'projects/ext-datasets/operations/4NQJKDXO4OVFAK7MXPFEDZH6'}\n","Grid 16\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762653888440, 'update_timestamp_ms': 1762653888440, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QU43U6BDPA4N35BDT5NJJIXS', 'name': 'projects/ext-datasets/operations/QU43U6BDPA4N35BDT5NJJIXS'}\n","Grid 17\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762653895140, 'update_timestamp_ms': 1762653895140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XKLNC5QV3WSY4CFMWVAEMAIN', 'name': 'projects/ext-datasets/operations/XKLNC5QV3WSY4CFMWVAEMAIN'}\n","Grid 18\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762653904788, 'update_timestamp_ms': 1762653904788, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GBJHBSZ2S4ELOUHGGSIUDMG7', 'name': 'projects/ext-datasets/operations/GBJHBSZ2S4ELOUHGGSIUDMG7'}\n","Grid 19\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762653912798, 'update_timestamp_ms': 1762653912798, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KKB6K7JJR5NW33DZRDFX3YK4', 'name': 'projects/ext-datasets/operations/KKB6K7JJR5NW33DZRDFX3YK4'}\n","Grid 20\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762653919534, 'update_timestamp_ms': 1762653919534, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UJ4UD5WWKANSIE2AWNTGMU4Q', 'name': 'projects/ext-datasets/operations/UJ4UD5WWKANSIE2AWNTGMU4Q'}\n","Grid 21\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762653925878, 'update_timestamp_ms': 1762653925878, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ET77UW3Y3THD7NJM52CSYJNO', 'name': 'projects/ext-datasets/operations/ET77UW3Y3THD7NJM52CSYJNO'}\n","Grid 22\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762653932951, 'update_timestamp_ms': 1762653932951, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2MRIBK3DGIVLB7LEI3IDII5W', 'name': 'projects/ext-datasets/operations/2MRIBK3DGIVLB7LEI3IDII5W'}\n","Grid 23\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762653943326, 'update_timestamp_ms': 1762653943326, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RH42F7CGTX5HHIXR22M3DXW4', 'name': 'projects/ext-datasets/operations/RH42F7CGTX5HHIXR22M3DXW4'}\n","Grid 24\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762653949452, 'update_timestamp_ms': 1762653949452, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NBXQWQYYKWZO7REI3E2KFCY2', 'name': 'projects/ext-datasets/operations/NBXQWQYYKWZO7REI3E2KFCY2'}\n","Grid 25\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762653957880, 'update_timestamp_ms': 1762653957880, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3HLL5UWJBBN4SNVIXAVSB45J', 'name': 'projects/ext-datasets/operations/3HLL5UWJBBN4SNVIXAVSB45J'}\n","Grid 26\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762653964804, 'update_timestamp_ms': 1762653964804, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPPJZCYRFS74YB2KC72AAF4O', 'name': 'projects/ext-datasets/operations/PPPJZCYRFS74YB2KC72AAF4O'}\n","Grid 27\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762653971742, 'update_timestamp_ms': 1762653971742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KRIKZXHKRXA6A4RF3C5ZZMME', 'name': 'projects/ext-datasets/operations/KRIKZXHKRXA6A4RF3C5ZZMME'}\n","Grid 28\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762653979906, 'update_timestamp_ms': 1762653979906, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LBDOREIVQQ735JGAHEMFGRAU', 'name': 'projects/ext-datasets/operations/LBDOREIVQQ735JGAHEMFGRAU'}\n","Grid 29\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762653988463, 'update_timestamp_ms': 1762653988463, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SN6G6M2C55NZ352BVJAQ35A4', 'name': 'projects/ext-datasets/operations/SN6G6M2C55NZ352BVJAQ35A4'}\n","Grid 30\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762653996378, 'update_timestamp_ms': 1762653996378, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QA5MAZT6HTJH5RQPNADA4BUK', 'name': 'projects/ext-datasets/operations/QA5MAZT6HTJH5RQPNADA4BUK'}\n","Grid 31\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762654001857, 'update_timestamp_ms': 1762654001857, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZ6HHRDQMKOYQHIYLMQNBGKR', 'name': 'projects/ext-datasets/operations/AZ6HHRDQMKOYQHIYLMQNBGKR'}\n","Grid 32\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762654009445, 'update_timestamp_ms': 1762654009445, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BEQN5GMLUCYMRSUUHIPODHYB', 'name': 'projects/ext-datasets/operations/BEQN5GMLUCYMRSUUHIPODHYB'}\n","Grid 33\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762654016924, 'update_timestamp_ms': 1762654016924, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '44HVPDN62JQH7BUOFR2VQ6LN', 'name': 'projects/ext-datasets/operations/44HVPDN62JQH7BUOFR2VQ6LN'}\n","Grid 34\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762654023800, 'update_timestamp_ms': 1762654023800, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VWZG7RILSLQ4AJFQRK2S3WSS', 'name': 'projects/ext-datasets/operations/VWZG7RILSLQ4AJFQRK2S3WSS'}\n","Grid 35\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762654032471, 'update_timestamp_ms': 1762654032471, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KUBX4FOSYCWCTCI75R7OABNH', 'name': 'projects/ext-datasets/operations/KUBX4FOSYCWCTCI75R7OABNH'}\n","Grid 36\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762654040811, 'update_timestamp_ms': 1762654040811, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KB7BCNNPE6GIJW72DTNL4RZQ', 'name': 'projects/ext-datasets/operations/KB7BCNNPE6GIJW72DTNL4RZQ'}\n","Grid 37\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762654049211, 'update_timestamp_ms': 1762654049211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLPHSI6XVO56J7PKYQQWM2DS', 'name': 'projects/ext-datasets/operations/JLPHSI6XVO56J7PKYQQWM2DS'}\n","Grid 38\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762654057262, 'update_timestamp_ms': 1762654057262, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKLC4T64M27G46OKHYBTJPZE', 'name': 'projects/ext-datasets/operations/SKLC4T64M27G46OKHYBTJPZE'}\n","Grid 39\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762654066731, 'update_timestamp_ms': 1762654066731, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FFARSCE33IKE74VD5CXXY7KZ', 'name': 'projects/ext-datasets/operations/FFARSCE33IKE74VD5CXXY7KZ'}\n","Grid 40\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762654071677, 'update_timestamp_ms': 1762654071677, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BVJUW6FK3OAJLPY544MSEXKZ', 'name': 'projects/ext-datasets/operations/BVJUW6FK3OAJLPY544MSEXKZ'}\n","Grid 41\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762654076894, 'update_timestamp_ms': 1762654076894, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QLFPYEPBTWNWVMFHBKEB7VPJ', 'name': 'projects/ext-datasets/operations/QLFPYEPBTWNWVMFHBKEB7VPJ'}\n","Grid 42\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762654084155, 'update_timestamp_ms': 1762654084155, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z6HSU344JNLLONL4TAWFXB7E', 'name': 'projects/ext-datasets/operations/Z6HSU344JNLLONL4TAWFXB7E'}\n","Grid 43\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762654096054, 'update_timestamp_ms': 1762654096054, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4HJ6CZEXP2ZWMHBM57ER3RD7', 'name': 'projects/ext-datasets/operations/4HJ6CZEXP2ZWMHBM57ER3RD7'}\n","Grid 44\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762654103872, 'update_timestamp_ms': 1762654103872, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R6HZAE6XRQWNPOQUZTJXLBDY', 'name': 'projects/ext-datasets/operations/R6HZAE6XRQWNPOQUZTJXLBDY'}\n","Grid 45\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762654108933, 'update_timestamp_ms': 1762654108933, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SSPWCJS7V3HRF2HPA652YD4T', 'name': 'projects/ext-datasets/operations/SSPWCJS7V3HRF2HPA652YD4T'}\n","Grid 46\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762654116877, 'update_timestamp_ms': 1762654116877, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EG2PSYE6HTAWGVIJSPH6GVAH', 'name': 'projects/ext-datasets/operations/EG2PSYE6HTAWGVIJSPH6GVAH'}\n","Grid 47\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762654123631, 'update_timestamp_ms': 1762654123631, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VGRICQZE2AKI33AUXFTY7LJL', 'name': 'projects/ext-datasets/operations/VGRICQZE2AKI33AUXFTY7LJL'}\n","Grid 48\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762654133474, 'update_timestamp_ms': 1762654133474, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37MGLXZAP4AEDSGNHR4YTFPK', 'name': 'projects/ext-datasets/operations/37MGLXZAP4AEDSGNHR4YTFPK'}\n","Grid 49\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762654141171, 'update_timestamp_ms': 1762654141171, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XCWB546AUSPANV7JYRNXDRA6', 'name': 'projects/ext-datasets/operations/XCWB546AUSPANV7JYRNXDRA6'}\n","Grid 50\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762654145765, 'update_timestamp_ms': 1762654145765, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2GCUUDHIUD342CLSQHQCOOJE', 'name': 'projects/ext-datasets/operations/2GCUUDHIUD342CLSQHQCOOJE'}\n","Grid 51\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762654154438, 'update_timestamp_ms': 1762654154438, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZEKHWK5DQ62KX4SYDI76W3QV', 'name': 'projects/ext-datasets/operations/ZEKHWK5DQ62KX4SYDI76W3QV'}\n","Grid 52\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762654160861, 'update_timestamp_ms': 1762654160861, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4VAWOFVC5P5PRRD6VVTS3H73', 'name': 'projects/ext-datasets/operations/4VAWOFVC5P5PRRD6VVTS3H73'}\n","Grid 53\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762654168851, 'update_timestamp_ms': 1762654168851, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LOKEALXX57SWTCFETEBPNSIX', 'name': 'projects/ext-datasets/operations/LOKEALXX57SWTCFETEBPNSIX'}\n","Grid 54\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762654176454, 'update_timestamp_ms': 1762654176454, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OGYQ23YWNQI7R2KYVYJ7VL52', 'name': 'projects/ext-datasets/operations/OGYQ23YWNQI7R2KYVYJ7VL52'}\n","Grid 55\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762654183923, 'update_timestamp_ms': 1762654183923, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UYJ4Z7K25V4NX7VILLH2UD5Y', 'name': 'projects/ext-datasets/operations/UYJ4Z7K25V4NX7VILLH2UD5Y'}\n","Grid 56\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762654191408, 'update_timestamp_ms': 1762654191408, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JOCJYMG3UWIIVPI4A6OB37JJ', 'name': 'projects/ext-datasets/operations/JOCJYMG3UWIIVPI4A6OB37JJ'}\n","Grid 57\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762654194986, 'update_timestamp_ms': 1762654194986, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SWCFXQQCMDTVOKHGU2FJNTAQ', 'name': 'projects/ext-datasets/operations/SWCFXQQCMDTVOKHGU2FJNTAQ'}\n","Grid 58\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762654202860, 'update_timestamp_ms': 1762654202860, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UVZ5XDC26KR5UIYJPBZRJEHA', 'name': 'projects/ext-datasets/operations/UVZ5XDC26KR5UIYJPBZRJEHA'}\n","Grid 59\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762654206594, 'update_timestamp_ms': 1762654206594, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WVIMTAFGVPTPLMHC3CQBITNI', 'name': 'projects/ext-datasets/operations/WVIMTAFGVPTPLMHC3CQBITNI'}\n","Grid 60\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762654213512, 'update_timestamp_ms': 1762654213512, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YUVX5V4UVUIULAQRELZGWCCZ', 'name': 'projects/ext-datasets/operations/YUVX5V4UVUIULAQRELZGWCCZ'}\n","Grid 61\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762654221501, 'update_timestamp_ms': 1762654221501, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ININU7K5RYMHTU45UCPHJZIE', 'name': 'projects/ext-datasets/operations/ININU7K5RYMHTU45UCPHJZIE'}\n","Grid 62\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762654229227, 'update_timestamp_ms': 1762654229227, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4XB3RDH2RLZT4O3N6PFVNF6I', 'name': 'projects/ext-datasets/operations/4XB3RDH2RLZT4O3N6PFVNF6I'}\n","Grid 63\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762654234955, 'update_timestamp_ms': 1762654234955, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VXQYA4V7ULZKEIKMHPMXRGVE', 'name': 'projects/ext-datasets/operations/VXQYA4V7ULZKEIKMHPMXRGVE'}\n","Grid 64\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762654242660, 'update_timestamp_ms': 1762654242660, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TS33FE4ICXVIA25FBCHQ2QEB', 'name': 'projects/ext-datasets/operations/TS33FE4ICXVIA25FBCHQ2QEB'}\n","Grid 65\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762654251350, 'update_timestamp_ms': 1762654251350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QIESXKNY7CRZCN5QX34XK6N7', 'name': 'projects/ext-datasets/operations/QIESXKNY7CRZCN5QX34XK6N7'}\n","Grid 66\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762654259090, 'update_timestamp_ms': 1762654259090, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G2ZNNHIMPD2ZOL6UWBB2QSE4', 'name': 'projects/ext-datasets/operations/G2ZNNHIMPD2ZOL6UWBB2QSE4'}\n","Grid 67\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762654266099, 'update_timestamp_ms': 1762654266099, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZHT2OMTNF43VE7DM5WYCLB6K', 'name': 'projects/ext-datasets/operations/ZHT2OMTNF43VE7DM5WYCLB6K'}\n","Grid 68\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762654274490, 'update_timestamp_ms': 1762654274490, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LXH2NTPT4WO4334Q5K3G6REW', 'name': 'projects/ext-datasets/operations/LXH2NTPT4WO4334Q5K3G6REW'}\n","Grid 69\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762654281865, 'update_timestamp_ms': 1762654281865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IHJG6HO5JXBURXH6QIY52ZXB', 'name': 'projects/ext-datasets/operations/IHJG6HO5JXBURXH6QIY52ZXB'}\n","Grid 70\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762654288001, 'update_timestamp_ms': 1762654288001, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z64IXTFYHMT4YP2FTWTAF4BQ', 'name': 'projects/ext-datasets/operations/Z64IXTFYHMT4YP2FTWTAF4BQ'}\n","Grid 71\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762654295009, 'update_timestamp_ms': 1762654295009, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6U4HLKEPGFFPGTUECDNENYSI', 'name': 'projects/ext-datasets/operations/6U4HLKEPGFFPGTUECDNENYSI'}\n","Grid 72\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762654302138, 'update_timestamp_ms': 1762654302138, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IQDVX5WSXQ7E5WU6TWYRDJRI', 'name': 'projects/ext-datasets/operations/IQDVX5WSXQ7E5WU6TWYRDJRI'}\n","Grid 73\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762654309416, 'update_timestamp_ms': 1762654309416, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VLQQ6MSCOAGR7XOF7PJILBJY', 'name': 'projects/ext-datasets/operations/VLQQ6MSCOAGR7XOF7PJILBJY'}\n","Grid 74\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762654316819, 'update_timestamp_ms': 1762654316819, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYHSYAVWZCJPNH47ZHL4YJLJ', 'name': 'projects/ext-datasets/operations/ZYHSYAVWZCJPNH47ZHL4YJLJ'}\n","Grid 75\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762654324497, 'update_timestamp_ms': 1762654324497, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HHCUJR4DOHQTWZYVJC3VPQHB', 'name': 'projects/ext-datasets/operations/HHCUJR4DOHQTWZYVJC3VPQHB'}\n","Grid 76\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762654333435, 'update_timestamp_ms': 1762654333435, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2NXHHIZTAWMUIP2ONH2WKRNK', 'name': 'projects/ext-datasets/operations/2NXHHIZTAWMUIP2ONH2WKRNK'}\n","Grid 77\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762654340665, 'update_timestamp_ms': 1762654340665, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PA7N3M6BT5NFEOYVZTT2RF3E', 'name': 'projects/ext-datasets/operations/PA7N3M6BT5NFEOYVZTT2RF3E'}\n","Grid 78\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762654355061, 'update_timestamp_ms': 1762654355061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4LZYVKDAFXDBAZAZCMAD5HJS', 'name': 'projects/ext-datasets/operations/4LZYVKDAFXDBAZAZCMAD5HJS'}\n","Grid 79\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762654363095, 'update_timestamp_ms': 1762654363095, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BZ5UIKJ4MH62FMCDX4RDPB6N', 'name': 'projects/ext-datasets/operations/BZ5UIKJ4MH62FMCDX4RDPB6N'}\n","Grid 80\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762654367787, 'update_timestamp_ms': 1762654367787, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IZSLOUU4TXEVO65BL6F6A6TI', 'name': 'projects/ext-datasets/operations/IZSLOUU4TXEVO65BL6F6A6TI'}\n","Grid 81\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762654373562, 'update_timestamp_ms': 1762654373562, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7CLOGSI2U3AVSKGXPAAOUUYO', 'name': 'projects/ext-datasets/operations/7CLOGSI2U3AVSKGXPAAOUUYO'}\n","Grid 82\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762654380912, 'update_timestamp_ms': 1762654380912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LAIUHPT4O2BI27Z6XUYATFCI', 'name': 'projects/ext-datasets/operations/LAIUHPT4O2BI27Z6XUYATFCI'}\n","Grid 83\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762654386601, 'update_timestamp_ms': 1762654386601, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4A4QTYVS3JCDIL7WNKOCQOJC', 'name': 'projects/ext-datasets/operations/4A4QTYVS3JCDIL7WNKOCQOJC'}\n","Grid 84\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762654393177, 'update_timestamp_ms': 1762654393177, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AWH4WDKEPAPR2NUVUO55REA2', 'name': 'projects/ext-datasets/operations/AWH4WDKEPAPR2NUVUO55REA2'}\n","Grid 85\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762654399017, 'update_timestamp_ms': 1762654399017, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PTZJ333PZBVDKFMJ2CA2B27P', 'name': 'projects/ext-datasets/operations/PTZJ333PZBVDKFMJ2CA2B27P'}\n","Grid 86\n","curr_year 2017\n","Saving data for Murshidabad 2017\n","Task Started {'state': 'READY', 'description': 'Murshidabad_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762654405087, 'update_timestamp_ms': 1762654405087, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NHJPNXCKQABNHNA7FTKI47K2', 'name': 'projects/ext-datasets/operations/NHJPNXCKQABNHNA7FTKI47K2'}\n","4858.730777263641\n","Year 2017, District 21: Nadia, grids: 72\n","Grid 0\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762654419792, 'update_timestamp_ms': 1762654419792, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P5PGIJ6O6ZOCYR5GPDTFUSGM', 'name': 'projects/ext-datasets/operations/P5PGIJ6O6ZOCYR5GPDTFUSGM'}\n","Grid 1\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762654428290, 'update_timestamp_ms': 1762654428290, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EV7E5A62P67TQE7K7H72GMHI', 'name': 'projects/ext-datasets/operations/EV7E5A62P67TQE7K7H72GMHI'}\n","Grid 2\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762654436050, 'update_timestamp_ms': 1762654436050, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4AVZ7E3WSF7MZQ5SRC22BDN5', 'name': 'projects/ext-datasets/operations/4AVZ7E3WSF7MZQ5SRC22BDN5'}\n","Grid 3\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762654442743, 'update_timestamp_ms': 1762654442743, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZQODKJ3S5ANYKJQYAXBL447K', 'name': 'projects/ext-datasets/operations/ZQODKJ3S5ANYKJQYAXBL447K'}\n","Grid 4\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762654448632, 'update_timestamp_ms': 1762654448632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XNYFGFYQFEOSIAKVFEE7W6XU', 'name': 'projects/ext-datasets/operations/XNYFGFYQFEOSIAKVFEE7W6XU'}\n","Grid 5\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762654455185, 'update_timestamp_ms': 1762654455185, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3BXQRHRNSWRULT4HJMA3CN6R', 'name': 'projects/ext-datasets/operations/3BXQRHRNSWRULT4HJMA3CN6R'}\n","Grid 6\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762654462176, 'update_timestamp_ms': 1762654462176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q77RUTGWKSFVUYJCEDQXB6XR', 'name': 'projects/ext-datasets/operations/Q77RUTGWKSFVUYJCEDQXB6XR'}\n","Grid 7\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762654468567, 'update_timestamp_ms': 1762654468567, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SOHWXOHUHGMGRQVYC3LRXY7I', 'name': 'projects/ext-datasets/operations/SOHWXOHUHGMGRQVYC3LRXY7I'}\n","Grid 8\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762654476363, 'update_timestamp_ms': 1762654476363, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2N3M5A4DU65YQXAZEEAGWDBO', 'name': 'projects/ext-datasets/operations/2N3M5A4DU65YQXAZEEAGWDBO'}\n","Grid 9\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762654482096, 'update_timestamp_ms': 1762654482096, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W7DJDN6AT7R73UQCRTXHUPLX', 'name': 'projects/ext-datasets/operations/W7DJDN6AT7R73UQCRTXHUPLX'}\n","Grid 10\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762654489169, 'update_timestamp_ms': 1762654489169, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JAZE4BTT7ULXR4AW22IC4IA5', 'name': 'projects/ext-datasets/operations/JAZE4BTT7ULXR4AW22IC4IA5'}\n","Grid 11\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762654494392, 'update_timestamp_ms': 1762654494392, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P5CR4MTND7KHWX6MBWDVSDX6', 'name': 'projects/ext-datasets/operations/P5CR4MTND7KHWX6MBWDVSDX6'}\n","Grid 12\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762654507057, 'update_timestamp_ms': 1762654507057, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LG6MCIURPMHJE72WS37BF67W', 'name': 'projects/ext-datasets/operations/LG6MCIURPMHJE72WS37BF67W'}\n","Grid 13\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762654510762, 'update_timestamp_ms': 1762654510762, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q62SVQJXSOLW33DSXWEA54WH', 'name': 'projects/ext-datasets/operations/Q62SVQJXSOLW33DSXWEA54WH'}\n","Grid 14\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762654518502, 'update_timestamp_ms': 1762654518502, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NOV7L45SQCUNMKRPUY6WRTG2', 'name': 'projects/ext-datasets/operations/NOV7L45SQCUNMKRPUY6WRTG2'}\n","Grid 15\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762654523429, 'update_timestamp_ms': 1762654523429, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZBKSXJEJEZ3CMYOJSNRBYOPN', 'name': 'projects/ext-datasets/operations/ZBKSXJEJEZ3CMYOJSNRBYOPN'}\n","Grid 16\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762654532988, 'update_timestamp_ms': 1762654532988, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N7IIJR5LMYZRNWOZ7IHY34XE', 'name': 'projects/ext-datasets/operations/N7IIJR5LMYZRNWOZ7IHY34XE'}\n","Grid 17\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762654545412, 'update_timestamp_ms': 1762654545412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFWAH4H5QTNH6PORAP5DRIKG', 'name': 'projects/ext-datasets/operations/IFWAH4H5QTNH6PORAP5DRIKG'}\n","Grid 18\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762654552007, 'update_timestamp_ms': 1762654552007, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UQOIY7PFUKS5XT7DSBV2RCNO', 'name': 'projects/ext-datasets/operations/UQOIY7PFUKS5XT7DSBV2RCNO'}\n","Grid 19\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762654560385, 'update_timestamp_ms': 1762654560385, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5ODI7Q3VCCSI2CDX6VSTEJSX', 'name': 'projects/ext-datasets/operations/5ODI7Q3VCCSI2CDX6VSTEJSX'}\n","Grid 20\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762654569196, 'update_timestamp_ms': 1762654569196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3HI2V3ZNVQVHDBITC7KIAH3', 'name': 'projects/ext-datasets/operations/K3HI2V3ZNVQVHDBITC7KIAH3'}\n","Grid 21\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762654578627, 'update_timestamp_ms': 1762654578627, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BA67H25NCB5XSZWY4VYBJRFR', 'name': 'projects/ext-datasets/operations/BA67H25NCB5XSZWY4VYBJRFR'}\n","Grid 22\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762654585544, 'update_timestamp_ms': 1762654585544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHSQSRDRJV65EYXLCUXD5G2K', 'name': 'projects/ext-datasets/operations/YHSQSRDRJV65EYXLCUXD5G2K'}\n","Grid 23\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762654592409, 'update_timestamp_ms': 1762654592409, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IURVCPQ472HQXBMIR2WKMMPW', 'name': 'projects/ext-datasets/operations/IURVCPQ472HQXBMIR2WKMMPW'}\n","Grid 24\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762654598493, 'update_timestamp_ms': 1762654598493, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LMKFYNE6L3XZKRID6G4MI26H', 'name': 'projects/ext-datasets/operations/LMKFYNE6L3XZKRID6G4MI26H'}\n","Grid 25\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762654608846, 'update_timestamp_ms': 1762654608846, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ESSMALVXLSZGFXYDHOG572TM', 'name': 'projects/ext-datasets/operations/ESSMALVXLSZGFXYDHOG572TM'}\n","Grid 26\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762654614074, 'update_timestamp_ms': 1762654614074, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R4B5FL2FNRE33XDQ5WTJD6PS', 'name': 'projects/ext-datasets/operations/R4B5FL2FNRE33XDQ5WTJD6PS'}\n","Grid 27\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762654620875, 'update_timestamp_ms': 1762654620875, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CJ64I3PTFXQXK2AH5PNH26NK', 'name': 'projects/ext-datasets/operations/CJ64I3PTFXQXK2AH5PNH26NK'}\n","Grid 28\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762654631431, 'update_timestamp_ms': 1762654631431, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3VBOFEDHHXMHQSTTTJWI3P2S', 'name': 'projects/ext-datasets/operations/3VBOFEDHHXMHQSTTTJWI3P2S'}\n","Grid 29\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762654640823, 'update_timestamp_ms': 1762654640823, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2TAVARUICMYDVATVCAYUY3OD', 'name': 'projects/ext-datasets/operations/2TAVARUICMYDVATVCAYUY3OD'}\n","Grid 30\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762654648097, 'update_timestamp_ms': 1762654648097, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BT2DVMSYFWV7HFUAFGMFUE3O', 'name': 'projects/ext-datasets/operations/BT2DVMSYFWV7HFUAFGMFUE3O'}\n","Grid 31\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762654656171, 'update_timestamp_ms': 1762654656171, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BR7YIKUSAEU46WPPX4MSJFFQ', 'name': 'projects/ext-datasets/operations/BR7YIKUSAEU46WPPX4MSJFFQ'}\n","Grid 32\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762654663746, 'update_timestamp_ms': 1762654663746, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AG5VIAECDXES6USZKPVVJ5BO', 'name': 'projects/ext-datasets/operations/AG5VIAECDXES6USZKPVVJ5BO'}\n","Grid 33\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762654673967, 'update_timestamp_ms': 1762654673967, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6NZMVZGPB5T5R4Y6Y2OVDK43', 'name': 'projects/ext-datasets/operations/6NZMVZGPB5T5R4Y6Y2OVDK43'}\n","Grid 34\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762654682863, 'update_timestamp_ms': 1762654682863, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J2F2C4UPFWOTRU4URM36FEUH', 'name': 'projects/ext-datasets/operations/J2F2C4UPFWOTRU4URM36FEUH'}\n","Grid 35\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762654688596, 'update_timestamp_ms': 1762654688596, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EUJEKUNXV2E6DCPHQDF5WYXR', 'name': 'projects/ext-datasets/operations/EUJEKUNXV2E6DCPHQDF5WYXR'}\n","Grid 36\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762654697025, 'update_timestamp_ms': 1762654697025, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RSLDAJITOU6XBMMLFH4WIREI', 'name': 'projects/ext-datasets/operations/RSLDAJITOU6XBMMLFH4WIREI'}\n","Grid 37\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762654703898, 'update_timestamp_ms': 1762654703898, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3MN5ODMGPRQQP3D5TRYPT23E', 'name': 'projects/ext-datasets/operations/3MN5ODMGPRQQP3D5TRYPT23E'}\n","Grid 38\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762654708866, 'update_timestamp_ms': 1762654708866, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CR45V4BGQRBTEHIX7ZCRNQRU', 'name': 'projects/ext-datasets/operations/CR45V4BGQRBTEHIX7ZCRNQRU'}\n","Grid 39\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762654716825, 'update_timestamp_ms': 1762654716825, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LTNFHQHZQHYMRFNFV5JO5K3J', 'name': 'projects/ext-datasets/operations/LTNFHQHZQHYMRFNFV5JO5K3J'}\n","Grid 40\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762654725729, 'update_timestamp_ms': 1762654725729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4TODRBKH5ZRCB5EPJCEJUYMH', 'name': 'projects/ext-datasets/operations/4TODRBKH5ZRCB5EPJCEJUYMH'}\n","Grid 41\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762654736107, 'update_timestamp_ms': 1762654736107, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4AOARY2SLRNTYXLJORE3NG4J', 'name': 'projects/ext-datasets/operations/4AOARY2SLRNTYXLJORE3NG4J'}\n","Grid 42\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762654744870, 'update_timestamp_ms': 1762654744870, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VZFPYELGR7JPXWZIX7ZSRPBT', 'name': 'projects/ext-datasets/operations/VZFPYELGR7JPXWZIX7ZSRPBT'}\n","Grid 43\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762654751181, 'update_timestamp_ms': 1762654751181, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S2HCJSHR54YV77OBCXQI3DOJ', 'name': 'projects/ext-datasets/operations/S2HCJSHR54YV77OBCXQI3DOJ'}\n","Grid 44\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762654762793, 'update_timestamp_ms': 1762654762793, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3XVLYS5J2BHIUDHLKQPBX3SU', 'name': 'projects/ext-datasets/operations/3XVLYS5J2BHIUDHLKQPBX3SU'}\n","Grid 45\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762654769048, 'update_timestamp_ms': 1762654769048, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BGT53AHBJXRE6EO47BFSMU7Q', 'name': 'projects/ext-datasets/operations/BGT53AHBJXRE6EO47BFSMU7Q'}\n","Grid 46\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762654775783, 'update_timestamp_ms': 1762654775783, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L2T64P3RVYPMN2R4F7QPLPAS', 'name': 'projects/ext-datasets/operations/L2T64P3RVYPMN2R4F7QPLPAS'}\n","Grid 47\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762654784493, 'update_timestamp_ms': 1762654784493, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KQR6772HX4KTWYGY64G2PTMS', 'name': 'projects/ext-datasets/operations/KQR6772HX4KTWYGY64G2PTMS'}\n","Grid 48\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762654793860, 'update_timestamp_ms': 1762654793860, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYRQMDUCXAPDCNOMN44ICPNZ', 'name': 'projects/ext-datasets/operations/ZYRQMDUCXAPDCNOMN44ICPNZ'}\n","Grid 49\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762654801999, 'update_timestamp_ms': 1762654801999, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UGG75TWLKBDKU7BBKFYT2K3L', 'name': 'projects/ext-datasets/operations/UGG75TWLKBDKU7BBKFYT2K3L'}\n","Grid 50\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762654809058, 'update_timestamp_ms': 1762654809058, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DBI3JVZTUKCZ6WFTHDYMYE72', 'name': 'projects/ext-datasets/operations/DBI3JVZTUKCZ6WFTHDYMYE72'}\n","Grid 51\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762654815411, 'update_timestamp_ms': 1762654815411, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YYQUFHIO7ZCVXTWCRCBV4RKY', 'name': 'projects/ext-datasets/operations/YYQUFHIO7ZCVXTWCRCBV4RKY'}\n","Grid 52\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762654820819, 'update_timestamp_ms': 1762654820819, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXHMRD53TXSEE2T4DNQCZLKL', 'name': 'projects/ext-datasets/operations/WXHMRD53TXSEE2T4DNQCZLKL'}\n","Grid 53\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762654826365, 'update_timestamp_ms': 1762654826365, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMZSET7SI2P6SZT3O2GTTC6Q', 'name': 'projects/ext-datasets/operations/BMZSET7SI2P6SZT3O2GTTC6Q'}\n","Grid 54\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762654835460, 'update_timestamp_ms': 1762654835460, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DJ7QBEERICXDVW7S3EFUSEGQ', 'name': 'projects/ext-datasets/operations/DJ7QBEERICXDVW7S3EFUSEGQ'}\n","Grid 55\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762654841227, 'update_timestamp_ms': 1762654841227, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DZY3TALCKANBXFPZ3YKQ5GZP', 'name': 'projects/ext-datasets/operations/DZY3TALCKANBXFPZ3YKQ5GZP'}\n","Grid 56\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762654847526, 'update_timestamp_ms': 1762654847526, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y4ZBUKN5QJGU5BBU3XJPS3VZ', 'name': 'projects/ext-datasets/operations/Y4ZBUKN5QJGU5BBU3XJPS3VZ'}\n","Grid 57\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762654853996, 'update_timestamp_ms': 1762654853996, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LZZI4UADWIEV6DEFWGJUVMET', 'name': 'projects/ext-datasets/operations/LZZI4UADWIEV6DEFWGJUVMET'}\n","Grid 58\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762654861628, 'update_timestamp_ms': 1762654861628, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TNCY45SMZQLYF37UOTZC6TLT', 'name': 'projects/ext-datasets/operations/TNCY45SMZQLYF37UOTZC6TLT'}\n","Grid 59\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762654867107, 'update_timestamp_ms': 1762654867107, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TG2IIJXIUJBET3N5PBXDZ6L4', 'name': 'projects/ext-datasets/operations/TG2IIJXIUJBET3N5PBXDZ6L4'}\n","Grid 60\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762654877080, 'update_timestamp_ms': 1762654877080, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62FQR26OJQKQRQZRQ3C2LQOW', 'name': 'projects/ext-datasets/operations/62FQR26OJQKQRQZRQ3C2LQOW'}\n","Grid 61\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762654883093, 'update_timestamp_ms': 1762654883093, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6HX4QN2VANPWCKCTFGLTIQ5Q', 'name': 'projects/ext-datasets/operations/6HX4QN2VANPWCKCTFGLTIQ5Q'}\n","Grid 62\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762654889075, 'update_timestamp_ms': 1762654889075, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J4J7OZVT7DZ7VIZ54AMQUUGM', 'name': 'projects/ext-datasets/operations/J4J7OZVT7DZ7VIZ54AMQUUGM'}\n","Grid 63\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762654898050, 'update_timestamp_ms': 1762654898050, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6V6KGVQEOODRMADWTEC5VUIX', 'name': 'projects/ext-datasets/operations/6V6KGVQEOODRMADWTEC5VUIX'}\n","Grid 64\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762654907283, 'update_timestamp_ms': 1762654907283, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PXHUD3F4RPLGPJOENXUU7QLH', 'name': 'projects/ext-datasets/operations/PXHUD3F4RPLGPJOENXUU7QLH'}\n","Grid 65\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762654913799, 'update_timestamp_ms': 1762654913799, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X22HJFUCDZTOY3LEVAP475AG', 'name': 'projects/ext-datasets/operations/X22HJFUCDZTOY3LEVAP475AG'}\n","Grid 66\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762654918742, 'update_timestamp_ms': 1762654918742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KTJGUNDA4CFEUNAM56WYNUW5', 'name': 'projects/ext-datasets/operations/KTJGUNDA4CFEUNAM56WYNUW5'}\n","Grid 67\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762654925619, 'update_timestamp_ms': 1762654925619, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H5NS234TDT6RSMEP4GIGW3FT', 'name': 'projects/ext-datasets/operations/H5NS234TDT6RSMEP4GIGW3FT'}\n","Grid 68\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762654931695, 'update_timestamp_ms': 1762654931695, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CMXM2QRIPI5NQ46B6W3OZZ4E', 'name': 'projects/ext-datasets/operations/CMXM2QRIPI5NQ46B6W3OZZ4E'}\n","Grid 69\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762654937022, 'update_timestamp_ms': 1762654937022, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DFCGXRSZ5FPTOVNFXDYRLDJJ', 'name': 'projects/ext-datasets/operations/DFCGXRSZ5FPTOVNFXDYRLDJJ'}\n","Grid 70\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762654943181, 'update_timestamp_ms': 1762654943181, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VOCVCP5TOGPBCZ6DVT5MUCPL', 'name': 'projects/ext-datasets/operations/VOCVCP5TOGPBCZ6DVT5MUCPL'}\n","Grid 71\n","curr_year 2017\n","Saving data for Nadia 2017\n","Task Started {'state': 'READY', 'description': 'Nadia_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762654951856, 'update_timestamp_ms': 1762654951856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZAI6CKVCWKYXPUH56URUNSEM', 'name': 'projects/ext-datasets/operations/ZAI6CKVCWKYXPUH56URUNSEM'}\n","5405.53115773201\n","Year 2017, District 22: North 24 Parganas, grids: 105\n","Grid 0\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762654976848, 'update_timestamp_ms': 1762654976848, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IX3YTELXICHR7WVK2Z62N5ML', 'name': 'projects/ext-datasets/operations/IX3YTELXICHR7WVK2Z62N5ML'}\n","Grid 1\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762654988635, 'update_timestamp_ms': 1762654988635, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HQTZLQNDDVIFV2COJGSYYUC3', 'name': 'projects/ext-datasets/operations/HQTZLQNDDVIFV2COJGSYYUC3'}\n","Grid 2\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762655000432, 'update_timestamp_ms': 1762655000432, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJEHLVGA7GCO2NXDI6UC6C2F', 'name': 'projects/ext-datasets/operations/HJEHLVGA7GCO2NXDI6UC6C2F'}\n","Grid 3\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762655012757, 'update_timestamp_ms': 1762655012757, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '57SZOWMBEBJ5WBYS4TKGECYJ', 'name': 'projects/ext-datasets/operations/57SZOWMBEBJ5WBYS4TKGECYJ'}\n","Grid 4\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762655030436, 'update_timestamp_ms': 1762655030436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DZL2TEFZSV4PTLOAACAPA7GC', 'name': 'projects/ext-datasets/operations/DZL2TEFZSV4PTLOAACAPA7GC'}\n","Grid 5\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762655044543, 'update_timestamp_ms': 1762655044543, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UOHJY4563SXEXLQESUBMHGF7', 'name': 'projects/ext-datasets/operations/UOHJY4563SXEXLQESUBMHGF7'}\n","Grid 6\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762655056942, 'update_timestamp_ms': 1762655056942, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QFUSNJ4LZ7Y4VI2AL4NUIF65', 'name': 'projects/ext-datasets/operations/QFUSNJ4LZ7Y4VI2AL4NUIF65'}\n","Grid 7\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762655076261, 'update_timestamp_ms': 1762655076261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7SXDEJIRQIBCJRVREOB4JIQB', 'name': 'projects/ext-datasets/operations/7SXDEJIRQIBCJRVREOB4JIQB'}\n","Grid 8\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762655088635, 'update_timestamp_ms': 1762655088635, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OU4EF7IU5VABENSGND35BQ7B', 'name': 'projects/ext-datasets/operations/OU4EF7IU5VABENSGND35BQ7B'}\n","Grid 9\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762655103067, 'update_timestamp_ms': 1762655103067, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7SEPBYXLSKKQ2OZI2W3FYKKP', 'name': 'projects/ext-datasets/operations/7SEPBYXLSKKQ2OZI2W3FYKKP'}\n","Grid 10\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762655119325, 'update_timestamp_ms': 1762655119325, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FGJALR34NPODVL3QEPSEFAB3', 'name': 'projects/ext-datasets/operations/FGJALR34NPODVL3QEPSEFAB3'}\n","Grid 11\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762655132956, 'update_timestamp_ms': 1762655132956, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WUUIQAY4G7HDHHXDCIIU53DT', 'name': 'projects/ext-datasets/operations/WUUIQAY4G7HDHHXDCIIU53DT'}\n","Grid 12\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762655143399, 'update_timestamp_ms': 1762655143399, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '47AR4NXM5ACLYTVF2MOUQGS6', 'name': 'projects/ext-datasets/operations/47AR4NXM5ACLYTVF2MOUQGS6'}\n","Grid 13\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762655161623, 'update_timestamp_ms': 1762655161623, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NXL5YFKZPM7J4UBKCZPMRRTI', 'name': 'projects/ext-datasets/operations/NXL5YFKZPM7J4UBKCZPMRRTI'}\n","Grid 14\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762655168524, 'update_timestamp_ms': 1762655168524, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IH44RSFCI7PYZ6ESQIS6DRGK', 'name': 'projects/ext-datasets/operations/IH44RSFCI7PYZ6ESQIS6DRGK'}\n","Grid 15\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762655180629, 'update_timestamp_ms': 1762655180629, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VXJC2T6FBLXSZEHJVAG57HOY', 'name': 'projects/ext-datasets/operations/VXJC2T6FBLXSZEHJVAG57HOY'}\n","Grid 16\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762655190174, 'update_timestamp_ms': 1762655190174, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NKDBBM5DZ2TNHTM7UFKR3A46', 'name': 'projects/ext-datasets/operations/NKDBBM5DZ2TNHTM7UFKR3A46'}\n","Grid 17\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762655202483, 'update_timestamp_ms': 1762655202483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CB5ERTFD4JPIXUEVYR34YVRY', 'name': 'projects/ext-datasets/operations/CB5ERTFD4JPIXUEVYR34YVRY'}\n","Grid 18\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762655212750, 'update_timestamp_ms': 1762655212750, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JV3HE5IDT23QL2IY7CR73QIM', 'name': 'projects/ext-datasets/operations/JV3HE5IDT23QL2IY7CR73QIM'}\n","Grid 19\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762655225852, 'update_timestamp_ms': 1762655225852, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KT2OPPZXMCHK7LARGF5A3I43', 'name': 'projects/ext-datasets/operations/KT2OPPZXMCHK7LARGF5A3I43'}\n","Grid 20\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762655244130, 'update_timestamp_ms': 1762655244130, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DWZ36N3TWAIMRRFLBZUGPMLT', 'name': 'projects/ext-datasets/operations/DWZ36N3TWAIMRRFLBZUGPMLT'}\n","Grid 21\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762655256212, 'update_timestamp_ms': 1762655256212, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4GRPMKLZNUQ6PYKCJWRJE4LA', 'name': 'projects/ext-datasets/operations/4GRPMKLZNUQ6PYKCJWRJE4LA'}\n","Grid 22\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762655270216, 'update_timestamp_ms': 1762655270216, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O6OVG3P3SUVO3Y5CZNCTZAVO', 'name': 'projects/ext-datasets/operations/O6OVG3P3SUVO3Y5CZNCTZAVO'}\n","Grid 23\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762655281646, 'update_timestamp_ms': 1762655281646, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37IZXH6ORMCC75QDZWKBG22S', 'name': 'projects/ext-datasets/operations/37IZXH6ORMCC75QDZWKBG22S'}\n","Grid 24\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762655295759, 'update_timestamp_ms': 1762655295759, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W6BZHPC5DVKLUT4AUFYFQG3G', 'name': 'projects/ext-datasets/operations/W6BZHPC5DVKLUT4AUFYFQG3G'}\n","Grid 25\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762655307696, 'update_timestamp_ms': 1762655307696, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E32DF6SECLI4JF2Q62UKBIEZ', 'name': 'projects/ext-datasets/operations/E32DF6SECLI4JF2Q62UKBIEZ'}\n","Grid 26\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762655315987, 'update_timestamp_ms': 1762655315987, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A7BTXPTBVCHH5FJADNUYVJQO', 'name': 'projects/ext-datasets/operations/A7BTXPTBVCHH5FJADNUYVJQO'}\n","Grid 27\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762655327366, 'update_timestamp_ms': 1762655327366, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BHO6MBWKMQOXTJQUOQJWQ5F6', 'name': 'projects/ext-datasets/operations/BHO6MBWKMQOXTJQUOQJWQ5F6'}\n","Grid 28\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762655342826, 'update_timestamp_ms': 1762655342826, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KRNTPQEAXGRCALHR6HC5KKDM', 'name': 'projects/ext-datasets/operations/KRNTPQEAXGRCALHR6HC5KKDM'}\n","Grid 29\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762655356008, 'update_timestamp_ms': 1762655356008, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JPU5JNDCC6IUNL3KNOGALPJY', 'name': 'projects/ext-datasets/operations/JPU5JNDCC6IUNL3KNOGALPJY'}\n","Grid 30\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762655367045, 'update_timestamp_ms': 1762655367045, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OHHR6AWOAWANUTFWOYT72LHL', 'name': 'projects/ext-datasets/operations/OHHR6AWOAWANUTFWOYT72LHL'}\n","Grid 31\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762655379618, 'update_timestamp_ms': 1762655379618, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLTHQ7QTAMO3MUJBHCPVBQCI', 'name': 'projects/ext-datasets/operations/XLTHQ7QTAMO3MUJBHCPVBQCI'}\n","Grid 32\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762655391240, 'update_timestamp_ms': 1762655391240, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6ZGIXYUNNVJFBM5LCJGVCM7', 'name': 'projects/ext-datasets/operations/X6ZGIXYUNNVJFBM5LCJGVCM7'}\n","Grid 33\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762655404979, 'update_timestamp_ms': 1762655404979, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OPFFGITEIHWSSTGXUJIJSVOB', 'name': 'projects/ext-datasets/operations/OPFFGITEIHWSSTGXUJIJSVOB'}\n","Grid 34\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762655416831, 'update_timestamp_ms': 1762655416831, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TMRMQRWM4NXFZLHUGQTNHPGG', 'name': 'projects/ext-datasets/operations/TMRMQRWM4NXFZLHUGQTNHPGG'}\n","Grid 35\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762655427035, 'update_timestamp_ms': 1762655427035, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64CUDXKEVKXTUB2NKGA7IHHO', 'name': 'projects/ext-datasets/operations/64CUDXKEVKXTUB2NKGA7IHHO'}\n","Grid 36\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762655436251, 'update_timestamp_ms': 1762655436251, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A6PB2MTGI7FROARAEHN7R4HT', 'name': 'projects/ext-datasets/operations/A6PB2MTGI7FROARAEHN7R4HT'}\n","Grid 37\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762655449694, 'update_timestamp_ms': 1762655449694, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BO5O4Y5JK6ZRCILR5PNWHPAE', 'name': 'projects/ext-datasets/operations/BO5O4Y5JK6ZRCILR5PNWHPAE'}\n","Grid 38\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762655461029, 'update_timestamp_ms': 1762655461029, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KQU6WKD25NUHN6SW7IMX3LVV', 'name': 'projects/ext-datasets/operations/KQU6WKD25NUHN6SW7IMX3LVV'}\n","Grid 39\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762655474265, 'update_timestamp_ms': 1762655474265, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NKEINJ2WOJDUUXP72THAERTP', 'name': 'projects/ext-datasets/operations/NKEINJ2WOJDUUXP72THAERTP'}\n","Grid 40\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762655487921, 'update_timestamp_ms': 1762655487921, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LTCKVS7DKSEAXJEXFW6U7UN4', 'name': 'projects/ext-datasets/operations/LTCKVS7DKSEAXJEXFW6U7UN4'}\n","Grid 41\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762655497459, 'update_timestamp_ms': 1762655497459, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PQNPYVU7YWNBXVYX77A62EVL', 'name': 'projects/ext-datasets/operations/PQNPYVU7YWNBXVYX77A62EVL'}\n","Grid 42\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762655508634, 'update_timestamp_ms': 1762655508634, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RVG2POCLM7KQU5Q26DD3ZWYM', 'name': 'projects/ext-datasets/operations/RVG2POCLM7KQU5Q26DD3ZWYM'}\n","Grid 43\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762655517217, 'update_timestamp_ms': 1762655517217, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H45J2KL2RYHLAA6E5QAEEFC3', 'name': 'projects/ext-datasets/operations/H45J2KL2RYHLAA6E5QAEEFC3'}\n","Grid 44\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762655523539, 'update_timestamp_ms': 1762655523539, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGNDTXX66NOQCT5RLTDNBKF7', 'name': 'projects/ext-datasets/operations/QGNDTXX66NOQCT5RLTDNBKF7'}\n","Grid 45\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762655529939, 'update_timestamp_ms': 1762655529939, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R4GA47JEOWN7CMGCXYPM5ME5', 'name': 'projects/ext-datasets/operations/R4GA47JEOWN7CMGCXYPM5ME5'}\n","Grid 46\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762655543210, 'update_timestamp_ms': 1762655543210, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AGVLEVA4ZYWNF3F75BY3FKIP', 'name': 'projects/ext-datasets/operations/AGVLEVA4ZYWNF3F75BY3FKIP'}\n","Grid 47\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762655549662, 'update_timestamp_ms': 1762655549662, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SOYKXSQHASSQCB5M5YJSYSHL', 'name': 'projects/ext-datasets/operations/SOYKXSQHASSQCB5M5YJSYSHL'}\n","Grid 48\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762655563350, 'update_timestamp_ms': 1762655563350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O7FMO7H5WC5XU3SPIZCX6NZE', 'name': 'projects/ext-datasets/operations/O7FMO7H5WC5XU3SPIZCX6NZE'}\n","Grid 49\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762655574655, 'update_timestamp_ms': 1762655574655, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PHQDH6UTU65QV6BOVIH26CJB', 'name': 'projects/ext-datasets/operations/PHQDH6UTU65QV6BOVIH26CJB'}\n","Grid 50\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762655585367, 'update_timestamp_ms': 1762655585367, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PKHHLPYXRAKWBVUEDGKPIAVY', 'name': 'projects/ext-datasets/operations/PKHHLPYXRAKWBVUEDGKPIAVY'}\n","Grid 51\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762655598013, 'update_timestamp_ms': 1762655598013, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCWDWGOBTXXEL3FZL66BXJBJ', 'name': 'projects/ext-datasets/operations/FCWDWGOBTXXEL3FZL66BXJBJ'}\n","Grid 52\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762655609770, 'update_timestamp_ms': 1762655609770, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WLQ7KIFF54BPQYOJHUNZJYTP', 'name': 'projects/ext-datasets/operations/WLQ7KIFF54BPQYOJHUNZJYTP'}\n","Grid 53\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762655620410, 'update_timestamp_ms': 1762655620410, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EWPX3KHW6CS2VJHRW7HCU64U', 'name': 'projects/ext-datasets/operations/EWPX3KHW6CS2VJHRW7HCU64U'}\n","Grid 54\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762655633339, 'update_timestamp_ms': 1762655633339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VND6DUIIUFA4UDNDC55IMNS6', 'name': 'projects/ext-datasets/operations/VND6DUIIUFA4UDNDC55IMNS6'}\n","Grid 55\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762655645576, 'update_timestamp_ms': 1762655645576, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4YEKEDVHF2G4OKN7UXRWGBYE', 'name': 'projects/ext-datasets/operations/4YEKEDVHF2G4OKN7UXRWGBYE'}\n","Grid 56\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762655658201, 'update_timestamp_ms': 1762655658201, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QN5AMLTUHMY6AHF4JWMCG3L3', 'name': 'projects/ext-datasets/operations/QN5AMLTUHMY6AHF4JWMCG3L3'}\n","Grid 57\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762655667677, 'update_timestamp_ms': 1762655667677, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HHME5LVG7V5RETPFCIIWWUCU', 'name': 'projects/ext-datasets/operations/HHME5LVG7V5RETPFCIIWWUCU'}\n","Grid 58\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762655678161, 'update_timestamp_ms': 1762655678161, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYG55LEM3YLQ2WJK7FTEP6YV', 'name': 'projects/ext-datasets/operations/ZYG55LEM3YLQ2WJK7FTEP6YV'}\n","Grid 59\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762655690215, 'update_timestamp_ms': 1762655690215, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KWN4WCPDRQFPJSNST622DE4N', 'name': 'projects/ext-datasets/operations/KWN4WCPDRQFPJSNST622DE4N'}\n","Grid 60\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762655696093, 'update_timestamp_ms': 1762655696093, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5S5O6LIB4YQQMFOAUBQHUSRP', 'name': 'projects/ext-datasets/operations/5S5O6LIB4YQQMFOAUBQHUSRP'}\n","Grid 61\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762655708255, 'update_timestamp_ms': 1762655708255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AIM3ISYMBBC5AGP4NYSX5V3X', 'name': 'projects/ext-datasets/operations/AIM3ISYMBBC5AGP4NYSX5V3X'}\n","Grid 62\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762655718928, 'update_timestamp_ms': 1762655718928, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PC3IQNDBADK6WHIXMUTI4SVS', 'name': 'projects/ext-datasets/operations/PC3IQNDBADK6WHIXMUTI4SVS'}\n","Grid 63\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762655725111, 'update_timestamp_ms': 1762655725111, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7IL4CBTLG5ESCTKBF5ZAG5HX', 'name': 'projects/ext-datasets/operations/7IL4CBTLG5ESCTKBF5ZAG5HX'}\n","Grid 64\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762655734244, 'update_timestamp_ms': 1762655734244, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VTPIDHJ7PU6XLEDBASD6OKHO', 'name': 'projects/ext-datasets/operations/VTPIDHJ7PU6XLEDBASD6OKHO'}\n","Grid 65\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762655747051, 'update_timestamp_ms': 1762655747051, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '73M47M7S64CCFJLMXFSAIUSP', 'name': 'projects/ext-datasets/operations/73M47M7S64CCFJLMXFSAIUSP'}\n","Grid 66\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762655758633, 'update_timestamp_ms': 1762655758633, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '36SO62NUWDUPMIXGCYEAZF36', 'name': 'projects/ext-datasets/operations/36SO62NUWDUPMIXGCYEAZF36'}\n","Grid 67\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762655767427, 'update_timestamp_ms': 1762655767427, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PTOMVNQSCULA2NAPAX4A7LUM', 'name': 'projects/ext-datasets/operations/PTOMVNQSCULA2NAPAX4A7LUM'}\n","Grid 68\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762655781290, 'update_timestamp_ms': 1762655781290, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LTKXVT2D2W6N2A52D6NT6HO5', 'name': 'projects/ext-datasets/operations/LTKXVT2D2W6N2A52D6NT6HO5'}\n","Grid 69\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762655794396, 'update_timestamp_ms': 1762655794396, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R7O5Q3QCBUHDD3X7FEALVVGK', 'name': 'projects/ext-datasets/operations/R7O5Q3QCBUHDD3X7FEALVVGK'}\n","Grid 70\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762655804805, 'update_timestamp_ms': 1762655804805, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCUUPEE7UGV5LWAKYAFFX62C', 'name': 'projects/ext-datasets/operations/DCUUPEE7UGV5LWAKYAFFX62C'}\n","Grid 71\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762655818999, 'update_timestamp_ms': 1762655818999, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AC4VLPBOUELDZEOFKNYEAYSK', 'name': 'projects/ext-datasets/operations/AC4VLPBOUELDZEOFKNYEAYSK'}\n","Grid 72\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762655830408, 'update_timestamp_ms': 1762655830408, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YIKMPSTGZRZ6XJVQZK5WPM6L', 'name': 'projects/ext-datasets/operations/YIKMPSTGZRZ6XJVQZK5WPM6L'}\n","Grid 73\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762655836444, 'update_timestamp_ms': 1762655836444, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2IFISRQJO3D4IOMBI4WZPFL7', 'name': 'projects/ext-datasets/operations/2IFISRQJO3D4IOMBI4WZPFL7'}\n","Grid 74\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762655846537, 'update_timestamp_ms': 1762655846537, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NPPKK3SL5QLEFNARGHEW4QSQ', 'name': 'projects/ext-datasets/operations/NPPKK3SL5QLEFNARGHEW4QSQ'}\n","Grid 75\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762655856602, 'update_timestamp_ms': 1762655856602, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T4UVWYLPOGAKFAJE7DYRDZ76', 'name': 'projects/ext-datasets/operations/T4UVWYLPOGAKFAJE7DYRDZ76'}\n","Grid 76\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762655868124, 'update_timestamp_ms': 1762655868124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JEZ25FEEXJM4LMJ56J3DBR4B', 'name': 'projects/ext-datasets/operations/JEZ25FEEXJM4LMJ56J3DBR4B'}\n","Grid 77\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762655878153, 'update_timestamp_ms': 1762655878153, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DBSDB7JA556AXJGHKT3N3MJ7', 'name': 'projects/ext-datasets/operations/DBSDB7JA556AXJGHKT3N3MJ7'}\n","Grid 78\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762655888430, 'update_timestamp_ms': 1762655888430, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z6WTJCMTQJ5QNDY6MJUJYK7I', 'name': 'projects/ext-datasets/operations/Z6WTJCMTQJ5QNDY6MJUJYK7I'}\n","Grid 79\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762655899704, 'update_timestamp_ms': 1762655899704, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J7KMXLL6WPC6RR54WXNP67C2', 'name': 'projects/ext-datasets/operations/J7KMXLL6WPC6RR54WXNP67C2'}\n","Grid 80\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762655908193, 'update_timestamp_ms': 1762655908193, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '22ZVGIHCZPFFTKELHEAG4J2H', 'name': 'projects/ext-datasets/operations/22ZVGIHCZPFFTKELHEAG4J2H'}\n","Grid 81\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762655919120, 'update_timestamp_ms': 1762655919120, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VDDSVPBRKZEQ2AGRUBCBZ5IQ', 'name': 'projects/ext-datasets/operations/VDDSVPBRKZEQ2AGRUBCBZ5IQ'}\n","Grid 82\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762655932454, 'update_timestamp_ms': 1762655932454, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SUOKV2YMIZ2GRASZ2HGX3LO7', 'name': 'projects/ext-datasets/operations/SUOKV2YMIZ2GRASZ2HGX3LO7'}\n","Grid 83\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762655944385, 'update_timestamp_ms': 1762655944385, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GRU4X6X53JPRGQQ2UMKYDMIQ', 'name': 'projects/ext-datasets/operations/GRU4X6X53JPRGQQ2UMKYDMIQ'}\n","Grid 84\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762655954682, 'update_timestamp_ms': 1762655954682, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F5YOTQSJ64LU7DUZJAL46QLL', 'name': 'projects/ext-datasets/operations/F5YOTQSJ64LU7DUZJAL46QLL'}\n","Grid 85\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762655969185, 'update_timestamp_ms': 1762655969185, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UWZQBZI76ZIRNEPITYPMK46P', 'name': 'projects/ext-datasets/operations/UWZQBZI76ZIRNEPITYPMK46P'}\n","Grid 86\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762655982255, 'update_timestamp_ms': 1762655982255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VMJKZLV2PVBXQQXL63SZKWVM', 'name': 'projects/ext-datasets/operations/VMJKZLV2PVBXQQXL63SZKWVM'}\n","Grid 87\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_87_2017', 'priority': 100, 'creation_timestamp_ms': 1762655993638, 'update_timestamp_ms': 1762655993638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ED5VBUYOJL52KIN7QTE4LJKI', 'name': 'projects/ext-datasets/operations/ED5VBUYOJL52KIN7QTE4LJKI'}\n","Grid 88\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_88_2017', 'priority': 100, 'creation_timestamp_ms': 1762656006534, 'update_timestamp_ms': 1762656006534, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P3YUV5F7TM2ZJEWT5UKRVM2S', 'name': 'projects/ext-datasets/operations/P3YUV5F7TM2ZJEWT5UKRVM2S'}\n","Grid 89\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_89_2017', 'priority': 100, 'creation_timestamp_ms': 1762656013201, 'update_timestamp_ms': 1762656013201, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3PADHC3TZWN44XTCZSC7BTWU', 'name': 'projects/ext-datasets/operations/3PADHC3TZWN44XTCZSC7BTWU'}\n","Grid 90\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_90_2017', 'priority': 100, 'creation_timestamp_ms': 1762656023237, 'update_timestamp_ms': 1762656023237, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MG5W4B57RFQUUHP2JAPEJJQU', 'name': 'projects/ext-datasets/operations/MG5W4B57RFQUUHP2JAPEJJQU'}\n","Grid 91\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_91_2017', 'priority': 100, 'creation_timestamp_ms': 1762656035260, 'update_timestamp_ms': 1762656035260, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3TUBH7I2NLEAW4UHUZOZZBQ', 'name': 'projects/ext-datasets/operations/K3TUBH7I2NLEAW4UHUZOZZBQ'}\n","Grid 92\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_92_2017', 'priority': 100, 'creation_timestamp_ms': 1762656048547, 'update_timestamp_ms': 1762656048547, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QSIW3MTO4K5FBZPUZ37HL3RB', 'name': 'projects/ext-datasets/operations/QSIW3MTO4K5FBZPUZ37HL3RB'}\n","Grid 93\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_93_2017', 'priority': 100, 'creation_timestamp_ms': 1762656054558, 'update_timestamp_ms': 1762656054558, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L242L32UERU36XZJUCVSPXBN', 'name': 'projects/ext-datasets/operations/L242L32UERU36XZJUCVSPXBN'}\n","Grid 94\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_94_2017', 'priority': 100, 'creation_timestamp_ms': 1762656065988, 'update_timestamp_ms': 1762656065988, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SSSQWNHQHBAXPYSYO5QNAIOS', 'name': 'projects/ext-datasets/operations/SSSQWNHQHBAXPYSYO5QNAIOS'}\n","Grid 95\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_95_2017', 'priority': 100, 'creation_timestamp_ms': 1762656075268, 'update_timestamp_ms': 1762656075268, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '42SVQ22STRZEWQBL4CEBOEQH', 'name': 'projects/ext-datasets/operations/42SVQ22STRZEWQBL4CEBOEQH'}\n","Grid 96\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_96_2017', 'priority': 100, 'creation_timestamp_ms': 1762656085224, 'update_timestamp_ms': 1762656085224, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NQGQXCWPFKOTQWX3SJCW74XM', 'name': 'projects/ext-datasets/operations/NQGQXCWPFKOTQWX3SJCW74XM'}\n","Grid 97\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_97_2017', 'priority': 100, 'creation_timestamp_ms': 1762656098910, 'update_timestamp_ms': 1762656098910, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EKILBAPLUR6HIHYST2K4C776', 'name': 'projects/ext-datasets/operations/EKILBAPLUR6HIHYST2K4C776'}\n","Grid 98\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_98_2017', 'priority': 100, 'creation_timestamp_ms': 1762656111954, 'update_timestamp_ms': 1762656111954, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OLWXDYBZJH5AP6XBS6YF6S3M', 'name': 'projects/ext-datasets/operations/OLWXDYBZJH5AP6XBS6YF6S3M'}\n","Grid 99\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_99_2017', 'priority': 100, 'creation_timestamp_ms': 1762656123282, 'update_timestamp_ms': 1762656123282, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SJ5JB5XL45YMAX3KWSRF3YHG', 'name': 'projects/ext-datasets/operations/SJ5JB5XL45YMAX3KWSRF3YHG'}\n","Grid 100\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_100_2017', 'priority': 100, 'creation_timestamp_ms': 1762656135162, 'update_timestamp_ms': 1762656135162, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EOWGBJLP42XPBVYFSO7PEZTR', 'name': 'projects/ext-datasets/operations/EOWGBJLP42XPBVYFSO7PEZTR'}\n","Grid 101\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_101_2017', 'priority': 100, 'creation_timestamp_ms': 1762656146769, 'update_timestamp_ms': 1762656146769, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZHUWTEUEEKVWUPJX2IPOKDLR', 'name': 'projects/ext-datasets/operations/ZHUWTEUEEKVWUPJX2IPOKDLR'}\n","Grid 102\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_102_2017', 'priority': 100, 'creation_timestamp_ms': 1762656158297, 'update_timestamp_ms': 1762656158297, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M6ULVNULWU4C2QTCCQWIACXT', 'name': 'projects/ext-datasets/operations/M6ULVNULWU4C2QTCCQWIACXT'}\n","Grid 103\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_103_2017', 'priority': 100, 'creation_timestamp_ms': 1762656168478, 'update_timestamp_ms': 1762656168478, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DQACBR4OLFAKEA73STZMIODJ', 'name': 'projects/ext-datasets/operations/DQACBR4OLFAKEA73STZMIODJ'}\n","Grid 104\n","curr_year 2017\n","Saving data for North 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'North 24 Parganas_104_2017', 'priority': 100, 'creation_timestamp_ms': 1762656179056, 'update_timestamp_ms': 1762656179056, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TGRBOCRF7CMNYEOPSAYYMBXB', 'name': 'projects/ext-datasets/operations/TGRBOCRF7CMNYEOPSAYYMBXB'}\n","6632.857327699661\n","Year 2017, District 23: Pashchim Medinipur, grids: 137\n","Grid 0\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762656191525, 'update_timestamp_ms': 1762656191525, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N5IXXWLNZPAOUEC2LUF6GBFG', 'name': 'projects/ext-datasets/operations/N5IXXWLNZPAOUEC2LUF6GBFG'}\n","Grid 1\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762656199110, 'update_timestamp_ms': 1762656199110, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3GPF6PQ4CNSVXY7Z7UELEKEC', 'name': 'projects/ext-datasets/operations/3GPF6PQ4CNSVXY7Z7UELEKEC'}\n","Grid 2\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762656205412, 'update_timestamp_ms': 1762656205412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KO7DGC6KXHXA7TENULOYJQOV', 'name': 'projects/ext-datasets/operations/KO7DGC6KXHXA7TENULOYJQOV'}\n","Grid 3\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762656211108, 'update_timestamp_ms': 1762656211108, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GGFAAJUZAOI6J7MMCSS3Z2IN', 'name': 'projects/ext-datasets/operations/GGFAAJUZAOI6J7MMCSS3Z2IN'}\n","Grid 4\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762656219479, 'update_timestamp_ms': 1762656219479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WOVFKMXZRPBR2AHE6J4UYZWT', 'name': 'projects/ext-datasets/operations/WOVFKMXZRPBR2AHE6J4UYZWT'}\n","Grid 5\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762656226538, 'update_timestamp_ms': 1762656226538, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A6LXPACANOCYOG5E3INVUFSX', 'name': 'projects/ext-datasets/operations/A6LXPACANOCYOG5E3INVUFSX'}\n","Grid 6\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762656233248, 'update_timestamp_ms': 1762656233248, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GYKC34WL45AXUBSP3MLXTMAR', 'name': 'projects/ext-datasets/operations/GYKC34WL45AXUBSP3MLXTMAR'}\n","Grid 7\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762656241331, 'update_timestamp_ms': 1762656241331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KVIJ7Q56RFCYRAPI4PW2ZHCC', 'name': 'projects/ext-datasets/operations/KVIJ7Q56RFCYRAPI4PW2ZHCC'}\n","Grid 8\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762656248402, 'update_timestamp_ms': 1762656248402, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X5SC6YCJTY5C5HFKL745JI6Z', 'name': 'projects/ext-datasets/operations/X5SC6YCJTY5C5HFKL745JI6Z'}\n","Grid 9\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762656257963, 'update_timestamp_ms': 1762656257963, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O6PQHD2BGKHGAEO6QSI6P5GC', 'name': 'projects/ext-datasets/operations/O6PQHD2BGKHGAEO6QSI6P5GC'}\n","Grid 10\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762656261641, 'update_timestamp_ms': 1762656261641, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '524RUKBLGXLDC4A6WALRMP3G', 'name': 'projects/ext-datasets/operations/524RUKBLGXLDC4A6WALRMP3G'}\n","Grid 11\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762656271484, 'update_timestamp_ms': 1762656271484, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YLU53JR2VIEKKHALFJZHDOGD', 'name': 'projects/ext-datasets/operations/YLU53JR2VIEKKHALFJZHDOGD'}\n","Grid 12\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762656276108, 'update_timestamp_ms': 1762656276108, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MW2TQDKNT6SO7ENF26DIKV5M', 'name': 'projects/ext-datasets/operations/MW2TQDKNT6SO7ENF26DIKV5M'}\n","Grid 13\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762656280867, 'update_timestamp_ms': 1762656280867, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RWFA2T4ICTSYJHAFZ235E2MU', 'name': 'projects/ext-datasets/operations/RWFA2T4ICTSYJHAFZ235E2MU'}\n","Grid 14\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762656288738, 'update_timestamp_ms': 1762656288738, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLDBQFW7US6GNAPO7XUL7XKV', 'name': 'projects/ext-datasets/operations/XLDBQFW7US6GNAPO7XUL7XKV'}\n","Grid 15\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762656292460, 'update_timestamp_ms': 1762656292460, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MXV4MNAWY2S6RXIX5P4SC4H4', 'name': 'projects/ext-datasets/operations/MXV4MNAWY2S6RXIX5P4SC4H4'}\n","Grid 16\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762656296534, 'update_timestamp_ms': 1762656296534, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5FVBS7U4SHMCGLG7LMCYWZCN', 'name': 'projects/ext-datasets/operations/5FVBS7U4SHMCGLG7LMCYWZCN'}\n","Grid 17\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762656300879, 'update_timestamp_ms': 1762656300879, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FQEB4PWKHHMNKF2Q5FDWZSSK', 'name': 'projects/ext-datasets/operations/FQEB4PWKHHMNKF2Q5FDWZSSK'}\n","Grid 18\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762656309319, 'update_timestamp_ms': 1762656309319, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GH4XP7S2XMN7Q5KMFBX4LQ2N', 'name': 'projects/ext-datasets/operations/GH4XP7S2XMN7Q5KMFBX4LQ2N'}\n","Grid 19\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762656316641, 'update_timestamp_ms': 1762656316641, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXSCVZEOV5O2BZDCLRYEQEWT', 'name': 'projects/ext-datasets/operations/IXSCVZEOV5O2BZDCLRYEQEWT'}\n","Grid 20\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762656320650, 'update_timestamp_ms': 1762656320650, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OB6CO3QWKHN5FDR3UYVX55FS', 'name': 'projects/ext-datasets/operations/OB6CO3QWKHN5FDR3UYVX55FS'}\n","Grid 21\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762656326833, 'update_timestamp_ms': 1762656326833, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CY6HGBILDRRCFQ3IMB52DXRE', 'name': 'projects/ext-datasets/operations/CY6HGBILDRRCFQ3IMB52DXRE'}\n","Grid 22\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762656335955, 'update_timestamp_ms': 1762656335955, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F7OBIUKKF6JTZXGZQ2AZD4IZ', 'name': 'projects/ext-datasets/operations/F7OBIUKKF6JTZXGZQ2AZD4IZ'}\n","Grid 23\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762656339758, 'update_timestamp_ms': 1762656339758, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HDOFNZZX3ERQYTN54S2YB5BF', 'name': 'projects/ext-datasets/operations/HDOFNZZX3ERQYTN54S2YB5BF'}\n","Grid 24\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762656346987, 'update_timestamp_ms': 1762656346987, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2HANCJRX2W33KZQJFG4F56RB', 'name': 'projects/ext-datasets/operations/2HANCJRX2W33KZQJFG4F56RB'}\n","Grid 25\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762656354290, 'update_timestamp_ms': 1762656354290, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UHONUPREWQUGU3FRAFBP4KZE', 'name': 'projects/ext-datasets/operations/UHONUPREWQUGU3FRAFBP4KZE'}\n","Grid 26\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762656362444, 'update_timestamp_ms': 1762656362444, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WY4KG7Z4KJWSYPEDMBU4XKBT', 'name': 'projects/ext-datasets/operations/WY4KG7Z4KJWSYPEDMBU4XKBT'}\n","Grid 27\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762656371100, 'update_timestamp_ms': 1762656371100, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7WIJYTVOPX5ETBM5JGYSFHE2', 'name': 'projects/ext-datasets/operations/7WIJYTVOPX5ETBM5JGYSFHE2'}\n","Grid 28\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762656378402, 'update_timestamp_ms': 1762656378402, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MFIEIXFUR6Y3UZJEG5MMA5FH', 'name': 'projects/ext-datasets/operations/MFIEIXFUR6Y3UZJEG5MMA5FH'}\n","Grid 29\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762656386206, 'update_timestamp_ms': 1762656386206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D7IF32YKD3DAFVPQ5JASEWGS', 'name': 'projects/ext-datasets/operations/D7IF32YKD3DAFVPQ5JASEWGS'}\n","Grid 30\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762656392645, 'update_timestamp_ms': 1762656392645, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4WGZ6NBUQQRZ4TXLRTFW2Y3', 'name': 'projects/ext-datasets/operations/V4WGZ6NBUQQRZ4TXLRTFW2Y3'}\n","Grid 31\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762656397093, 'update_timestamp_ms': 1762656397093, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LXQJV4BS4KANLHELC32HNS2T', 'name': 'projects/ext-datasets/operations/LXQJV4BS4KANLHELC32HNS2T'}\n","Grid 32\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762656404004, 'update_timestamp_ms': 1762656404004, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K5PVV7G3P7XSJ6BRNGM4QCQH', 'name': 'projects/ext-datasets/operations/K5PVV7G3P7XSJ6BRNGM4QCQH'}\n","Grid 33\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762656411957, 'update_timestamp_ms': 1762656411957, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MTJ3WWRWD4VLDRNN67JVG6BW', 'name': 'projects/ext-datasets/operations/MTJ3WWRWD4VLDRNN67JVG6BW'}\n","Grid 34\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762656418840, 'update_timestamp_ms': 1762656418840, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DYO3ALZEAUM2MONCEYUYB24F', 'name': 'projects/ext-datasets/operations/DYO3ALZEAUM2MONCEYUYB24F'}\n","Grid 35\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762656427719, 'update_timestamp_ms': 1762656427719, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HFOXK4MPZPS3PA5MVDFPINIZ', 'name': 'projects/ext-datasets/operations/HFOXK4MPZPS3PA5MVDFPINIZ'}\n","Grid 36\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762656432553, 'update_timestamp_ms': 1762656432553, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EATJNAJPJASL6NHED6GBMLC3', 'name': 'projects/ext-datasets/operations/EATJNAJPJASL6NHED6GBMLC3'}\n","Grid 37\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762656442254, 'update_timestamp_ms': 1762656442254, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OMX24IFOFKEYLPATVQANCTUD', 'name': 'projects/ext-datasets/operations/OMX24IFOFKEYLPATVQANCTUD'}\n","Grid 38\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762656452255, 'update_timestamp_ms': 1762656452255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N5RBFHAJFWIIV3V7BNEM46Q2', 'name': 'projects/ext-datasets/operations/N5RBFHAJFWIIV3V7BNEM46Q2'}\n","Grid 39\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762656459895, 'update_timestamp_ms': 1762656459895, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BJVJXEVOF42MDX5O4LEQU4EU', 'name': 'projects/ext-datasets/operations/BJVJXEVOF42MDX5O4LEQU4EU'}\n","Grid 40\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762656465910, 'update_timestamp_ms': 1762656465910, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NTCLSHMOPZEWQOBUSF7UZE4Z', 'name': 'projects/ext-datasets/operations/NTCLSHMOPZEWQOBUSF7UZE4Z'}\n","Grid 41\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762656474428, 'update_timestamp_ms': 1762656474428, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXNUJJMRO7PZHYZCFWTQS6NU', 'name': 'projects/ext-datasets/operations/IXNUJJMRO7PZHYZCFWTQS6NU'}\n","Grid 42\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762656480606, 'update_timestamp_ms': 1762656480606, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K5H7C4LNI4Z6IHYTZTSPFUSH', 'name': 'projects/ext-datasets/operations/K5H7C4LNI4Z6IHYTZTSPFUSH'}\n","Grid 43\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762656487456, 'update_timestamp_ms': 1762656487456, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S5DFHBRCBKCD3MAV2AIJJ5LN', 'name': 'projects/ext-datasets/operations/S5DFHBRCBKCD3MAV2AIJJ5LN'}\n","Grid 44\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762656495657, 'update_timestamp_ms': 1762656495657, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZOOVCQRKGQGW4GYQ6OSGTDI', 'name': 'projects/ext-datasets/operations/AZOOVCQRKGQGW4GYQ6OSGTDI'}\n","Grid 45\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762656505780, 'update_timestamp_ms': 1762656505780, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3KNLJXFSBGO6SCJDGHGW3BE', 'name': 'projects/ext-datasets/operations/K3KNLJXFSBGO6SCJDGHGW3BE'}\n","Grid 46\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762656512324, 'update_timestamp_ms': 1762656512324, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BFE4HB7KGSK6ZS6RBG337BXT', 'name': 'projects/ext-datasets/operations/BFE4HB7KGSK6ZS6RBG337BXT'}\n","Grid 47\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762656518176, 'update_timestamp_ms': 1762656518176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TRNVLEFFW3B4GSDB5UWPPUB4', 'name': 'projects/ext-datasets/operations/TRNVLEFFW3B4GSDB5UWPPUB4'}\n","Grid 48\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762656527454, 'update_timestamp_ms': 1762656527454, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JGRJKCYFQZY3GLDBVXF4VL5W', 'name': 'projects/ext-datasets/operations/JGRJKCYFQZY3GLDBVXF4VL5W'}\n","Grid 49\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762656534302, 'update_timestamp_ms': 1762656534302, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TVFKBPWGU3I2ANR5ZZ62QQ7V', 'name': 'projects/ext-datasets/operations/TVFKBPWGU3I2ANR5ZZ62QQ7V'}\n","Grid 50\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762656538293, 'update_timestamp_ms': 1762656538293, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QH4Y4UB2AEIVTKHSDZSZP77E', 'name': 'projects/ext-datasets/operations/QH4Y4UB2AEIVTKHSDZSZP77E'}\n","Grid 51\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762656545479, 'update_timestamp_ms': 1762656545479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3GBB4YCPVFLWB3R5ISX5RBOA', 'name': 'projects/ext-datasets/operations/3GBB4YCPVFLWB3R5ISX5RBOA'}\n","Grid 52\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762656552841, 'update_timestamp_ms': 1762656552841, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NAMJHCTMIPZ7EVBAMWVP2UXC', 'name': 'projects/ext-datasets/operations/NAMJHCTMIPZ7EVBAMWVP2UXC'}\n","Grid 53\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762656560184, 'update_timestamp_ms': 1762656560184, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YLAZNLYRQ7CN3HQFGUAQYDR7', 'name': 'projects/ext-datasets/operations/YLAZNLYRQ7CN3HQFGUAQYDR7'}\n","Grid 54\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762656566137, 'update_timestamp_ms': 1762656566137, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SENC3EBMKCJ6DBXFPTFHJT73', 'name': 'projects/ext-datasets/operations/SENC3EBMKCJ6DBXFPTFHJT73'}\n","Grid 55\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762656573607, 'update_timestamp_ms': 1762656573607, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P2BS6PMTLVP54KI4SR3444TD', 'name': 'projects/ext-datasets/operations/P2BS6PMTLVP54KI4SR3444TD'}\n","Grid 56\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762656579996, 'update_timestamp_ms': 1762656579996, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BNQELTIGCTOOI2DOJ53ZQVHE', 'name': 'projects/ext-datasets/operations/BNQELTIGCTOOI2DOJ53ZQVHE'}\n","Grid 57\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762656588102, 'update_timestamp_ms': 1762656588102, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K2IKQ44ZFZP3FP6OKVJ7UKPY', 'name': 'projects/ext-datasets/operations/K2IKQ44ZFZP3FP6OKVJ7UKPY'}\n","Grid 58\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762656593522, 'update_timestamp_ms': 1762656593522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DV6XT6WGMSCKULSFVXXOBTNP', 'name': 'projects/ext-datasets/operations/DV6XT6WGMSCKULSFVXXOBTNP'}\n","Grid 59\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762656596924, 'update_timestamp_ms': 1762656596924, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZOMXOXZH2QWG5CEYSPLAAA42', 'name': 'projects/ext-datasets/operations/ZOMXOXZH2QWG5CEYSPLAAA42'}\n","Grid 60\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762656600989, 'update_timestamp_ms': 1762656600989, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '53Y6PSKHDAKNNLWUAEBJUFYR', 'name': 'projects/ext-datasets/operations/53Y6PSKHDAKNNLWUAEBJUFYR'}\n","Grid 61\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762656613380, 'update_timestamp_ms': 1762656613380, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SN3YIREDMLZITHQQWJERXFR7', 'name': 'projects/ext-datasets/operations/SN3YIREDMLZITHQQWJERXFR7'}\n","Grid 62\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762656620943, 'update_timestamp_ms': 1762656620943, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KNCWZK6F2XN6MKPJO6HVYXVE', 'name': 'projects/ext-datasets/operations/KNCWZK6F2XN6MKPJO6HVYXVE'}\n","Grid 63\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762656625158, 'update_timestamp_ms': 1762656625158, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VAU3N6VKUG5HETJN7AW3SK2I', 'name': 'projects/ext-datasets/operations/VAU3N6VKUG5HETJN7AW3SK2I'}\n","Grid 64\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762656631973, 'update_timestamp_ms': 1762656631973, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L754LPKGNNRQ6N5ESAPOXWZM', 'name': 'projects/ext-datasets/operations/L754LPKGNNRQ6N5ESAPOXWZM'}\n","Grid 65\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762656639958, 'update_timestamp_ms': 1762656639958, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6RWXDNP6BLABZPKXMZK5YC2V', 'name': 'projects/ext-datasets/operations/6RWXDNP6BLABZPKXMZK5YC2V'}\n","Grid 66\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762656646795, 'update_timestamp_ms': 1762656646795, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TR3RQIKCRZZDCTFL6H35VVEL', 'name': 'projects/ext-datasets/operations/TR3RQIKCRZZDCTFL6H35VVEL'}\n","Grid 67\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762656654192, 'update_timestamp_ms': 1762656654192, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3536G55NHWZJRXCUNX5INMMX', 'name': 'projects/ext-datasets/operations/3536G55NHWZJRXCUNX5INMMX'}\n","Grid 68\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762656657940, 'update_timestamp_ms': 1762656657940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5AP4XWHGCVSSYF276DXPUXKY', 'name': 'projects/ext-datasets/operations/5AP4XWHGCVSSYF276DXPUXKY'}\n","Grid 69\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762656665004, 'update_timestamp_ms': 1762656665004, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5CBUYVJFTEXYX2LON3G6ZWWT', 'name': 'projects/ext-datasets/operations/5CBUYVJFTEXYX2LON3G6ZWWT'}\n","Grid 70\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762656672582, 'update_timestamp_ms': 1762656672582, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '47V4ESB3D6M22D52LU4XKILJ', 'name': 'projects/ext-datasets/operations/47V4ESB3D6M22D52LU4XKILJ'}\n","Grid 71\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762656676519, 'update_timestamp_ms': 1762656676519, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GPINVNQTY3O6B5Q3ZW3B6ICL', 'name': 'projects/ext-datasets/operations/GPINVNQTY3O6B5Q3ZW3B6ICL'}\n","Grid 72\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762656683911, 'update_timestamp_ms': 1762656683911, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BAAKIKPXZO55ODDFIRL54NBO', 'name': 'projects/ext-datasets/operations/BAAKIKPXZO55ODDFIRL54NBO'}\n","Grid 73\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762656690478, 'update_timestamp_ms': 1762656690478, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J32725Z3XMKNYRHR2DFJEIED', 'name': 'projects/ext-datasets/operations/J32725Z3XMKNYRHR2DFJEIED'}\n","Grid 74\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762656696440, 'update_timestamp_ms': 1762656696440, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ICSQFWP3TWZH757K3KWJV4AM', 'name': 'projects/ext-datasets/operations/ICSQFWP3TWZH757K3KWJV4AM'}\n","Grid 75\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762656705583, 'update_timestamp_ms': 1762656705583, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BUDJCDIJ5BXY4I7X6V2VXOVS', 'name': 'projects/ext-datasets/operations/BUDJCDIJ5BXY4I7X6V2VXOVS'}\n","Grid 76\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762656714025, 'update_timestamp_ms': 1762656714025, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XM7PPW7FECNNNKQ6M3O66TK2', 'name': 'projects/ext-datasets/operations/XM7PPW7FECNNNKQ6M3O66TK2'}\n","Grid 77\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762656721691, 'update_timestamp_ms': 1762656721691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2VKXQCYRMV5WIVN6ELMCSWLA', 'name': 'projects/ext-datasets/operations/2VKXQCYRMV5WIVN6ELMCSWLA'}\n","Grid 78\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762656729644, 'update_timestamp_ms': 1762656729644, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IKCUB72MJN4NYMBZ7CZ6KRLJ', 'name': 'projects/ext-datasets/operations/IKCUB72MJN4NYMBZ7CZ6KRLJ'}\n","Grid 79\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762656736150, 'update_timestamp_ms': 1762656736150, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3NS6QI5NYBN5WLJGMLLMQYCZ', 'name': 'projects/ext-datasets/operations/3NS6QI5NYBN5WLJGMLLMQYCZ'}\n","Grid 80\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762656744215, 'update_timestamp_ms': 1762656744215, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C5DMPQANFPK6R6JRXP4OZOIU', 'name': 'projects/ext-datasets/operations/C5DMPQANFPK6R6JRXP4OZOIU'}\n","Grid 81\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762656751264, 'update_timestamp_ms': 1762656751264, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MUK6GYTC6RFW5MGWS5ZFPXD6', 'name': 'projects/ext-datasets/operations/MUK6GYTC6RFW5MGWS5ZFPXD6'}\n","Grid 82\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762656757892, 'update_timestamp_ms': 1762656757892, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C5AYSNYGXU25AAVQO4W6PCCH', 'name': 'projects/ext-datasets/operations/C5AYSNYGXU25AAVQO4W6PCCH'}\n","Grid 83\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762656766108, 'update_timestamp_ms': 1762656766108, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JZ5XQCLNBBW2UWEYUAMHFA7W', 'name': 'projects/ext-datasets/operations/JZ5XQCLNBBW2UWEYUAMHFA7W'}\n","Grid 84\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762656773523, 'update_timestamp_ms': 1762656773523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GTSDY74FH5Q7LZ6PUWZMTEQF', 'name': 'projects/ext-datasets/operations/GTSDY74FH5Q7LZ6PUWZMTEQF'}\n","Grid 85\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762656780140, 'update_timestamp_ms': 1762656780140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4UDWEDAOVC7WA3PDZSQD5VKI', 'name': 'projects/ext-datasets/operations/4UDWEDAOVC7WA3PDZSQD5VKI'}\n","Grid 86\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762656787998, 'update_timestamp_ms': 1762656787998, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZB62CJ4FM2LRBQGNPFCHWOQN', 'name': 'projects/ext-datasets/operations/ZB62CJ4FM2LRBQGNPFCHWOQN'}\n","Grid 87\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_87_2017', 'priority': 100, 'creation_timestamp_ms': 1762656796603, 'update_timestamp_ms': 1762656796603, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UJZBUDA55TS4UTZ5QNACJROS', 'name': 'projects/ext-datasets/operations/UJZBUDA55TS4UTZ5QNACJROS'}\n","Grid 88\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_88_2017', 'priority': 100, 'creation_timestamp_ms': 1762656803339, 'update_timestamp_ms': 1762656803339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPYIAGJK2JSNFJ2ZSUY5YUUI', 'name': 'projects/ext-datasets/operations/PPYIAGJK2JSNFJ2ZSUY5YUUI'}\n","Grid 89\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_89_2017', 'priority': 100, 'creation_timestamp_ms': 1762656811006, 'update_timestamp_ms': 1762656811006, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DSMVS6TSJ5XCD4P3WSFZXBXN', 'name': 'projects/ext-datasets/operations/DSMVS6TSJ5XCD4P3WSFZXBXN'}\n","Grid 90\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_90_2017', 'priority': 100, 'creation_timestamp_ms': 1762656818857, 'update_timestamp_ms': 1762656818857, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LW3GQQMPYOUJND54C57CMQET', 'name': 'projects/ext-datasets/operations/LW3GQQMPYOUJND54C57CMQET'}\n","Grid 91\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_91_2017', 'priority': 100, 'creation_timestamp_ms': 1762656825725, 'update_timestamp_ms': 1762656825725, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYBPIQYELLUCX73RQYL4DPT4', 'name': 'projects/ext-datasets/operations/XYBPIQYELLUCX73RQYL4DPT4'}\n","Grid 92\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_92_2017', 'priority': 100, 'creation_timestamp_ms': 1762656829437, 'update_timestamp_ms': 1762656829437, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N7QT4WOOFY3NT2RW2XDD54LV', 'name': 'projects/ext-datasets/operations/N7QT4WOOFY3NT2RW2XDD54LV'}\n","Grid 93\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_93_2017', 'priority': 100, 'creation_timestamp_ms': 1762656836388, 'update_timestamp_ms': 1762656836388, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'U3FNIEREMAZJM3MVYKRIRQCW', 'name': 'projects/ext-datasets/operations/U3FNIEREMAZJM3MVYKRIRQCW'}\n","Grid 94\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_94_2017', 'priority': 100, 'creation_timestamp_ms': 1762656844471, 'update_timestamp_ms': 1762656844471, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7KEVA4YXW4LZDTKJMGDEBA23', 'name': 'projects/ext-datasets/operations/7KEVA4YXW4LZDTKJMGDEBA23'}\n","Grid 95\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_95_2017', 'priority': 100, 'creation_timestamp_ms': 1762656851187, 'update_timestamp_ms': 1762656851187, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TYJGQKQOLJNDSAZRI37YNKZS', 'name': 'projects/ext-datasets/operations/TYJGQKQOLJNDSAZRI37YNKZS'}\n","Grid 96\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_96_2017', 'priority': 100, 'creation_timestamp_ms': 1762656858006, 'update_timestamp_ms': 1762656858006, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M7NG73K2EKRNO7LQMGFV3AOH', 'name': 'projects/ext-datasets/operations/M7NG73K2EKRNO7LQMGFV3AOH'}\n","Grid 97\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_97_2017', 'priority': 100, 'creation_timestamp_ms': 1762656865190, 'update_timestamp_ms': 1762656865190, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7ASTVLZ4NF3MPHDM567SFJVT', 'name': 'projects/ext-datasets/operations/7ASTVLZ4NF3MPHDM567SFJVT'}\n","Grid 98\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_98_2017', 'priority': 100, 'creation_timestamp_ms': 1762656869442, 'update_timestamp_ms': 1762656869442, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4XMKVY3QE4XT752M4CLLUTIG', 'name': 'projects/ext-datasets/operations/4XMKVY3QE4XT752M4CLLUTIG'}\n","Grid 99\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_99_2017', 'priority': 100, 'creation_timestamp_ms': 1762656876779, 'update_timestamp_ms': 1762656876779, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZWD2ZDDWWX45EAEPEB65J3K7', 'name': 'projects/ext-datasets/operations/ZWD2ZDDWWX45EAEPEB65J3K7'}\n","Grid 100\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_100_2017', 'priority': 100, 'creation_timestamp_ms': 1762656883926, 'update_timestamp_ms': 1762656883926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GT753YC5YE7L3J3LYFNTRGD4', 'name': 'projects/ext-datasets/operations/GT753YC5YE7L3J3LYFNTRGD4'}\n","Grid 101\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_101_2017', 'priority': 100, 'creation_timestamp_ms': 1762656887934, 'update_timestamp_ms': 1762656887934, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L3YFCSA2TL6YTS3GLFOADGST', 'name': 'projects/ext-datasets/operations/L3YFCSA2TL6YTS3GLFOADGST'}\n","Grid 102\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_102_2017', 'priority': 100, 'creation_timestamp_ms': 1762656892037, 'update_timestamp_ms': 1762656892037, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RLR6X7Y7VREGJ7ZY2PDSEZQW', 'name': 'projects/ext-datasets/operations/RLR6X7Y7VREGJ7ZY2PDSEZQW'}\n","Grid 103\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_103_2017', 'priority': 100, 'creation_timestamp_ms': 1762656900537, 'update_timestamp_ms': 1762656900537, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C6GJS2NQGLNEWJANSA7AH3EE', 'name': 'projects/ext-datasets/operations/C6GJS2NQGLNEWJANSA7AH3EE'}\n","Grid 104\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_104_2017', 'priority': 100, 'creation_timestamp_ms': 1762656904748, 'update_timestamp_ms': 1762656904748, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4MZT6NOPFNCEDHHGRFNUL453', 'name': 'projects/ext-datasets/operations/4MZT6NOPFNCEDHHGRFNUL453'}\n","Grid 105\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_105_2017', 'priority': 100, 'creation_timestamp_ms': 1762656913234, 'update_timestamp_ms': 1762656913234, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7W6MQEGDDUFYQGTB6A5GXM4W', 'name': 'projects/ext-datasets/operations/7W6MQEGDDUFYQGTB6A5GXM4W'}\n","Grid 106\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_106_2017', 'priority': 100, 'creation_timestamp_ms': 1762656922137, 'update_timestamp_ms': 1762656922137, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XBGPKGS4G6PYW7DK2AZMHSRM', 'name': 'projects/ext-datasets/operations/XBGPKGS4G6PYW7DK2AZMHSRM'}\n","Grid 107\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_107_2017', 'priority': 100, 'creation_timestamp_ms': 1762656928297, 'update_timestamp_ms': 1762656928297, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XSMYFCI23ZGDWCISG7PNNHSI', 'name': 'projects/ext-datasets/operations/XSMYFCI23ZGDWCISG7PNNHSI'}\n","Grid 108\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_108_2017', 'priority': 100, 'creation_timestamp_ms': 1762656934892, 'update_timestamp_ms': 1762656934892, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'METXYW4NNU7GOR5QNQ55N2IA', 'name': 'projects/ext-datasets/operations/METXYW4NNU7GOR5QNQ55N2IA'}\n","Grid 109\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_109_2017', 'priority': 100, 'creation_timestamp_ms': 1762656942637, 'update_timestamp_ms': 1762656942637, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BVCD7DLCYT6UTC67HZSHSQS3', 'name': 'projects/ext-datasets/operations/BVCD7DLCYT6UTC67HZSHSQS3'}\n","Grid 110\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_110_2017', 'priority': 100, 'creation_timestamp_ms': 1762656951277, 'update_timestamp_ms': 1762656951277, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E2WUB6OT4IXJTAP3PKKESEPI', 'name': 'projects/ext-datasets/operations/E2WUB6OT4IXJTAP3PKKESEPI'}\n","Grid 111\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_111_2017', 'priority': 100, 'creation_timestamp_ms': 1762656959080, 'update_timestamp_ms': 1762656959080, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2RZVNFL6BGNK6Z5CCYKE5KIZ', 'name': 'projects/ext-datasets/operations/2RZVNFL6BGNK6Z5CCYKE5KIZ'}\n","Grid 112\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_112_2017', 'priority': 100, 'creation_timestamp_ms': 1762656962562, 'update_timestamp_ms': 1762656962562, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XCZSBBI3H3732SCYAI6UZ7LR', 'name': 'projects/ext-datasets/operations/XCZSBBI3H3732SCYAI6UZ7LR'}\n","Grid 113\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_113_2017', 'priority': 100, 'creation_timestamp_ms': 1762656966527, 'update_timestamp_ms': 1762656966527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BUGV2EWOG2CHKPF5F453MEE7', 'name': 'projects/ext-datasets/operations/BUGV2EWOG2CHKPF5F453MEE7'}\n","Grid 114\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_114_2017', 'priority': 100, 'creation_timestamp_ms': 1762656974449, 'update_timestamp_ms': 1762656974449, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y5QPJKP2AUXCVSHP7P7PN2ZO', 'name': 'projects/ext-datasets/operations/Y5QPJKP2AUXCVSHP7P7PN2ZO'}\n","Grid 115\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_115_2017', 'priority': 100, 'creation_timestamp_ms': 1762656983994, 'update_timestamp_ms': 1762656983994, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2F3ORFW6VJNRZ3DFENH53KJU', 'name': 'projects/ext-datasets/operations/2F3ORFW6VJNRZ3DFENH53KJU'}\n","Grid 116\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_116_2017', 'priority': 100, 'creation_timestamp_ms': 1762656988094, 'update_timestamp_ms': 1762656988094, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JXTE3DZQAQTAVLOWX4DT6UBR', 'name': 'projects/ext-datasets/operations/JXTE3DZQAQTAVLOWX4DT6UBR'}\n","Grid 117\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_117_2017', 'priority': 100, 'creation_timestamp_ms': 1762656996237, 'update_timestamp_ms': 1762656996237, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C7YIAUKEHE7T5ZWI677IWWAC', 'name': 'projects/ext-datasets/operations/C7YIAUKEHE7T5ZWI677IWWAC'}\n","Grid 118\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_118_2017', 'priority': 100, 'creation_timestamp_ms': 1762657000170, 'update_timestamp_ms': 1762657000170, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WMVWOGJPZL4JGZQEDDH7OZWU', 'name': 'projects/ext-datasets/operations/WMVWOGJPZL4JGZQEDDH7OZWU'}\n","Grid 119\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_119_2017', 'priority': 100, 'creation_timestamp_ms': 1762657004443, 'update_timestamp_ms': 1762657004443, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6JX7VU44KIYFEV3QFUQ7AVMC', 'name': 'projects/ext-datasets/operations/6JX7VU44KIYFEV3QFUQ7AVMC'}\n","Grid 120\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_120_2017', 'priority': 100, 'creation_timestamp_ms': 1762657011353, 'update_timestamp_ms': 1762657011353, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J3YRSQKUOEUKAIZWRAJIVGNO', 'name': 'projects/ext-datasets/operations/J3YRSQKUOEUKAIZWRAJIVGNO'}\n","Grid 121\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_121_2017', 'priority': 100, 'creation_timestamp_ms': 1762657020222, 'update_timestamp_ms': 1762657020222, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OTGVIPC6DG2X2IFLRARVM37O', 'name': 'projects/ext-datasets/operations/OTGVIPC6DG2X2IFLRARVM37O'}\n","Grid 122\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_122_2017', 'priority': 100, 'creation_timestamp_ms': 1762657027173, 'update_timestamp_ms': 1762657027173, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H6C6L7WSL4XAO7IZHUYSNQQH', 'name': 'projects/ext-datasets/operations/H6C6L7WSL4XAO7IZHUYSNQQH'}\n","Grid 123\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_123_2017', 'priority': 100, 'creation_timestamp_ms': 1762657034045, 'update_timestamp_ms': 1762657034045, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BH2QCAX4UY542XGJ3OTBL3UU', 'name': 'projects/ext-datasets/operations/BH2QCAX4UY542XGJ3OTBL3UU'}\n","Grid 124\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_124_2017', 'priority': 100, 'creation_timestamp_ms': 1762657040222, 'update_timestamp_ms': 1762657040222, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RFJGCG43WQHXWTEIZOO5PLHO', 'name': 'projects/ext-datasets/operations/RFJGCG43WQHXWTEIZOO5PLHO'}\n","Grid 125\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_125_2017', 'priority': 100, 'creation_timestamp_ms': 1762657047717, 'update_timestamp_ms': 1762657047717, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YZB4KBBV6YL77ZH2V5KYQVOH', 'name': 'projects/ext-datasets/operations/YZB4KBBV6YL77ZH2V5KYQVOH'}\n","Grid 126\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_126_2017', 'priority': 100, 'creation_timestamp_ms': 1762657055564, 'update_timestamp_ms': 1762657055564, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HN2YO27JJ655VQPWLRY3CKDJ', 'name': 'projects/ext-datasets/operations/HN2YO27JJ655VQPWLRY3CKDJ'}\n","Grid 127\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_127_2017', 'priority': 100, 'creation_timestamp_ms': 1762657062509, 'update_timestamp_ms': 1762657062509, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QH76BPVJSYGQCZRBOBQQCVB3', 'name': 'projects/ext-datasets/operations/QH76BPVJSYGQCZRBOBQQCVB3'}\n","Grid 128\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_128_2017', 'priority': 100, 'creation_timestamp_ms': 1762657066784, 'update_timestamp_ms': 1762657066784, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FY46YSUR3GJO6EVU7JRAQEY2', 'name': 'projects/ext-datasets/operations/FY46YSUR3GJO6EVU7JRAQEY2'}\n","Grid 129\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_129_2017', 'priority': 100, 'creation_timestamp_ms': 1762657075843, 'update_timestamp_ms': 1762657075843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCZM2OIBC4XWFCHZTL4C3Z2N', 'name': 'projects/ext-datasets/operations/DCZM2OIBC4XWFCHZTL4C3Z2N'}\n","Grid 130\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_130_2017', 'priority': 100, 'creation_timestamp_ms': 1762657079376, 'update_timestamp_ms': 1762657079376, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4OEAXEP6MTWKSKGQ7DE2CD5Q', 'name': 'projects/ext-datasets/operations/4OEAXEP6MTWKSKGQ7DE2CD5Q'}\n","Grid 131\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_131_2017', 'priority': 100, 'creation_timestamp_ms': 1762657089946, 'update_timestamp_ms': 1762657089946, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4JWFYNHQ3X5H2KW6VY4PN57X', 'name': 'projects/ext-datasets/operations/4JWFYNHQ3X5H2KW6VY4PN57X'}\n","Grid 132\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_132_2017', 'priority': 100, 'creation_timestamp_ms': 1762657096800, 'update_timestamp_ms': 1762657096800, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NTWDLE6Q3BILPFQGJLO2SJIS', 'name': 'projects/ext-datasets/operations/NTWDLE6Q3BILPFQGJLO2SJIS'}\n","Grid 133\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_133_2017', 'priority': 100, 'creation_timestamp_ms': 1762657102962, 'update_timestamp_ms': 1762657102962, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SPSTNZV2PHA3NH7ELB23XKDV', 'name': 'projects/ext-datasets/operations/SPSTNZV2PHA3NH7ELB23XKDV'}\n","Grid 134\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_134_2017', 'priority': 100, 'creation_timestamp_ms': 1762657108805, 'update_timestamp_ms': 1762657108805, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AWG4QRGYMWFW2N67LXPFTQ46', 'name': 'projects/ext-datasets/operations/AWG4QRGYMWFW2N67LXPFTQ46'}\n","Grid 135\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_135_2017', 'priority': 100, 'creation_timestamp_ms': 1762657116011, 'update_timestamp_ms': 1762657116011, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMREEUG36OXX7BDXOLY7IRCX', 'name': 'projects/ext-datasets/operations/BMREEUG36OXX7BDXOLY7IRCX'}\n","Grid 136\n","curr_year 2017\n","Saving data for Pashchim Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Pashchim Medinipur_136_2017', 'priority': 100, 'creation_timestamp_ms': 1762657127187, 'update_timestamp_ms': 1762657127187, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MB5NLIHHLXPL47HCZXG5ZVP6', 'name': 'projects/ext-datasets/operations/MB5NLIHHLXPL47HCZXG5ZVP6'}\n","7580.883316516876\n","Year 2017, District 24: Purba Medinipur, grids: 66\n","Grid 0\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762657143044, 'update_timestamp_ms': 1762657143044, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NFKNSCZCXX6NWFUEEIA23I6P', 'name': 'projects/ext-datasets/operations/NFKNSCZCXX6NWFUEEIA23I6P'}\n","Grid 1\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762657149907, 'update_timestamp_ms': 1762657149907, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X3LL7FUYRIIXVHMNEYJLIEL4', 'name': 'projects/ext-datasets/operations/X3LL7FUYRIIXVHMNEYJLIEL4'}\n","Grid 2\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762657157370, 'update_timestamp_ms': 1762657157370, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EYL36YDY2FGDLEFWNAAL7FNX', 'name': 'projects/ext-datasets/operations/EYL36YDY2FGDLEFWNAAL7FNX'}\n","Grid 3\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762657165718, 'update_timestamp_ms': 1762657165718, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VHZAWT6Y4OZESX3S4MI3OYI7', 'name': 'projects/ext-datasets/operations/VHZAWT6Y4OZESX3S4MI3OYI7'}\n","Grid 4\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762657172956, 'update_timestamp_ms': 1762657172956, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TLT5VBL3NCFFLCC5RPKIGZLB', 'name': 'projects/ext-datasets/operations/TLT5VBL3NCFFLCC5RPKIGZLB'}\n","Grid 5\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762657179898, 'update_timestamp_ms': 1762657179898, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HFXM6IK3DPR5BYCNCTPG7CIV', 'name': 'projects/ext-datasets/operations/HFXM6IK3DPR5BYCNCTPG7CIV'}\n","Grid 6\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762657189258, 'update_timestamp_ms': 1762657189258, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YRYOEJG7PEYVYP3IXVBD6QMH', 'name': 'projects/ext-datasets/operations/YRYOEJG7PEYVYP3IXVBD6QMH'}\n","Grid 7\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762657195636, 'update_timestamp_ms': 1762657195636, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UNRTKAJKC7LWJ64MAEUPGGQQ', 'name': 'projects/ext-datasets/operations/UNRTKAJKC7LWJ64MAEUPGGQQ'}\n","Grid 8\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762657199840, 'update_timestamp_ms': 1762657199840, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NXTYHOQ6L5ZG5VQFLWRNXUHV', 'name': 'projects/ext-datasets/operations/NXTYHOQ6L5ZG5VQFLWRNXUHV'}\n","Grid 9\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762657208523, 'update_timestamp_ms': 1762657208523, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HLOI6D76NAR6CKRMMZZNPYK3', 'name': 'projects/ext-datasets/operations/HLOI6D76NAR6CKRMMZZNPYK3'}\n","Grid 10\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762657213022, 'update_timestamp_ms': 1762657213022, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5CPS6XPVWQOQX3YPE5QJR34O', 'name': 'projects/ext-datasets/operations/5CPS6XPVWQOQX3YPE5QJR34O'}\n","Grid 11\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762657218824, 'update_timestamp_ms': 1762657218824, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QI2VU5JGZ26EGOXYFDZUCPNN', 'name': 'projects/ext-datasets/operations/QI2VU5JGZ26EGOXYFDZUCPNN'}\n","Grid 12\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762657227317, 'update_timestamp_ms': 1762657227317, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XN65OGOBPUAGWGXVCUP5XIZC', 'name': 'projects/ext-datasets/operations/XN65OGOBPUAGWGXVCUP5XIZC'}\n","Grid 13\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762657233690, 'update_timestamp_ms': 1762657233690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WNCVRHZJ2HA4ZUJ3XNFK7M6A', 'name': 'projects/ext-datasets/operations/WNCVRHZJ2HA4ZUJ3XNFK7M6A'}\n","Grid 14\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762657239566, 'update_timestamp_ms': 1762657239566, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SHA377MFE4VLLNTC7BCJK6X5', 'name': 'projects/ext-datasets/operations/SHA377MFE4VLLNTC7BCJK6X5'}\n","Grid 15\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762657244254, 'update_timestamp_ms': 1762657244254, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7HCKJKLFG27BJ6XQQKERHD3W', 'name': 'projects/ext-datasets/operations/7HCKJKLFG27BJ6XQQKERHD3W'}\n","Grid 16\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762657248546, 'update_timestamp_ms': 1762657248546, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2OL4T4ZXUFO5EN3TO6EHXAY2', 'name': 'projects/ext-datasets/operations/2OL4T4ZXUFO5EN3TO6EHXAY2'}\n","Grid 17\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762657252318, 'update_timestamp_ms': 1762657252318, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S3R53I57CP6FFMPDSJURSPLU', 'name': 'projects/ext-datasets/operations/S3R53I57CP6FFMPDSJURSPLU'}\n","Grid 18\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762657259317, 'update_timestamp_ms': 1762657259317, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4QCUGOYVM4AODWF4JSBNRK6H', 'name': 'projects/ext-datasets/operations/4QCUGOYVM4AODWF4JSBNRK6H'}\n","Grid 19\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762657266873, 'update_timestamp_ms': 1762657266873, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PQ7JQWSHIZSXNU3KHRWV226V', 'name': 'projects/ext-datasets/operations/PQ7JQWSHIZSXNU3KHRWV226V'}\n","Grid 20\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762657274261, 'update_timestamp_ms': 1762657274261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HR64SO7UJ3ZQIABZDZMTXN2S', 'name': 'projects/ext-datasets/operations/HR64SO7UJ3ZQIABZDZMTXN2S'}\n","Grid 21\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762657283377, 'update_timestamp_ms': 1762657283377, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NCMIUWATTM2MQ47PNU4QLQGX', 'name': 'projects/ext-datasets/operations/NCMIUWATTM2MQ47PNU4QLQGX'}\n","Grid 22\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762657289088, 'update_timestamp_ms': 1762657289088, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QHNNO2ELSV4WI6EG7PS5RTSV', 'name': 'projects/ext-datasets/operations/QHNNO2ELSV4WI6EG7PS5RTSV'}\n","Grid 23\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762657296286, 'update_timestamp_ms': 1762657296286, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VH33RAYDODUAOACTX5VDFGXM', 'name': 'projects/ext-datasets/operations/VH33RAYDODUAOACTX5VDFGXM'}\n","Grid 24\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762657303274, 'update_timestamp_ms': 1762657303274, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L6ZWWHFMMA6XQVOI4TPK2R6A', 'name': 'projects/ext-datasets/operations/L6ZWWHFMMA6XQVOI4TPK2R6A'}\n","Grid 25\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762657312339, 'update_timestamp_ms': 1762657312339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JOS6MHIGUDMJK6AG36XO7CGN', 'name': 'projects/ext-datasets/operations/JOS6MHIGUDMJK6AG36XO7CGN'}\n","Grid 26\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762657320926, 'update_timestamp_ms': 1762657320926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RSUGU6BKDPJE2JEPESARW2PS', 'name': 'projects/ext-datasets/operations/RSUGU6BKDPJE2JEPESARW2PS'}\n","Grid 27\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762657328345, 'update_timestamp_ms': 1762657328345, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QDRLKNCE35GCMASX2R2VOAJD', 'name': 'projects/ext-datasets/operations/QDRLKNCE35GCMASX2R2VOAJD'}\n","Grid 28\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762657334358, 'update_timestamp_ms': 1762657334358, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YE7AYLCHK2EAFOT2JMZ2PYUQ', 'name': 'projects/ext-datasets/operations/YE7AYLCHK2EAFOT2JMZ2PYUQ'}\n","Grid 29\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762657341340, 'update_timestamp_ms': 1762657341340, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L5JUS35KWHCPGC6WYRVEZ3NS', 'name': 'projects/ext-datasets/operations/L5JUS35KWHCPGC6WYRVEZ3NS'}\n","Grid 30\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762657349275, 'update_timestamp_ms': 1762657349275, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MKQY5NBG44JJ5QRVZHRPA2B4', 'name': 'projects/ext-datasets/operations/MKQY5NBG44JJ5QRVZHRPA2B4'}\n","Grid 31\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762657356682, 'update_timestamp_ms': 1762657356682, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3X447YFUNHEHIMOFNAWBXMJJ', 'name': 'projects/ext-datasets/operations/3X447YFUNHEHIMOFNAWBXMJJ'}\n","Grid 32\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762657363331, 'update_timestamp_ms': 1762657363331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2KJETJDPKLZJBSW772CLXJQA', 'name': 'projects/ext-datasets/operations/2KJETJDPKLZJBSW772CLXJQA'}\n","Grid 33\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762657368275, 'update_timestamp_ms': 1762657368275, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7FMTES43YOXVLO3EKXN5M6CX', 'name': 'projects/ext-datasets/operations/7FMTES43YOXVLO3EKXN5M6CX'}\n","Grid 34\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762657376229, 'update_timestamp_ms': 1762657376229, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NLRTYMEML47QL3MZLI5M7Y7A', 'name': 'projects/ext-datasets/operations/NLRTYMEML47QL3MZLI5M7Y7A'}\n","Grid 35\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762657383237, 'update_timestamp_ms': 1762657383237, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AHFEMQK3ZRKL2NSHSP47OHK3', 'name': 'projects/ext-datasets/operations/AHFEMQK3ZRKL2NSHSP47OHK3'}\n","Grid 36\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762657389555, 'update_timestamp_ms': 1762657389555, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5WUSEIDEOJWLMHCS2KADKWXC', 'name': 'projects/ext-datasets/operations/5WUSEIDEOJWLMHCS2KADKWXC'}\n","Grid 37\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762657397022, 'update_timestamp_ms': 1762657397022, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YE3U3KFAKUL6DOJMDYCXZBHU', 'name': 'projects/ext-datasets/operations/YE3U3KFAKUL6DOJMDYCXZBHU'}\n","Grid 38\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762657405477, 'update_timestamp_ms': 1762657405477, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XIMTQNVF3BZTESY35B5SJDTN', 'name': 'projects/ext-datasets/operations/XIMTQNVF3BZTESY35B5SJDTN'}\n","Grid 39\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762657413101, 'update_timestamp_ms': 1762657413101, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NMY3CNFHLH2NQMM4F66PEMSI', 'name': 'projects/ext-datasets/operations/NMY3CNFHLH2NQMM4F66PEMSI'}\n","Grid 40\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762657421166, 'update_timestamp_ms': 1762657421166, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A7TCPZ32MIPT3G7LXBOEXG63', 'name': 'projects/ext-datasets/operations/A7TCPZ32MIPT3G7LXBOEXG63'}\n","Grid 41\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762657428249, 'update_timestamp_ms': 1762657428249, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W2NXGIOOMEQV4QIODVZ5FOBW', 'name': 'projects/ext-datasets/operations/W2NXGIOOMEQV4QIODVZ5FOBW'}\n","Grid 42\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762657436025, 'update_timestamp_ms': 1762657436025, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64V3QFTQXEFBI3XVAYC3NHJN', 'name': 'projects/ext-datasets/operations/64V3QFTQXEFBI3XVAYC3NHJN'}\n","Grid 43\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762657443414, 'update_timestamp_ms': 1762657443414, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VLVA4DDQOGFYDZ5WZJP2UDAJ', 'name': 'projects/ext-datasets/operations/VLVA4DDQOGFYDZ5WZJP2UDAJ'}\n","Grid 44\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762657451191, 'update_timestamp_ms': 1762657451191, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7NYB47PM4MVRZ6NIPA5WORN4', 'name': 'projects/ext-datasets/operations/7NYB47PM4MVRZ6NIPA5WORN4'}\n","Grid 45\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762657459746, 'update_timestamp_ms': 1762657459746, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LUU6WH2SK7PDN4PK3CHEDO3M', 'name': 'projects/ext-datasets/operations/LUU6WH2SK7PDN4PK3CHEDO3M'}\n","Grid 46\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762657466211, 'update_timestamp_ms': 1762657466211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UVS2QOYH2PLGTMFSHM3XMPL3', 'name': 'projects/ext-datasets/operations/UVS2QOYH2PLGTMFSHM3XMPL3'}\n","Grid 47\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762657474364, 'update_timestamp_ms': 1762657474364, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HYMGFLYI3EUHTCRX2G4YSIBJ', 'name': 'projects/ext-datasets/operations/HYMGFLYI3EUHTCRX2G4YSIBJ'}\n","Grid 48\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762657481945, 'update_timestamp_ms': 1762657481945, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4BN76EHEBBDZGQ36ZFRXQUQS', 'name': 'projects/ext-datasets/operations/4BN76EHEBBDZGQ36ZFRXQUQS'}\n","Grid 49\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762657486739, 'update_timestamp_ms': 1762657486739, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G2QERUTI7ZNPL2SGP6BHQG3T', 'name': 'projects/ext-datasets/operations/G2QERUTI7ZNPL2SGP6BHQG3T'}\n","Grid 50\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762657492966, 'update_timestamp_ms': 1762657492966, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GYW4IE7E6CWEFZ4W4PPYTQHN', 'name': 'projects/ext-datasets/operations/GYW4IE7E6CWEFZ4W4PPYTQHN'}\n","Grid 51\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762657501242, 'update_timestamp_ms': 1762657501242, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'STFV4MBGEI4IU5WJJV6FGP7M', 'name': 'projects/ext-datasets/operations/STFV4MBGEI4IU5WJJV6FGP7M'}\n","Grid 52\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762657508705, 'update_timestamp_ms': 1762657508705, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7EJGN4K6ZQ5BKTGOKWOUADDC', 'name': 'projects/ext-datasets/operations/7EJGN4K6ZQ5BKTGOKWOUADDC'}\n","Grid 53\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762657517426, 'update_timestamp_ms': 1762657517426, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MGHCS3PDQ43H5AWWB7ZLYMAR', 'name': 'projects/ext-datasets/operations/MGHCS3PDQ43H5AWWB7ZLYMAR'}\n","Grid 54\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762657523343, 'update_timestamp_ms': 1762657523343, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ECSDKPVOKNRGOWKRPHD6YQO2', 'name': 'projects/ext-datasets/operations/ECSDKPVOKNRGOWKRPHD6YQO2'}\n","Grid 55\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762657531327, 'update_timestamp_ms': 1762657531327, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HTBG5GMB44VGTI2DQMDFAUHQ', 'name': 'projects/ext-datasets/operations/HTBG5GMB44VGTI2DQMDFAUHQ'}\n","Grid 56\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762657539664, 'update_timestamp_ms': 1762657539664, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UTURDEYASPPGZ2JAKQIWVTX6', 'name': 'projects/ext-datasets/operations/UTURDEYASPPGZ2JAKQIWVTX6'}\n","Grid 57\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762657547246, 'update_timestamp_ms': 1762657547246, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T3L2GNWKIVO6OA64TECF5IFC', 'name': 'projects/ext-datasets/operations/T3L2GNWKIVO6OA64TECF5IFC'}\n","Grid 58\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762657551131, 'update_timestamp_ms': 1762657551131, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2LPEF26DVQLNIFSGD6HIT25S', 'name': 'projects/ext-datasets/operations/2LPEF26DVQLNIFSGD6HIT25S'}\n","Grid 59\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762657559342, 'update_timestamp_ms': 1762657559342, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WLGCU5XBQGP6MXJDOXWER3V6', 'name': 'projects/ext-datasets/operations/WLGCU5XBQGP6MXJDOXWER3V6'}\n","Grid 60\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762657572975, 'update_timestamp_ms': 1762657572975, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GOEHV2ZA4X664FMUNO62HUM5', 'name': 'projects/ext-datasets/operations/GOEHV2ZA4X664FMUNO62HUM5'}\n","Grid 61\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762657580419, 'update_timestamp_ms': 1762657580419, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ACJYYPK7NQXZNZUHTBICPG42', 'name': 'projects/ext-datasets/operations/ACJYYPK7NQXZNZUHTBICPG42'}\n","Grid 62\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762657584248, 'update_timestamp_ms': 1762657584248, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2HHKDJSRNZMEI5KMBDLFFWD5', 'name': 'projects/ext-datasets/operations/2HHKDJSRNZMEI5KMBDLFFWD5'}\n","Grid 63\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762657591412, 'update_timestamp_ms': 1762657591412, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F2B2IYVKSVYZ56Y7WV6XHBI2', 'name': 'projects/ext-datasets/operations/F2B2IYVKSVYZ56Y7WV6XHBI2'}\n","Grid 64\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762657598365, 'update_timestamp_ms': 1762657598365, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q5TXVMHH6QL5F3EVHOZIB2QZ', 'name': 'projects/ext-datasets/operations/Q5TXVMHH6QL5F3EVHOZIB2QZ'}\n","Grid 65\n","curr_year 2017\n","Saving data for Purba Medinipur 2017\n","Task Started {'state': 'READY', 'description': 'Purba Medinipur_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762657605363, 'update_timestamp_ms': 1762657605363, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZUGC6PKQR6AFOA4ID5ELHTL', 'name': 'projects/ext-datasets/operations/AZUGC6PKQR6AFOA4ID5ELHTL'}\n","8059.015738725662\n","Year 2017, District 25: Puruliya, grids: 14\n","Grid 0\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762657617740, 'update_timestamp_ms': 1762657617740, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5M4FOJF74A6FYVNEWN7QBZRV', 'name': 'projects/ext-datasets/operations/5M4FOJF74A6FYVNEWN7QBZRV'}\n","Grid 1\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762657627843, 'update_timestamp_ms': 1762657627843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4P6PUJOLKOGAWBVRANK67ES', 'name': 'projects/ext-datasets/operations/V4P6PUJOLKOGAWBVRANK67ES'}\n","Grid 2\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762657633527, 'update_timestamp_ms': 1762657633527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CHHXOWHFOOSQY6C5DKEY77JN', 'name': 'projects/ext-datasets/operations/CHHXOWHFOOSQY6C5DKEY77JN'}\n","Grid 3\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762657638867, 'update_timestamp_ms': 1762657638867, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IBFQ6N35DOGLCCN5G5EBIKTP', 'name': 'projects/ext-datasets/operations/IBFQ6N35DOGLCCN5G5EBIKTP'}\n","Grid 4\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762657646397, 'update_timestamp_ms': 1762657646397, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ITON6E27PXDBKJCJ5DJXWXLH', 'name': 'projects/ext-datasets/operations/ITON6E27PXDBKJCJ5DJXWXLH'}\n","Grid 5\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762657654320, 'update_timestamp_ms': 1762657654320, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M253CVPDA5FTXKXKP5FKOXEH', 'name': 'projects/ext-datasets/operations/M253CVPDA5FTXKXKP5FKOXEH'}\n","Grid 6\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762657662112, 'update_timestamp_ms': 1762657662112, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IDACHDF74JE7SJFBRCGNOS7A', 'name': 'projects/ext-datasets/operations/IDACHDF74JE7SJFBRCGNOS7A'}\n","Grid 7\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762657668483, 'update_timestamp_ms': 1762657668483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7C6PQT5WVKHTOT7FYOPWBOGQ', 'name': 'projects/ext-datasets/operations/7C6PQT5WVKHTOT7FYOPWBOGQ'}\n","Grid 8\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762657674895, 'update_timestamp_ms': 1762657674895, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LAY5XYLYOEAGFFD67X2CLZS7', 'name': 'projects/ext-datasets/operations/LAY5XYLYOEAGFFD67X2CLZS7'}\n","Grid 9\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762657683603, 'update_timestamp_ms': 1762657683603, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '57YVND4GM65EWUX5C3SKUTGY', 'name': 'projects/ext-datasets/operations/57YVND4GM65EWUX5C3SKUTGY'}\n","Grid 10\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762657690623, 'update_timestamp_ms': 1762657690623, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NWEGVDZQ7XZKA75ZJ6JEVBB', 'name': 'projects/ext-datasets/operations/4NWEGVDZQ7XZKA75ZJ6JEVBB'}\n","Grid 11\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762657697680, 'update_timestamp_ms': 1762657697680, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XJR3AEJSUWFQVS2AC5S3L7SE', 'name': 'projects/ext-datasets/operations/XJR3AEJSUWFQVS2AC5S3L7SE'}\n","Grid 12\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762657703789, 'update_timestamp_ms': 1762657703789, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DINWXLEKJUU2VZWR6AACYX7T', 'name': 'projects/ext-datasets/operations/DINWXLEKJUU2VZWR6AACYX7T'}\n","Grid 13\n","curr_year 2017\n","Saving data for Puruliya 2017\n","Task Started {'state': 'READY', 'description': 'Puruliya_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762657710134, 'update_timestamp_ms': 1762657710134, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VOFZSRN2Q24F3TWJNTGQSRZR', 'name': 'projects/ext-datasets/operations/VOFZSRN2Q24F3TWJNTGQSRZR'}\n","8163.815611839294\n","Year 2017, District 26: South 24 Parganas, grids: 96\n","Grid 0\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762657721617, 'update_timestamp_ms': 1762657721617, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CO7CVHBLECLP5O3BCXI2VNPK', 'name': 'projects/ext-datasets/operations/CO7CVHBLECLP5O3BCXI2VNPK'}\n","Grid 1\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762657726433, 'update_timestamp_ms': 1762657726433, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2H5I2G4GW4K5S4R4RKRLVOAO', 'name': 'projects/ext-datasets/operations/2H5I2G4GW4K5S4R4RKRLVOAO'}\n","Grid 2\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762657736622, 'update_timestamp_ms': 1762657736622, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2WIJVNRWZLDJJGSV7VJ6SNWV', 'name': 'projects/ext-datasets/operations/2WIJVNRWZLDJJGSV7VJ6SNWV'}\n","Grid 3\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762657743457, 'update_timestamp_ms': 1762657743457, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5PTR3G7WQSO5C4AQD2UYV6OC', 'name': 'projects/ext-datasets/operations/5PTR3G7WQSO5C4AQD2UYV6OC'}\n","Grid 4\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762657749135, 'update_timestamp_ms': 1762657749135, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZAZWNY6SF5H5QTLV2W3FX6AD', 'name': 'projects/ext-datasets/operations/ZAZWNY6SF5H5QTLV2W3FX6AD'}\n","Grid 5\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762657760790, 'update_timestamp_ms': 1762657760790, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62EE5QGJXR4TJKLPXQSSFEVQ', 'name': 'projects/ext-datasets/operations/62EE5QGJXR4TJKLPXQSSFEVQ'}\n","Grid 6\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762657774826, 'update_timestamp_ms': 1762657774826, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y3MW4Z3OJWSOEBFODVQGLMEN', 'name': 'projects/ext-datasets/operations/Y3MW4Z3OJWSOEBFODVQGLMEN'}\n","Grid 7\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762657786809, 'update_timestamp_ms': 1762657786809, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FZBZWJY5HIQ6VHYO6IAXI2P4', 'name': 'projects/ext-datasets/operations/FZBZWJY5HIQ6VHYO6IAXI2P4'}\n","Grid 8\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762657794926, 'update_timestamp_ms': 1762657794926, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BIIP6T4I7WSMPUBS3MLW7LF6', 'name': 'projects/ext-datasets/operations/BIIP6T4I7WSMPUBS3MLW7LF6'}\n","Grid 9\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762657806207, 'update_timestamp_ms': 1762657806207, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2JG6KWR5AFWD2AYHRGX2O7PZ', 'name': 'projects/ext-datasets/operations/2JG6KWR5AFWD2AYHRGX2O7PZ'}\n","Grid 10\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762657815663, 'update_timestamp_ms': 1762657815663, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XUAJECSKIE4BD3IVPXQI4J2S', 'name': 'projects/ext-datasets/operations/XUAJECSKIE4BD3IVPXQI4J2S'}\n","Grid 11\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762657825834, 'update_timestamp_ms': 1762657825834, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XIFX4RYGD7KEQOZPW2M7Q2R6', 'name': 'projects/ext-datasets/operations/XIFX4RYGD7KEQOZPW2M7Q2R6'}\n","Grid 12\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762657836975, 'update_timestamp_ms': 1762657836975, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BJBAMUYGWTH43MX5TH7KVZDW', 'name': 'projects/ext-datasets/operations/BJBAMUYGWTH43MX5TH7KVZDW'}\n","Grid 13\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762657846609, 'update_timestamp_ms': 1762657846609, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2TQCMU7EHVXVHQL6HU7NF4QN', 'name': 'projects/ext-datasets/operations/2TQCMU7EHVXVHQL6HU7NF4QN'}\n","Grid 14\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762657853958, 'update_timestamp_ms': 1762657853958, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LSVMZ4URZV2HPVRNQ3KZAT55', 'name': 'projects/ext-datasets/operations/LSVMZ4URZV2HPVRNQ3KZAT55'}\n","Grid 15\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762657862953, 'update_timestamp_ms': 1762657862953, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PRP73XPBKBLQ4FKCGPNQVXB4', 'name': 'projects/ext-datasets/operations/PRP73XPBKBLQ4FKCGPNQVXB4'}\n","Grid 16\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762657872487, 'update_timestamp_ms': 1762657872487, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IA6XNM7FRZNTIGHDLO63YYZA', 'name': 'projects/ext-datasets/operations/IA6XNM7FRZNTIGHDLO63YYZA'}\n","Grid 17\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762657883397, 'update_timestamp_ms': 1762657883397, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NGZI7NSE6Q3RIUKLGVIB25NJ', 'name': 'projects/ext-datasets/operations/NGZI7NSE6Q3RIUKLGVIB25NJ'}\n","Grid 18\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762657894076, 'update_timestamp_ms': 1762657894076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7R7NGWVLTW5EGP6BHJYR7SXX', 'name': 'projects/ext-datasets/operations/7R7NGWVLTW5EGP6BHJYR7SXX'}\n","Grid 19\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762657905886, 'update_timestamp_ms': 1762657905886, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UKD4UTDCYP27YHBC2BDV4EWL', 'name': 'projects/ext-datasets/operations/UKD4UTDCYP27YHBC2BDV4EWL'}\n","Grid 20\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762657914676, 'update_timestamp_ms': 1762657914676, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '35Z66UZU7LC5PTK6GMVJ6V6J', 'name': 'projects/ext-datasets/operations/35Z66UZU7LC5PTK6GMVJ6V6J'}\n","Grid 21\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762657924865, 'update_timestamp_ms': 1762657924865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D2BMBYJJZ2ZLVEARBZJML565', 'name': 'projects/ext-datasets/operations/D2BMBYJJZ2ZLVEARBZJML565'}\n","Grid 22\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762657941140, 'update_timestamp_ms': 1762657941140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S4K2JP3VDT5UKVFPI7HABIHR', 'name': 'projects/ext-datasets/operations/S4K2JP3VDT5UKVFPI7HABIHR'}\n","Grid 23\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762657949056, 'update_timestamp_ms': 1762657949056, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXOGIGFDDNUA72RRHV3ZLI3C', 'name': 'projects/ext-datasets/operations/IXOGIGFDDNUA72RRHV3ZLI3C'}\n","Grid 24\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762657954728, 'update_timestamp_ms': 1762657954728, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XKRWEJTXI5TZCHJ5Q7FEEIEZ', 'name': 'projects/ext-datasets/operations/XKRWEJTXI5TZCHJ5Q7FEEIEZ'}\n","Grid 25\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762657967115, 'update_timestamp_ms': 1762657967115, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IJ2FCKDETNQKZKF55VR5L6TM', 'name': 'projects/ext-datasets/operations/IJ2FCKDETNQKZKF55VR5L6TM'}\n","Grid 26\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762657977118, 'update_timestamp_ms': 1762657977118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PJW2WA7SH4TBRYBBBQY6KKBD', 'name': 'projects/ext-datasets/operations/PJW2WA7SH4TBRYBBBQY6KKBD'}\n","Grid 27\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762657982147, 'update_timestamp_ms': 1762657982147, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YQR3742F6Y5NL53KSI56C4GC', 'name': 'projects/ext-datasets/operations/YQR3742F6Y5NL53KSI56C4GC'}\n","Grid 28\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762657993527, 'update_timestamp_ms': 1762657993527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C3GNJPDZHAKUU3V2MB2A7K2D', 'name': 'projects/ext-datasets/operations/C3GNJPDZHAKUU3V2MB2A7K2D'}\n","Grid 29\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762658004659, 'update_timestamp_ms': 1762658004659, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2G2FEI4UX2Y5CVDRCFSYPYE4', 'name': 'projects/ext-datasets/operations/2G2FEI4UX2Y5CVDRCFSYPYE4'}\n","Grid 30\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762658015257, 'update_timestamp_ms': 1762658015257, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WNGWXIIJZ63GHTKHWWJOQFPM', 'name': 'projects/ext-datasets/operations/WNGWXIIJZ63GHTKHWWJOQFPM'}\n","Grid 31\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762658023473, 'update_timestamp_ms': 1762658023473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NJ3JE2P2BHN2KRXTVR3PQ5HC', 'name': 'projects/ext-datasets/operations/NJ3JE2P2BHN2KRXTVR3PQ5HC'}\n","Grid 32\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762658028979, 'update_timestamp_ms': 1762658028979, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QV6U3AYQ5F75G2CIJHVHSWDG', 'name': 'projects/ext-datasets/operations/QV6U3AYQ5F75G2CIJHVHSWDG'}\n","Grid 33\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762658039477, 'update_timestamp_ms': 1762658039477, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZQIBCHQZ5GSCYOUFQI3A3UVG', 'name': 'projects/ext-datasets/operations/ZQIBCHQZ5GSCYOUFQI3A3UVG'}\n","Grid 34\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762658045365, 'update_timestamp_ms': 1762658045365, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4XOT7VGN5VILILS63LLUZHCK', 'name': 'projects/ext-datasets/operations/4XOT7VGN5VILILS63LLUZHCK'}\n","Grid 35\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762658050426, 'update_timestamp_ms': 1762658050426, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NTDMYCDK3IMXBZ2K7VKKN2EN', 'name': 'projects/ext-datasets/operations/NTDMYCDK3IMXBZ2K7VKKN2EN'}\n","Grid 36\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762658060828, 'update_timestamp_ms': 1762658060828, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V5ONBRTZRMNIIE5UNB4B46RT', 'name': 'projects/ext-datasets/operations/V5ONBRTZRMNIIE5UNB4B46RT'}\n","Grid 37\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762658070675, 'update_timestamp_ms': 1762658070675, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GF5TP6HSVDSEPOUTJJRKIK5X', 'name': 'projects/ext-datasets/operations/GF5TP6HSVDSEPOUTJJRKIK5X'}\n","Grid 38\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762658078959, 'update_timestamp_ms': 1762658078959, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CJXDQHZN5ME55NN7ZP4SF35N', 'name': 'projects/ext-datasets/operations/CJXDQHZN5ME55NN7ZP4SF35N'}\n","Grid 39\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762658084150, 'update_timestamp_ms': 1762658084150, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IOFWUZRXS2O4UVG4ZT5F5YYJ', 'name': 'projects/ext-datasets/operations/IOFWUZRXS2O4UVG4ZT5F5YYJ'}\n","Grid 40\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762658094491, 'update_timestamp_ms': 1762658094491, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SGHSFIDOMYNXJKBWODVTUE3I', 'name': 'projects/ext-datasets/operations/SGHSFIDOMYNXJKBWODVTUE3I'}\n","Grid 41\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762658099479, 'update_timestamp_ms': 1762658099479, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZ6L6VGYP573JOYHI7AK6EVL', 'name': 'projects/ext-datasets/operations/XZ6L6VGYP573JOYHI7AK6EVL'}\n","Grid 42\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762658104123, 'update_timestamp_ms': 1762658104123, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4T46VWLU42FSHPQNNN7G2KS3', 'name': 'projects/ext-datasets/operations/4T46VWLU42FSHPQNNN7G2KS3'}\n","Grid 43\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762658114192, 'update_timestamp_ms': 1762658114192, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N34EYKWSDADLOYRZCZQ54UMY', 'name': 'projects/ext-datasets/operations/N34EYKWSDADLOYRZCZQ54UMY'}\n","Grid 44\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762658123158, 'update_timestamp_ms': 1762658123158, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2BHSCBJJHNJROJBZP27657WY', 'name': 'projects/ext-datasets/operations/2BHSCBJJHNJROJBZP27657WY'}\n","Grid 45\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762658131902, 'update_timestamp_ms': 1762658131902, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2B2HHU7LRQCTGWJFIZ2B6IZO', 'name': 'projects/ext-datasets/operations/2B2HHU7LRQCTGWJFIZ2B6IZO'}\n","Grid 46\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762658139038, 'update_timestamp_ms': 1762658139038, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KSEBDSG3ESM6ZZH2YL5SRAFF', 'name': 'projects/ext-datasets/operations/KSEBDSG3ESM6ZZH2YL5SRAFF'}\n","Grid 47\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762658148373, 'update_timestamp_ms': 1762658148373, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LWBZ7EZT3JLKW4PLGKBSURAE', 'name': 'projects/ext-datasets/operations/LWBZ7EZT3JLKW4PLGKBSURAE'}\n","Grid 48\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762658156748, 'update_timestamp_ms': 1762658156748, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64YDSCGZFGFYXDYOVZCIVKPU', 'name': 'projects/ext-datasets/operations/64YDSCGZFGFYXDYOVZCIVKPU'}\n","Grid 49\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762658163124, 'update_timestamp_ms': 1762658163124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HCSBKBCRPOD66HQU62UXAIRW', 'name': 'projects/ext-datasets/operations/HCSBKBCRPOD66HQU62UXAIRW'}\n","Grid 50\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762658172736, 'update_timestamp_ms': 1762658172736, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6CANJ4KVKVSHZVFXDHJLQEU6', 'name': 'projects/ext-datasets/operations/6CANJ4KVKVSHZVFXDHJLQEU6'}\n","Grid 51\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762658183522, 'update_timestamp_ms': 1762658183522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E2XDHC6U6ONKZ4SSNF2HL5YZ', 'name': 'projects/ext-datasets/operations/E2XDHC6U6ONKZ4SSNF2HL5YZ'}\n","Grid 52\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762658189528, 'update_timestamp_ms': 1762658189528, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XS2IZR7Y5P26CONDEK2FNN4Y', 'name': 'projects/ext-datasets/operations/XS2IZR7Y5P26CONDEK2FNN4Y'}\n","Grid 53\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762658198178, 'update_timestamp_ms': 1762658198178, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q4R3ZNMSLY64M4M7YM7MPW5R', 'name': 'projects/ext-datasets/operations/Q4R3ZNMSLY64M4M7YM7MPW5R'}\n","Grid 54\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762658208255, 'update_timestamp_ms': 1762658208255, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NB53FPAQI4R4LNJVGW5PEADO', 'name': 'projects/ext-datasets/operations/NB53FPAQI4R4LNJVGW5PEADO'}\n","Grid 55\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762658218559, 'update_timestamp_ms': 1762658218559, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L2LCIJDEPBN5XK76UJB2KDKC', 'name': 'projects/ext-datasets/operations/L2LCIJDEPBN5XK76UJB2KDKC'}\n","Grid 56\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762658226990, 'update_timestamp_ms': 1762658226990, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '256PZ72MZY26EZB4LZQJIIY3', 'name': 'projects/ext-datasets/operations/256PZ72MZY26EZB4LZQJIIY3'}\n","Grid 57\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762658238553, 'update_timestamp_ms': 1762658238553, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RPJZSCLQAV5FOUYTKI6XGIOP', 'name': 'projects/ext-datasets/operations/RPJZSCLQAV5FOUYTKI6XGIOP'}\n","Grid 58\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762658250452, 'update_timestamp_ms': 1762658250452, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYUVV2JI7FN5APGHMBZG5A2F', 'name': 'projects/ext-datasets/operations/XYUVV2JI7FN5APGHMBZG5A2F'}\n","Grid 59\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762658256418, 'update_timestamp_ms': 1762658256418, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IOCEQRFICZYF7LG5DET6G433', 'name': 'projects/ext-datasets/operations/IOCEQRFICZYF7LG5DET6G433'}\n","Grid 60\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762658270323, 'update_timestamp_ms': 1762658270323, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LZ4JFOGPOPC4NGO2Z4JUZM3R', 'name': 'projects/ext-datasets/operations/LZ4JFOGPOPC4NGO2Z4JUZM3R'}\n","Grid 61\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762658280593, 'update_timestamp_ms': 1762658280593, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MQMOCAX5X2U4L4IBBKMOHAXA', 'name': 'projects/ext-datasets/operations/MQMOCAX5X2U4L4IBBKMOHAXA'}\n","Grid 62\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762658292209, 'update_timestamp_ms': 1762658292209, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RJ5EVGBN54NAWMTPA7W5N77Z', 'name': 'projects/ext-datasets/operations/RJ5EVGBN54NAWMTPA7W5N77Z'}\n","Grid 63\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762658302123, 'update_timestamp_ms': 1762658302123, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TGFFL47X7YC6OWXJZQO2XQYE', 'name': 'projects/ext-datasets/operations/TGFFL47X7YC6OWXJZQO2XQYE'}\n","Grid 64\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762658310424, 'update_timestamp_ms': 1762658310424, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3RTHW772TDXSGOMGTPBPCIIU', 'name': 'projects/ext-datasets/operations/3RTHW772TDXSGOMGTPBPCIIU'}\n","Grid 65\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762658319876, 'update_timestamp_ms': 1762658319876, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LQUWEEH2X7ZAQHBVKLPWRBJZ', 'name': 'projects/ext-datasets/operations/LQUWEEH2X7ZAQHBVKLPWRBJZ'}\n","Grid 66\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_66_2017', 'priority': 100, 'creation_timestamp_ms': 1762658325522, 'update_timestamp_ms': 1762658325522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ALNBVM2EM24TUHHAIC6MIZLY', 'name': 'projects/ext-datasets/operations/ALNBVM2EM24TUHHAIC6MIZLY'}\n","Grid 67\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_67_2017', 'priority': 100, 'creation_timestamp_ms': 1762658333125, 'update_timestamp_ms': 1762658333125, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UJQJYFMAUBQTHMB3R6KHPBS7', 'name': 'projects/ext-datasets/operations/UJQJYFMAUBQTHMB3R6KHPBS7'}\n","Grid 68\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_68_2017', 'priority': 100, 'creation_timestamp_ms': 1762658341309, 'update_timestamp_ms': 1762658341309, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4KRD7OSP4CGHHPVIJAW55W6F', 'name': 'projects/ext-datasets/operations/4KRD7OSP4CGHHPVIJAW55W6F'}\n","Grid 69\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_69_2017', 'priority': 100, 'creation_timestamp_ms': 1762658355706, 'update_timestamp_ms': 1762658355706, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JEJYU2S7LQX42S26BTHTA2UL', 'name': 'projects/ext-datasets/operations/JEJYU2S7LQX42S26BTHTA2UL'}\n","Grid 70\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_70_2017', 'priority': 100, 'creation_timestamp_ms': 1762658361264, 'update_timestamp_ms': 1762658361264, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F5NAZNHD5E34DLUFOUXSIA4U', 'name': 'projects/ext-datasets/operations/F5NAZNHD5E34DLUFOUXSIA4U'}\n","Grid 71\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_71_2017', 'priority': 100, 'creation_timestamp_ms': 1762658366655, 'update_timestamp_ms': 1762658366655, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ULJKUUTZ2PG67WRCGZFFLDZK', 'name': 'projects/ext-datasets/operations/ULJKUUTZ2PG67WRCGZFFLDZK'}\n","Grid 72\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_72_2017', 'priority': 100, 'creation_timestamp_ms': 1762658382194, 'update_timestamp_ms': 1762658382194, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VORWVY26L24WUPPRW3FXLSDD', 'name': 'projects/ext-datasets/operations/VORWVY26L24WUPPRW3FXLSDD'}\n","Grid 73\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_73_2017', 'priority': 100, 'creation_timestamp_ms': 1762658392229, 'update_timestamp_ms': 1762658392229, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7FJPJMR5LMLUUJFYLDNASLOF', 'name': 'projects/ext-datasets/operations/7FJPJMR5LMLUUJFYLDNASLOF'}\n","Grid 74\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_74_2017', 'priority': 100, 'creation_timestamp_ms': 1762658403918, 'update_timestamp_ms': 1762658403918, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KEITH5KA5SUJXJAUYW3ECCQA', 'name': 'projects/ext-datasets/operations/KEITH5KA5SUJXJAUYW3ECCQA'}\n","Grid 75\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_75_2017', 'priority': 100, 'creation_timestamp_ms': 1762658411665, 'update_timestamp_ms': 1762658411665, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2VKDQRLVUCXGXKKKH6SVUZAA', 'name': 'projects/ext-datasets/operations/2VKDQRLVUCXGXKKKH6SVUZAA'}\n","Grid 76\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_76_2017', 'priority': 100, 'creation_timestamp_ms': 1762658420266, 'update_timestamp_ms': 1762658420266, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HKGODGCCMJIDHJLSBJCQSLLZ', 'name': 'projects/ext-datasets/operations/HKGODGCCMJIDHJLSBJCQSLLZ'}\n","Grid 77\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_77_2017', 'priority': 100, 'creation_timestamp_ms': 1762658431524, 'update_timestamp_ms': 1762658431524, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TNJ7E433ZSQRKG4LKN2IIVYM', 'name': 'projects/ext-datasets/operations/TNJ7E433ZSQRKG4LKN2IIVYM'}\n","Grid 78\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_78_2017', 'priority': 100, 'creation_timestamp_ms': 1762658437803, 'update_timestamp_ms': 1762658437803, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4QLZZOBRVOCEZJOYK7FXDAFH', 'name': 'projects/ext-datasets/operations/4QLZZOBRVOCEZJOYK7FXDAFH'}\n","Grid 79\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_79_2017', 'priority': 100, 'creation_timestamp_ms': 1762658446019, 'update_timestamp_ms': 1762658446019, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EP5FJKS4UFY7BRL5YYLOBTBY', 'name': 'projects/ext-datasets/operations/EP5FJKS4UFY7BRL5YYLOBTBY'}\n","Grid 80\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_80_2017', 'priority': 100, 'creation_timestamp_ms': 1762658450410, 'update_timestamp_ms': 1762658450410, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TC2UYMSQ33VR6Y3FVWQOSGVI', 'name': 'projects/ext-datasets/operations/TC2UYMSQ33VR6Y3FVWQOSGVI'}\n","Grid 81\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_81_2017', 'priority': 100, 'creation_timestamp_ms': 1762658455489, 'update_timestamp_ms': 1762658455489, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6MPIM32AWN5QJRZ3XFYVJ2MD', 'name': 'projects/ext-datasets/operations/6MPIM32AWN5QJRZ3XFYVJ2MD'}\n","Grid 82\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_82_2017', 'priority': 100, 'creation_timestamp_ms': 1762658465865, 'update_timestamp_ms': 1762658465865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7N7K6WQUV5CCL637CV4JWXB5', 'name': 'projects/ext-datasets/operations/7N7K6WQUV5CCL637CV4JWXB5'}\n","Grid 83\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_83_2017', 'priority': 100, 'creation_timestamp_ms': 1762658474279, 'update_timestamp_ms': 1762658474279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NPOFQ37SR5OCIZ5C3V6NHIH3', 'name': 'projects/ext-datasets/operations/NPOFQ37SR5OCIZ5C3V6NHIH3'}\n","Grid 84\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_84_2017', 'priority': 100, 'creation_timestamp_ms': 1762658483657, 'update_timestamp_ms': 1762658483657, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HHMJILEVDQPYR3FOR52XBP3Y', 'name': 'projects/ext-datasets/operations/HHMJILEVDQPYR3FOR52XBP3Y'}\n","Grid 85\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_85_2017', 'priority': 100, 'creation_timestamp_ms': 1762658494915, 'update_timestamp_ms': 1762658494915, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y2Q3XCWM7A7PRAZ5KT4ZDAV6', 'name': 'projects/ext-datasets/operations/Y2Q3XCWM7A7PRAZ5KT4ZDAV6'}\n","Grid 86\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_86_2017', 'priority': 100, 'creation_timestamp_ms': 1762658503819, 'update_timestamp_ms': 1762658503819, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FPYYKEFO4KM3TXHPYYJ5I3GX', 'name': 'projects/ext-datasets/operations/FPYYKEFO4KM3TXHPYYJ5I3GX'}\n","Grid 87\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_87_2017', 'priority': 100, 'creation_timestamp_ms': 1762658508782, 'update_timestamp_ms': 1762658508782, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GEFDNCSDWP4JQDVDRNNMTJDA', 'name': 'projects/ext-datasets/operations/GEFDNCSDWP4JQDVDRNNMTJDA'}\n","Grid 88\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_88_2017', 'priority': 100, 'creation_timestamp_ms': 1762658516796, 'update_timestamp_ms': 1762658516796, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HGMZ4DUJBVWIFRR2ZFMZN2MN', 'name': 'projects/ext-datasets/operations/HGMZ4DUJBVWIFRR2ZFMZN2MN'}\n","Grid 89\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_89_2017', 'priority': 100, 'creation_timestamp_ms': 1762658522030, 'update_timestamp_ms': 1762658522030, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VO5ODDADPHD456GDIW3PKDPF', 'name': 'projects/ext-datasets/operations/VO5ODDADPHD456GDIW3PKDPF'}\n","Grid 90\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_90_2017', 'priority': 100, 'creation_timestamp_ms': 1762658531380, 'update_timestamp_ms': 1762658531380, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RI3OI4JF5LXQUBZZA2N3QSSA', 'name': 'projects/ext-datasets/operations/RI3OI4JF5LXQUBZZA2N3QSSA'}\n","Grid 91\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_91_2017', 'priority': 100, 'creation_timestamp_ms': 1762658537526, 'update_timestamp_ms': 1762658537526, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YHYVHZDHXUO575ETOIOGZBR3', 'name': 'projects/ext-datasets/operations/YHYVHZDHXUO575ETOIOGZBR3'}\n","Grid 92\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_92_2017', 'priority': 100, 'creation_timestamp_ms': 1762658546056, 'update_timestamp_ms': 1762658546056, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DLENUAUSWOUI6FUO3NJK5S4X', 'name': 'projects/ext-datasets/operations/DLENUAUSWOUI6FUO3NJK5S4X'}\n","Grid 93\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_93_2017', 'priority': 100, 'creation_timestamp_ms': 1762658556367, 'update_timestamp_ms': 1762658556367, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKVBGIOHI3KOZG4AXLQJCA3V', 'name': 'projects/ext-datasets/operations/SKVBGIOHI3KOZG4AXLQJCA3V'}\n","Grid 94\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_94_2017', 'priority': 100, 'creation_timestamp_ms': 1762658566574, 'update_timestamp_ms': 1762658566574, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NBKVSQ6ACYIHYENK3RVO6PGH', 'name': 'projects/ext-datasets/operations/NBKVSQ6ACYIHYENK3RVO6PGH'}\n","Grid 95\n","curr_year 2017\n","Saving data for South 24 Parganas 2017\n","Task Started {'state': 'READY', 'description': 'South 24 Parganas_95_2017', 'priority': 100, 'creation_timestamp_ms': 1762658570912, 'update_timestamp_ms': 1762658570912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5UZ57KTGRFOTCHMAP2XZN6XK', 'name': 'projects/ext-datasets/operations/5UZ57KTGRFOTCHMAP2XZN6XK'}\n","9024.560793161392\n","Year 2017, District 27: Uttar Dinajpur, grids: 66\n","Grid 0\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_0_2017', 'priority': 100, 'creation_timestamp_ms': 1762658581084, 'update_timestamp_ms': 1762658581084, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LWBBTEE4OQIGGA2Y4G7QRL3B', 'name': 'projects/ext-datasets/operations/LWBBTEE4OQIGGA2Y4G7QRL3B'}\n","Grid 1\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_1_2017', 'priority': 100, 'creation_timestamp_ms': 1762658588334, 'update_timestamp_ms': 1762658588334, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BHSAMUE5CFJZXG7SAJW5PYEC', 'name': 'projects/ext-datasets/operations/BHSAMUE5CFJZXG7SAJW5PYEC'}\n","Grid 2\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_2_2017', 'priority': 100, 'creation_timestamp_ms': 1762658594508, 'update_timestamp_ms': 1762658594508, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJSG7LDRDUZP7PB2D6NVHWKZ', 'name': 'projects/ext-datasets/operations/HJSG7LDRDUZP7PB2D6NVHWKZ'}\n","Grid 3\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_3_2017', 'priority': 100, 'creation_timestamp_ms': 1762658601690, 'update_timestamp_ms': 1762658601690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DWN4F3MWMNC55ZAXAXJHRGQH', 'name': 'projects/ext-datasets/operations/DWN4F3MWMNC55ZAXAXJHRGQH'}\n","Grid 4\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_4_2017', 'priority': 100, 'creation_timestamp_ms': 1762658608815, 'update_timestamp_ms': 1762658608815, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GUB7CAGUPH4KVCETAVJWTDYO', 'name': 'projects/ext-datasets/operations/GUB7CAGUPH4KVCETAVJWTDYO'}\n","Grid 5\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_5_2017', 'priority': 100, 'creation_timestamp_ms': 1762658615599, 'update_timestamp_ms': 1762658615599, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SLB2C4FUXIRXA6URE7QN4BTX', 'name': 'projects/ext-datasets/operations/SLB2C4FUXIRXA6URE7QN4BTX'}\n","Grid 6\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_6_2017', 'priority': 100, 'creation_timestamp_ms': 1762658623281, 'update_timestamp_ms': 1762658623281, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7PD3J3HTW4S2CBVRTAC6FZU4', 'name': 'projects/ext-datasets/operations/7PD3J3HTW4S2CBVRTAC6FZU4'}\n","Grid 7\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_7_2017', 'priority': 100, 'creation_timestamp_ms': 1762658630336, 'update_timestamp_ms': 1762658630336, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64FLPTQ3NXI36KU5PZCN5PS4', 'name': 'projects/ext-datasets/operations/64FLPTQ3NXI36KU5PZCN5PS4'}\n","Grid 8\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_8_2017', 'priority': 100, 'creation_timestamp_ms': 1762658633947, 'update_timestamp_ms': 1762658633947, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C2EHZ5CD5I7HLQJQWH3LXVXV', 'name': 'projects/ext-datasets/operations/C2EHZ5CD5I7HLQJQWH3LXVXV'}\n","Grid 9\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_9_2017', 'priority': 100, 'creation_timestamp_ms': 1762658642174, 'update_timestamp_ms': 1762658642174, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K3HEMYXEBW3WHHPG42ITU2YJ', 'name': 'projects/ext-datasets/operations/K3HEMYXEBW3WHHPG42ITU2YJ'}\n","Grid 10\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_10_2017', 'priority': 100, 'creation_timestamp_ms': 1762658650276, 'update_timestamp_ms': 1762658650276, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MXRWUZPDO5OBXYJ7WYSSA5Y3', 'name': 'projects/ext-datasets/operations/MXRWUZPDO5OBXYJ7WYSSA5Y3'}\n","Grid 11\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_11_2017', 'priority': 100, 'creation_timestamp_ms': 1762658657079, 'update_timestamp_ms': 1762658657079, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G3TZ7XSDMMJ2GZRH5GST2NIK', 'name': 'projects/ext-datasets/operations/G3TZ7XSDMMJ2GZRH5GST2NIK'}\n","Grid 12\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_12_2017', 'priority': 100, 'creation_timestamp_ms': 1762658663272, 'update_timestamp_ms': 1762658663272, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BN6YANDTBN5D4X2MHJCJLCDA', 'name': 'projects/ext-datasets/operations/BN6YANDTBN5D4X2MHJCJLCDA'}\n","Grid 13\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_13_2017', 'priority': 100, 'creation_timestamp_ms': 1762658669155, 'update_timestamp_ms': 1762658669155, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VFIYOHFXLL4T5SAFBKZ7UVIE', 'name': 'projects/ext-datasets/operations/VFIYOHFXLL4T5SAFBKZ7UVIE'}\n","Grid 14\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_14_2017', 'priority': 100, 'creation_timestamp_ms': 1762658676404, 'update_timestamp_ms': 1762658676404, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KHQJVAUYSK5VIX3LBZXM2L5V', 'name': 'projects/ext-datasets/operations/KHQJVAUYSK5VIX3LBZXM2L5V'}\n","Grid 15\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_15_2017', 'priority': 100, 'creation_timestamp_ms': 1762658684794, 'update_timestamp_ms': 1762658684794, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37USBI46MJ7PZVUQARNBFLYH', 'name': 'projects/ext-datasets/operations/37USBI46MJ7PZVUQARNBFLYH'}\n","Grid 16\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_16_2017', 'priority': 100, 'creation_timestamp_ms': 1762658691124, 'update_timestamp_ms': 1762658691124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '27JZ2HGHE3UX2GM74QLKCOEF', 'name': 'projects/ext-datasets/operations/27JZ2HGHE3UX2GM74QLKCOEF'}\n","Grid 17\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_17_2017', 'priority': 100, 'creation_timestamp_ms': 1762658697706, 'update_timestamp_ms': 1762658697706, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JADLTSZXIRCL2EW6ZCWMLMNQ', 'name': 'projects/ext-datasets/operations/JADLTSZXIRCL2EW6ZCWMLMNQ'}\n","Grid 18\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_18_2017', 'priority': 100, 'creation_timestamp_ms': 1762658703798, 'update_timestamp_ms': 1762658703798, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'N7LJ5V3OD3SJ6BLRH2UH4YJ7', 'name': 'projects/ext-datasets/operations/N7LJ5V3OD3SJ6BLRH2UH4YJ7'}\n","Grid 19\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_19_2017', 'priority': 100, 'creation_timestamp_ms': 1762658709708, 'update_timestamp_ms': 1762658709708, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NPAHS4K5NYZX3ELW74FKPBMW', 'name': 'projects/ext-datasets/operations/NPAHS4K5NYZX3ELW74FKPBMW'}\n","Grid 20\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_20_2017', 'priority': 100, 'creation_timestamp_ms': 1762658716807, 'update_timestamp_ms': 1762658716807, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXDUGDJI5HVT27NP6X7KW2G7', 'name': 'projects/ext-datasets/operations/WXDUGDJI5HVT27NP6X7KW2G7'}\n","Grid 21\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_21_2017', 'priority': 100, 'creation_timestamp_ms': 1762658723206, 'update_timestamp_ms': 1762658723206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VVS75FZPKQVCHGVWSDTFLPOK', 'name': 'projects/ext-datasets/operations/VVS75FZPKQVCHGVWSDTFLPOK'}\n","Grid 22\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_22_2017', 'priority': 100, 'creation_timestamp_ms': 1762658730930, 'update_timestamp_ms': 1762658730930, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GCKIQVZTHE76DTQVIC35YHHI', 'name': 'projects/ext-datasets/operations/GCKIQVZTHE76DTQVIC35YHHI'}\n","Grid 23\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_23_2017', 'priority': 100, 'creation_timestamp_ms': 1762658739413, 'update_timestamp_ms': 1762658739413, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RQZFSXT2DG7GTPGPUDPDC5UU', 'name': 'projects/ext-datasets/operations/RQZFSXT2DG7GTPGPUDPDC5UU'}\n","Grid 24\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_24_2017', 'priority': 100, 'creation_timestamp_ms': 1762658746912, 'update_timestamp_ms': 1762658746912, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NKGQ373GYKR2GMNPF2GLAJJ', 'name': 'projects/ext-datasets/operations/4NKGQ373GYKR2GMNPF2GLAJJ'}\n","Grid 25\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_25_2017', 'priority': 100, 'creation_timestamp_ms': 1762658750696, 'update_timestamp_ms': 1762658750696, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JHI6WM74MZ42YEQ733U462QE', 'name': 'projects/ext-datasets/operations/JHI6WM74MZ42YEQ733U462QE'}\n","Grid 26\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_26_2017', 'priority': 100, 'creation_timestamp_ms': 1762658758452, 'update_timestamp_ms': 1762658758452, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UUGIT6D53J6YJ7XE2LDEPGTE', 'name': 'projects/ext-datasets/operations/UUGIT6D53J6YJ7XE2LDEPGTE'}\n","Grid 27\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_27_2017', 'priority': 100, 'creation_timestamp_ms': 1762658766194, 'update_timestamp_ms': 1762658766194, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GDHLVVLG5UMB6LZEQZDF2FNU', 'name': 'projects/ext-datasets/operations/GDHLVVLG5UMB6LZEQZDF2FNU'}\n","Grid 28\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_28_2017', 'priority': 100, 'creation_timestamp_ms': 1762658774377, 'update_timestamp_ms': 1762658774377, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GWI2DLEAB62E6LCEW5LBHAO5', 'name': 'projects/ext-datasets/operations/GWI2DLEAB62E6LCEW5LBHAO5'}\n","Grid 29\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_29_2017', 'priority': 100, 'creation_timestamp_ms': 1762658782303, 'update_timestamp_ms': 1762658782303, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CBYE4GRNC6K7QCDMSAPYHT5S', 'name': 'projects/ext-datasets/operations/CBYE4GRNC6K7QCDMSAPYHT5S'}\n","Grid 30\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_30_2017', 'priority': 100, 'creation_timestamp_ms': 1762658785716, 'update_timestamp_ms': 1762658785716, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4NLNMCX7B7Z5VVFCXGAIWR33', 'name': 'projects/ext-datasets/operations/4NLNMCX7B7Z5VVFCXGAIWR33'}\n","Grid 31\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_31_2017', 'priority': 100, 'creation_timestamp_ms': 1762658789331, 'update_timestamp_ms': 1762658789331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WMUMCQIC5YE4QL3BVUO3IFQK', 'name': 'projects/ext-datasets/operations/WMUMCQIC5YE4QL3BVUO3IFQK'}\n","Grid 32\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_32_2017', 'priority': 100, 'creation_timestamp_ms': 1762658796969, 'update_timestamp_ms': 1762658796969, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IYPPIKH6YWZC35QORRFLVMQ3', 'name': 'projects/ext-datasets/operations/IYPPIKH6YWZC35QORRFLVMQ3'}\n","Grid 33\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_33_2017', 'priority': 100, 'creation_timestamp_ms': 1762658803366, 'update_timestamp_ms': 1762658803366, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JBCZMUD2BSIALH3AFWH5ZLKC', 'name': 'projects/ext-datasets/operations/JBCZMUD2BSIALH3AFWH5ZLKC'}\n","Grid 34\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_34_2017', 'priority': 100, 'creation_timestamp_ms': 1762658809327, 'update_timestamp_ms': 1762658809327, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JT3RHD7AG6MVTDFV5V3WOXL7', 'name': 'projects/ext-datasets/operations/JT3RHD7AG6MVTDFV5V3WOXL7'}\n","Grid 35\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_35_2017', 'priority': 100, 'creation_timestamp_ms': 1762658813413, 'update_timestamp_ms': 1762658813413, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7UOFD5XSRLBJS632WJQ4CZBA', 'name': 'projects/ext-datasets/operations/7UOFD5XSRLBJS632WJQ4CZBA'}\n","Grid 36\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_36_2017', 'priority': 100, 'creation_timestamp_ms': 1762658821265, 'update_timestamp_ms': 1762658821265, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GVZXFZ6IJZGHRGPPXMGBSNUE', 'name': 'projects/ext-datasets/operations/GVZXFZ6IJZGHRGPPXMGBSNUE'}\n","Grid 37\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_37_2017', 'priority': 100, 'creation_timestamp_ms': 1762658828449, 'update_timestamp_ms': 1762658828449, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MSAJDQF7DT75USRI3SOVBJJC', 'name': 'projects/ext-datasets/operations/MSAJDQF7DT75USRI3SOVBJJC'}\n","Grid 38\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_38_2017', 'priority': 100, 'creation_timestamp_ms': 1762658837439, 'update_timestamp_ms': 1762658837439, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JFEWEUKOQQD625JX3LLCJOND', 'name': 'projects/ext-datasets/operations/JFEWEUKOQQD625JX3LLCJOND'}\n","Grid 39\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_39_2017', 'priority': 100, 'creation_timestamp_ms': 1762658844483, 'update_timestamp_ms': 1762658844483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OKNWNZH6N7WXC6JZOJ3IYXZQ', 'name': 'projects/ext-datasets/operations/OKNWNZH6N7WXC6JZOJ3IYXZQ'}\n","Grid 40\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_40_2017', 'priority': 100, 'creation_timestamp_ms': 1762658850942, 'update_timestamp_ms': 1762658850942, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AGQGE46OLJA2Q57YAUBTBOTO', 'name': 'projects/ext-datasets/operations/AGQGE46OLJA2Q57YAUBTBOTO'}\n","Grid 41\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_41_2017', 'priority': 100, 'creation_timestamp_ms': 1762658855118, 'update_timestamp_ms': 1762658855118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7TALTC5S5CMHMX5QSRE33QOY', 'name': 'projects/ext-datasets/operations/7TALTC5S5CMHMX5QSRE33QOY'}\n","Grid 42\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_42_2017', 'priority': 100, 'creation_timestamp_ms': 1762658861803, 'update_timestamp_ms': 1762658861803, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FNIC44O2HXFHWDANPRNECDFZ', 'name': 'projects/ext-datasets/operations/FNIC44O2HXFHWDANPRNECDFZ'}\n","Grid 43\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_43_2017', 'priority': 100, 'creation_timestamp_ms': 1762658865791, 'update_timestamp_ms': 1762658865791, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7APNDRV5FSXSL5RFPHI4XWU3', 'name': 'projects/ext-datasets/operations/7APNDRV5FSXSL5RFPHI4XWU3'}\n","Grid 44\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_44_2017', 'priority': 100, 'creation_timestamp_ms': 1762658872691, 'update_timestamp_ms': 1762658872691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I5VL7VQMHOJJH22N3WBRKBVF', 'name': 'projects/ext-datasets/operations/I5VL7VQMHOJJH22N3WBRKBVF'}\n","Grid 45\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_45_2017', 'priority': 100, 'creation_timestamp_ms': 1762658879662, 'update_timestamp_ms': 1762658879662, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GXAMQXEATIK6LMLCEQZTNFED', 'name': 'projects/ext-datasets/operations/GXAMQXEATIK6LMLCEQZTNFED'}\n","Grid 46\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_46_2017', 'priority': 100, 'creation_timestamp_ms': 1762658888622, 'update_timestamp_ms': 1762658888622, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JV2Z6W5AYFCKYAFMUI4MBUD7', 'name': 'projects/ext-datasets/operations/JV2Z6W5AYFCKYAFMUI4MBUD7'}\n","Grid 47\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_47_2017', 'priority': 100, 'creation_timestamp_ms': 1762658892549, 'update_timestamp_ms': 1762658892549, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2BF62FDMDSTNPOG3X7VVX2LD', 'name': 'projects/ext-datasets/operations/2BF62FDMDSTNPOG3X7VVX2LD'}\n","Grid 48\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_48_2017', 'priority': 100, 'creation_timestamp_ms': 1762658900175, 'update_timestamp_ms': 1762658900175, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WNBRTHWUPCGZNG6FGEBD6CRE', 'name': 'projects/ext-datasets/operations/WNBRTHWUPCGZNG6FGEBD6CRE'}\n","Grid 49\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_49_2017', 'priority': 100, 'creation_timestamp_ms': 1762658907619, 'update_timestamp_ms': 1762658907619, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DN2S6XIWJLNH5BR7XTXH5347', 'name': 'projects/ext-datasets/operations/DN2S6XIWJLNH5BR7XTXH5347'}\n","Grid 50\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_50_2017', 'priority': 100, 'creation_timestamp_ms': 1762658917967, 'update_timestamp_ms': 1762658917967, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HUUEVATPM33A4GCBAXMVGDY5', 'name': 'projects/ext-datasets/operations/HUUEVATPM33A4GCBAXMVGDY5'}\n","Grid 51\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_51_2017', 'priority': 100, 'creation_timestamp_ms': 1762658927085, 'update_timestamp_ms': 1762658927085, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F7K2YZTMLLWN36CJFTEYXSTQ', 'name': 'projects/ext-datasets/operations/F7K2YZTMLLWN36CJFTEYXSTQ'}\n","Grid 52\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_52_2017', 'priority': 100, 'creation_timestamp_ms': 1762658933729, 'update_timestamp_ms': 1762658933729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RQL6KODKDQADPIVIYZOE6PSW', 'name': 'projects/ext-datasets/operations/RQL6KODKDQADPIVIYZOE6PSW'}\n","Grid 53\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_53_2017', 'priority': 100, 'creation_timestamp_ms': 1762658940194, 'update_timestamp_ms': 1762658940194, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K44YFWNUH4QCSZL3SDBFRCZL', 'name': 'projects/ext-datasets/operations/K44YFWNUH4QCSZL3SDBFRCZL'}\n","Grid 54\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_54_2017', 'priority': 100, 'creation_timestamp_ms': 1762658947968, 'update_timestamp_ms': 1762658947968, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T5NSIUR2WZQ3U4V2RCNMGSP4', 'name': 'projects/ext-datasets/operations/T5NSIUR2WZQ3U4V2RCNMGSP4'}\n","Grid 55\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_55_2017', 'priority': 100, 'creation_timestamp_ms': 1762658953425, 'update_timestamp_ms': 1762658953425, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SZ6MS7SNXMTXJZW746CFWWGV', 'name': 'projects/ext-datasets/operations/SZ6MS7SNXMTXJZW746CFWWGV'}\n","Grid 56\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_56_2017', 'priority': 100, 'creation_timestamp_ms': 1762658961614, 'update_timestamp_ms': 1762658961614, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AJTUSASAQMI7U4YPJJYT4MZO', 'name': 'projects/ext-datasets/operations/AJTUSASAQMI7U4YPJJYT4MZO'}\n","Grid 57\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_57_2017', 'priority': 100, 'creation_timestamp_ms': 1762658967654, 'update_timestamp_ms': 1762658967654, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BILBZACEENGO3RQ6KYFOWFIA', 'name': 'projects/ext-datasets/operations/BILBZACEENGO3RQ6KYFOWFIA'}\n","Grid 58\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_58_2017', 'priority': 100, 'creation_timestamp_ms': 1762658975388, 'update_timestamp_ms': 1762658975388, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '37DNBONKMZIWZBDFL5BB5SKB', 'name': 'projects/ext-datasets/operations/37DNBONKMZIWZBDFL5BB5SKB'}\n","Grid 59\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_59_2017', 'priority': 100, 'creation_timestamp_ms': 1762658982841, 'update_timestamp_ms': 1762658982841, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QCBCXIOSOXSCIJD65Z5RGS37', 'name': 'projects/ext-datasets/operations/QCBCXIOSOXSCIJD65Z5RGS37'}\n","Grid 60\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_60_2017', 'priority': 100, 'creation_timestamp_ms': 1762658990571, 'update_timestamp_ms': 1762658990571, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FB73R4TB2R3G4GJF6LSHX3ZO', 'name': 'projects/ext-datasets/operations/FB73R4TB2R3G4GJF6LSHX3ZO'}\n","Grid 61\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_61_2017', 'priority': 100, 'creation_timestamp_ms': 1762658998607, 'update_timestamp_ms': 1762658998607, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZIRZZXEGFVINO7RXYB2LTBM2', 'name': 'projects/ext-datasets/operations/ZIRZZXEGFVINO7RXYB2LTBM2'}\n","Grid 62\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_62_2017', 'priority': 100, 'creation_timestamp_ms': 1762659006326, 'update_timestamp_ms': 1762659006326, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '64BFXGRHWSYYKXTST73LWYTW', 'name': 'projects/ext-datasets/operations/64BFXGRHWSYYKXTST73LWYTW'}\n","Grid 63\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_63_2017', 'priority': 100, 'creation_timestamp_ms': 1762659013764, 'update_timestamp_ms': 1762659013764, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HH6Y7YEXLISJDIJNZFKMPOEO', 'name': 'projects/ext-datasets/operations/HH6Y7YEXLISJDIJNZFKMPOEO'}\n","Grid 64\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_64_2017', 'priority': 100, 'creation_timestamp_ms': 1762659020719, 'update_timestamp_ms': 1762659020719, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XQQ6EU4UYTNRJODBMWAAFA4P', 'name': 'projects/ext-datasets/operations/XQQ6EU4UYTNRJODBMWAAFA4P'}\n","Grid 65\n","curr_year 2017\n","Saving data for Uttar Dinajpur 2017\n","Task Started {'state': 'READY', 'description': 'Uttar Dinajpur_65_2017', 'priority': 100, 'creation_timestamp_ms': 1762659024344, 'update_timestamp_ms': 1762659024344, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6TVDYRTPWWZMLGBIE72ZVFYF', 'name': 'projects/ext-datasets/operations/6TVDYRTPWWZMLGBIE72ZVFYF'}\n","9478.025090694427\n","Waiting for last task to be completed...\n","Last task completed!\n","Total Time Taken: 11691.838443040848\n","Year 2018, District 0: Katihar, grids: 19\n","Grid 0\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661252729, 'update_timestamp_ms': 1762661252729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QZYGCE6PYALXOSC6G5TLKOI6', 'name': 'projects/ext-datasets/operations/QZYGCE6PYALXOSC6G5TLKOI6'}\n","Grid 1\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661259693, 'update_timestamp_ms': 1762661259693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L3CIIC4YVERUFHLZNGFQEM4U', 'name': 'projects/ext-datasets/operations/L3CIIC4YVERUFHLZNGFQEM4U'}\n","Grid 2\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661267234, 'update_timestamp_ms': 1762661267234, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GOOX3NPYWEG6DDKPVNUKDJMB', 'name': 'projects/ext-datasets/operations/GOOX3NPYWEG6DDKPVNUKDJMB'}\n","Grid 3\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661274822, 'update_timestamp_ms': 1762661274822, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7ENBZUSAEQTSFS5C5YYK3KD3', 'name': 'projects/ext-datasets/operations/7ENBZUSAEQTSFS5C5YYK3KD3'}\n","Grid 4\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661280196, 'update_timestamp_ms': 1762661280196, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7TAH2FLF5OCUW3YH4NNB2DHK', 'name': 'projects/ext-datasets/operations/7TAH2FLF5OCUW3YH4NNB2DHK'}\n","Grid 5\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661287279, 'update_timestamp_ms': 1762661287279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2BAQGYWIEAEMIGF6FN5AHHYG', 'name': 'projects/ext-datasets/operations/2BAQGYWIEAEMIGF6FN5AHHYG'}\n","Grid 6\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661296008, 'update_timestamp_ms': 1762661296008, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FTW23QJPOIKIX2VXB2GNIEKH', 'name': 'projects/ext-datasets/operations/FTW23QJPOIKIX2VXB2GNIEKH'}\n","Grid 7\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661305453, 'update_timestamp_ms': 1762661305453, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CD4HQSTQX6J3TRY4YPIPGVKB', 'name': 'projects/ext-datasets/operations/CD4HQSTQX6J3TRY4YPIPGVKB'}\n","Grid 8\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661314221, 'update_timestamp_ms': 1762661314221, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H4PRAJ3M6CCRVVSEMVIXFH7W', 'name': 'projects/ext-datasets/operations/H4PRAJ3M6CCRVVSEMVIXFH7W'}\n","Grid 9\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661321253, 'update_timestamp_ms': 1762661321253, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HJFXG5X4NLN4L6LWRJTM3VJO', 'name': 'projects/ext-datasets/operations/HJFXG5X4NLN4L6LWRJTM3VJO'}\n","Grid 10\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762661332720, 'update_timestamp_ms': 1762661332720, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NU63UW3MPLA4JJGQQRKOSE6M', 'name': 'projects/ext-datasets/operations/NU63UW3MPLA4JJGQQRKOSE6M'}\n","Grid 11\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762661341394, 'update_timestamp_ms': 1762661341394, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WY3BYQEUH54SKBD45DIS34CA', 'name': 'projects/ext-datasets/operations/WY3BYQEUH54SKBD45DIS34CA'}\n","Grid 12\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762661352695, 'update_timestamp_ms': 1762661352695, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7OJ5DW76UPJK4UDHOHAWSVC6', 'name': 'projects/ext-datasets/operations/7OJ5DW76UPJK4UDHOHAWSVC6'}\n","Grid 13\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762661359445, 'update_timestamp_ms': 1762661359445, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2WSTPBBVG46QES57363AHD7H', 'name': 'projects/ext-datasets/operations/2WSTPBBVG46QES57363AHD7H'}\n","Grid 14\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762661366850, 'update_timestamp_ms': 1762661366850, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CAGRXMPLNFMCRNIYR3NL4LQ6', 'name': 'projects/ext-datasets/operations/CAGRXMPLNFMCRNIYR3NL4LQ6'}\n","Grid 15\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762661372170, 'update_timestamp_ms': 1762661372170, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2ZGWCRGI3FMR4KUP7FNKGKYU', 'name': 'projects/ext-datasets/operations/2ZGWCRGI3FMR4KUP7FNKGKYU'}\n","Grid 16\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762661382418, 'update_timestamp_ms': 1762661382418, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RHSDM6ITGIDAIEJLVYOKERGB', 'name': 'projects/ext-datasets/operations/RHSDM6ITGIDAIEJLVYOKERGB'}\n","Grid 17\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762661390989, 'update_timestamp_ms': 1762661390989, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M2Y3DIHVC2HQBLRT4CV5N34Y', 'name': 'projects/ext-datasets/operations/M2Y3DIHVC2HQBLRT4CV5N34Y'}\n","Grid 18\n","curr_year 2018\n","Saving data for Katihar 2018\n","Task Started {'state': 'READY', 'description': 'Katihar_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762661401527, 'update_timestamp_ms': 1762661401527, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EWPEIRAXHB7QVSU36K4VV4TF', 'name': 'projects/ext-datasets/operations/EWPEIRAXHB7QVSU36K4VV4TF'}\n","163.33016920089722\n","Year 2018, District 1: Kishanganj, grids: 10\n","Grid 0\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661415169, 'update_timestamp_ms': 1762661415169, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TYPM26KHDKOTX5PMCOW4H5ZB', 'name': 'projects/ext-datasets/operations/TYPM26KHDKOTX5PMCOW4H5ZB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661421109, 'update_timestamp_ms': 1762661421109, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J7TRPDWY6BSAWHBDSV24FN6E', 'name': 'projects/ext-datasets/operations/J7TRPDWY6BSAWHBDSV24FN6E'}\n","Grid 2\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661428144, 'update_timestamp_ms': 1762661428144, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BUOJ7O7EF34JDCMOEVJSN2B7', 'name': 'projects/ext-datasets/operations/BUOJ7O7EF34JDCMOEVJSN2B7'}\n","Grid 3\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661432097, 'update_timestamp_ms': 1762661432097, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2MN4L7QBRR2JYQDFGCHRCNIV', 'name': 'projects/ext-datasets/operations/2MN4L7QBRR2JYQDFGCHRCNIV'}\n","Grid 4\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661441672, 'update_timestamp_ms': 1762661441672, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XFPIJKAZOPHDNLTCO667NCMM', 'name': 'projects/ext-datasets/operations/XFPIJKAZOPHDNLTCO667NCMM'}\n","Grid 5\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661446896, 'update_timestamp_ms': 1762661446896, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E5MGGGYFNMJFD3CQG77ZXZVN', 'name': 'projects/ext-datasets/operations/E5MGGGYFNMJFD3CQG77ZXZVN'}\n","Grid 6\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661454885, 'update_timestamp_ms': 1762661454885, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2RMT6KWPNLRPZK6UHA5CZV4G', 'name': 'projects/ext-datasets/operations/2RMT6KWPNLRPZK6UHA5CZV4G'}\n","Grid 7\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661461899, 'update_timestamp_ms': 1762661461899, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y3DACYNR4M7LWTQBJZTSHSMM', 'name': 'projects/ext-datasets/operations/Y3DACYNR4M7LWTQBJZTSHSMM'}\n","Grid 8\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661465511, 'update_timestamp_ms': 1762661465511, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFY7YVTGEWUCBE23HC6MBRTJ', 'name': 'projects/ext-datasets/operations/IFY7YVTGEWUCBE23HC6MBRTJ'}\n","Grid 9\n","curr_year 2018\n","Saving data for Kishanganj 2018\n","Task Started {'state': 'READY', 'description': 'Kishanganj_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661472919, 'update_timestamp_ms': 1762661472919, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BSACOQVJ5JRL55WG3XCZKURD', 'name': 'projects/ext-datasets/operations/BSACOQVJ5JRL55WG3XCZKURD'}\n","234.68766856193542\n","Year 2018, District 2: Purnia, grids: 1\n","Grid 0\n","curr_year 2018\n","Saving data for Purnia 2018\n","Task Started {'state': 'READY', 'description': 'Purnia_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661485177, 'update_timestamp_ms': 1762661485177, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LM7HP5TLZDFEPZ5JILM3GOTQ', 'name': 'projects/ext-datasets/operations/LM7HP5TLZDFEPZ5JILM3GOTQ'}\n","247.0161051750183\n","Year 2018, District 3: Dhanbad, grids: 1\n","Grid 0\n","curr_year 2018\n","Saving data for Dhanbad 2018\n","Task Started {'state': 'READY', 'description': 'Dhanbad_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661499029, 'update_timestamp_ms': 1762661499029, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RRQEKBZFOZAUYQGOOFRTJZKW', 'name': 'projects/ext-datasets/operations/RRQEKBZFOZAUYQGOOFRTJZKW'}\n","260.836279630661\n","Year 2018, District 4: Dumka, grids: 10\n","Grid 0\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661514902, 'update_timestamp_ms': 1762661514902, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QOHMMPKJFBL62RJZT34C2KWO', 'name': 'projects/ext-datasets/operations/QOHMMPKJFBL62RJZT34C2KWO'}\n","Grid 1\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661518980, 'update_timestamp_ms': 1762661518980, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WUDELHZTOL7ULYA6E6BAMJCG', 'name': 'projects/ext-datasets/operations/WUDELHZTOL7ULYA6E6BAMJCG'}\n","Grid 2\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661530802, 'update_timestamp_ms': 1762661530802, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y22SNWU3GELIZSVXNANU6T7T', 'name': 'projects/ext-datasets/operations/Y22SNWU3GELIZSVXNANU6T7T'}\n","Grid 3\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661538964, 'update_timestamp_ms': 1762661538964, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NEVS5TJX4XXIFSUUR2X5XBRD', 'name': 'projects/ext-datasets/operations/NEVS5TJX4XXIFSUUR2X5XBRD'}\n","Grid 4\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661545993, 'update_timestamp_ms': 1762661545993, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PEFVP6IGH6UZZW4OUNRWHONY', 'name': 'projects/ext-datasets/operations/PEFVP6IGH6UZZW4OUNRWHONY'}\n","Grid 5\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661552503, 'update_timestamp_ms': 1762661552503, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4GHVZWFUEPUIF26V7XEFCLXS', 'name': 'projects/ext-datasets/operations/4GHVZWFUEPUIF26V7XEFCLXS'}\n","Grid 6\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661561607, 'update_timestamp_ms': 1762661561607, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W4JBXELP2EUVFLWBYAGIOHBU', 'name': 'projects/ext-datasets/operations/W4JBXELP2EUVFLWBYAGIOHBU'}\n","Grid 7\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661572246, 'update_timestamp_ms': 1762661572246, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TUCB6WINJJKOI4VLONISQKRE', 'name': 'projects/ext-datasets/operations/TUCB6WINJJKOI4VLONISQKRE'}\n","Grid 8\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661579463, 'update_timestamp_ms': 1762661579463, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZTFPY63GXGI3H7VTPYAYUR6', 'name': 'projects/ext-datasets/operations/KZTFPY63GXGI3H7VTPYAYUR6'}\n","Grid 9\n","curr_year 2018\n","Saving data for Dumka 2018\n","Task Started {'state': 'READY', 'description': 'Dumka_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661587387, 'update_timestamp_ms': 1762661587387, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2Q4RZNFEG6UUONYB2X732F2U', 'name': 'projects/ext-datasets/operations/2Q4RZNFEG6UUONYB2X732F2U'}\n","349.2098460197449\n","Year 2018, District 5: Jamtara, grids: 10\n","Grid 0\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661600247, 'update_timestamp_ms': 1762661600247, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJXDMS2BXKXQ2L23P7KNW7XZ', 'name': 'projects/ext-datasets/operations/YJXDMS2BXKXQ2L23P7KNW7XZ'}\n","Grid 1\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661606632, 'update_timestamp_ms': 1762661606632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T5G3XPYJXY5QKXTEA7ZR25GS', 'name': 'projects/ext-datasets/operations/T5G3XPYJXY5QKXTEA7ZR25GS'}\n","Grid 2\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661613674, 'update_timestamp_ms': 1762661613674, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZD2QQCCV2JDR5BIHPHBQ7IE7', 'name': 'projects/ext-datasets/operations/ZD2QQCCV2JDR5BIHPHBQ7IE7'}\n","Grid 3\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661618531, 'update_timestamp_ms': 1762661618531, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MHWU4GXXNN72JNKE22DR6YGW', 'name': 'projects/ext-datasets/operations/MHWU4GXXNN72JNKE22DR6YGW'}\n","Grid 4\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661624769, 'update_timestamp_ms': 1762661624769, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CST5KGZVVXEBKR2HKWIY7AKU', 'name': 'projects/ext-datasets/operations/CST5KGZVVXEBKR2HKWIY7AKU'}\n","Grid 5\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661633742, 'update_timestamp_ms': 1762661633742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ONT23X7JJYEXWUHRKMZ2GZ35', 'name': 'projects/ext-datasets/operations/ONT23X7JJYEXWUHRKMZ2GZ35'}\n","Grid 6\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661639755, 'update_timestamp_ms': 1762661639755, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6KMZH673OJ5AX24CK3EB2QK', 'name': 'projects/ext-datasets/operations/X6KMZH673OJ5AX24CK3EB2QK'}\n","Grid 7\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661647080, 'update_timestamp_ms': 1762661647080, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7RHETXCLH4HDS2MEHSLAHEMJ', 'name': 'projects/ext-datasets/operations/7RHETXCLH4HDS2MEHSLAHEMJ'}\n","Grid 8\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661653205, 'update_timestamp_ms': 1762661653205, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZRHL4I7DKZ6SBAW5PQPZ4YE4', 'name': 'projects/ext-datasets/operations/ZRHL4I7DKZ6SBAW5PQPZ4YE4'}\n","Grid 9\n","curr_year 2018\n","Saving data for Jamtara 2018\n","Task Started {'state': 'READY', 'description': 'Jamtara_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661660269, 'update_timestamp_ms': 1762661660269, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X3FZARIGPQAZS47Z2IR2TAZ7', 'name': 'projects/ext-datasets/operations/X3FZARIGPQAZS47Z2IR2TAZ7'}\n","422.12399768829346\n","Year 2018, District 6: Pakur, grids: 9\n","Grid 0\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661674965, 'update_timestamp_ms': 1762661674965, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E7QLVYQOYAQJ3B5GOVQ6YYGB', 'name': 'projects/ext-datasets/operations/E7QLVYQOYAQJ3B5GOVQ6YYGB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661682558, 'update_timestamp_ms': 1762661682558, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7B5AQSQKKOSPEXVXYXS6PC25', 'name': 'projects/ext-datasets/operations/7B5AQSQKKOSPEXVXYXS6PC25'}\n","Grid 2\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661690346, 'update_timestamp_ms': 1762661690346, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UNZA2L3HLU6URA3K7HDTPPVP', 'name': 'projects/ext-datasets/operations/UNZA2L3HLU6URA3K7HDTPPVP'}\n","Grid 3\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661697664, 'update_timestamp_ms': 1762661697664, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MT66LGWDTK4UX7DMVMUU5SS2', 'name': 'projects/ext-datasets/operations/MT66LGWDTK4UX7DMVMUU5SS2'}\n","Grid 4\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661704964, 'update_timestamp_ms': 1762661704964, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LBFAI47IEXNQ5TSK2J45QCY2', 'name': 'projects/ext-datasets/operations/LBFAI47IEXNQ5TSK2J45QCY2'}\n","Grid 5\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661712665, 'update_timestamp_ms': 1762661712665, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FIULRPUSO3BYRBXQFOLOEQDG', 'name': 'projects/ext-datasets/operations/FIULRPUSO3BYRBXQFOLOEQDG'}\n","Grid 6\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661720057, 'update_timestamp_ms': 1762661720057, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ERQPB7GQQGXTWY45ZE6CGYQF', 'name': 'projects/ext-datasets/operations/ERQPB7GQQGXTWY45ZE6CGYQF'}\n","Grid 7\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661726493, 'update_timestamp_ms': 1762661726493, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QSLTLOQ7I2UDADZHY6XXYYRK', 'name': 'projects/ext-datasets/operations/QSLTLOQ7I2UDADZHY6XXYYRK'}\n","Grid 8\n","curr_year 2018\n","Saving data for Pakur 2018\n","Task Started {'state': 'READY', 'description': 'Pakur_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661732594, 'update_timestamp_ms': 1762661732594, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMYAEH5TNIX3ZSDJG7QUVLKE', 'name': 'projects/ext-datasets/operations/BMYAEH5TNIX3ZSDJG7QUVLKE'}\n","494.41910696029663\n","Year 2018, District 7: Purbi Singhbhum, grids: 5\n","Grid 0\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661746443, 'update_timestamp_ms': 1762661746443, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GSWALEKV4U7XTT44QJN4WEJF', 'name': 'projects/ext-datasets/operations/GSWALEKV4U7XTT44QJN4WEJF'}\n","Grid 1\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661755192, 'update_timestamp_ms': 1762661755192, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MW53TQGCIDDQMMB4YP54N2JK', 'name': 'projects/ext-datasets/operations/MW53TQGCIDDQMMB4YP54N2JK'}\n","Grid 2\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661762624, 'update_timestamp_ms': 1762661762624, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WKY2SNAKLC746OIREBOGKAAO', 'name': 'projects/ext-datasets/operations/WKY2SNAKLC746OIREBOGKAAO'}\n","Grid 3\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661770052, 'update_timestamp_ms': 1762661770052, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DGZRO2CORNNYUFATKIO5PNBT', 'name': 'projects/ext-datasets/operations/DGZRO2CORNNYUFATKIO5PNBT'}\n","Grid 4\n","curr_year 2018\n","Saving data for Purbi Singhbhum 2018\n","Task Started {'state': 'READY', 'description': 'Purbi Singhbhum_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661774151, 'update_timestamp_ms': 1762661774151, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RFKUIDHDBBG3WU6C27YSXET4', 'name': 'projects/ext-datasets/operations/RFKUIDHDBBG3WU6C27YSXET4'}\n","535.9720237255096\n","Year 2018, District 8: Sahibganj, grids: 3\n","Grid 0\n","curr_year 2018\n","Saving data for Sahibganj 2018\n","Task Started {'state': 'READY', 'description': 'Sahibganj_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661786676, 'update_timestamp_ms': 1762661786676, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OQIR2E6NJ65MF6PGXJWYWN7A', 'name': 'projects/ext-datasets/operations/OQIR2E6NJ65MF6PGXJWYWN7A'}\n","Grid 1\n","curr_year 2018\n","Saving data for Sahibganj 2018\n","Task Started {'state': 'READY', 'description': 'Sahibganj_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661793641, 'update_timestamp_ms': 1762661793641, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SFO53APFK6KST2IDAJ4357TS', 'name': 'projects/ext-datasets/operations/SFO53APFK6KST2IDAJ4357TS'}\n","Grid 2\n","curr_year 2018\n","Saving data for Sahibganj 2018\n","Task Started {'state': 'READY', 'description': 'Sahibganj_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661797304, 'update_timestamp_ms': 1762661797304, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D5OPSTNUNJUCQVDPLYN4F5D7', 'name': 'projects/ext-datasets/operations/D5OPSTNUNJUCQVDPLYN4F5D7'}\n","559.1519672870636\n","Year 2018, District 9: Baleshwar, grids: 6\n","Grid 0\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661811076, 'update_timestamp_ms': 1762661811076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TDYSXAPAI3DCCCPVWMQMYKBB', 'name': 'projects/ext-datasets/operations/TDYSXAPAI3DCCCPVWMQMYKBB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661817544, 'update_timestamp_ms': 1762661817544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B7WL3G2LPT3AGTBTPUFTR5WV', 'name': 'projects/ext-datasets/operations/B7WL3G2LPT3AGTBTPUFTR5WV'}\n","Grid 2\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661824254, 'update_timestamp_ms': 1762661824254, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B2YI4U7QZLL2EP5WK5K44QX2', 'name': 'projects/ext-datasets/operations/B2YI4U7QZLL2EP5WK5K44QX2'}\n","Grid 3\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661831304, 'update_timestamp_ms': 1762661831304, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y7SNA5MEAGB7OVYCJWYGQBD4', 'name': 'projects/ext-datasets/operations/Y7SNA5MEAGB7OVYCJWYGQBD4'}\n","Grid 4\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661836824, 'update_timestamp_ms': 1762661836824, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VWSA2I55M76LO5CYQRSNPGRZ', 'name': 'projects/ext-datasets/operations/VWSA2I55M76LO5CYQRSNPGRZ'}\n","Grid 5\n","curr_year 2018\n","Saving data for Baleshwar 2018\n","Task Started {'state': 'READY', 'description': 'Baleshwar_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661844389, 'update_timestamp_ms': 1762661844389, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2NP4JS264MKWYZS3M76QIJO5', 'name': 'projects/ext-datasets/operations/2NP4JS264MKWYZS3M76QIJO5'}\n","606.1991579532623\n","Year 2018, District 10: Mayurbhanj, grids: 1\n","Grid 0\n","curr_year 2018\n","Saving data for Mayurbhanj 2018\n","Task Started {'state': 'READY', 'description': 'Mayurbhanj_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661856879, 'update_timestamp_ms': 1762661856879, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R5CLPSQU5NDLKRR325ZAUNXC', 'name': 'projects/ext-datasets/operations/R5CLPSQU5NDLKRR325ZAUNXC'}\n","618.6984307765961\n","Year 2018, District 11: Bankura, grids: 103\n","Grid 0\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762661869759, 'update_timestamp_ms': 1762661869759, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'STAYYI6TSZPO5B4SQ6VC75WZ', 'name': 'projects/ext-datasets/operations/STAYYI6TSZPO5B4SQ6VC75WZ'}\n","Grid 1\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762661876752, 'update_timestamp_ms': 1762661876752, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '72IZWFUBEQ7A65W3ZWXIRW3G', 'name': 'projects/ext-datasets/operations/72IZWFUBEQ7A65W3ZWXIRW3G'}\n","Grid 2\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762661883727, 'update_timestamp_ms': 1762661883727, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZRN6K3MXNMIWACKXAPDLDXJC', 'name': 'projects/ext-datasets/operations/ZRN6K3MXNMIWACKXAPDLDXJC'}\n","Grid 3\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762661894436, 'update_timestamp_ms': 1762661894436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UCNY4CKHOYZXT453ZSL73MY5', 'name': 'projects/ext-datasets/operations/UCNY4CKHOYZXT453ZSL73MY5'}\n","Grid 4\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762661906210, 'update_timestamp_ms': 1762661906210, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MYRHJGIENHTG5AU4ES6WRCLC', 'name': 'projects/ext-datasets/operations/MYRHJGIENHTG5AU4ES6WRCLC'}\n","Grid 5\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762661914342, 'update_timestamp_ms': 1762661914342, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LLZCTPRO3HBFQL5YWECLYMBP', 'name': 'projects/ext-datasets/operations/LLZCTPRO3HBFQL5YWECLYMBP'}\n","Grid 6\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762661922702, 'update_timestamp_ms': 1762661922702, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JN5IV7RAI5ZBV26EWSS2UGJ2', 'name': 'projects/ext-datasets/operations/JN5IV7RAI5ZBV26EWSS2UGJ2'}\n","Grid 7\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762661930590, 'update_timestamp_ms': 1762661930590, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A3CAKZIZEU2SKK7CPHJ5DU3K', 'name': 'projects/ext-datasets/operations/A3CAKZIZEU2SKK7CPHJ5DU3K'}\n","Grid 8\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762661940164, 'update_timestamp_ms': 1762661940164, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TUV5FFETPUUO7GV4MPAITY64', 'name': 'projects/ext-datasets/operations/TUV5FFETPUUO7GV4MPAITY64'}\n","Grid 9\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762661948118, 'update_timestamp_ms': 1762661948118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O5DGHSRQLFSQVSYZJL6C4RFA', 'name': 'projects/ext-datasets/operations/O5DGHSRQLFSQVSYZJL6C4RFA'}\n","Grid 10\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762661955447, 'update_timestamp_ms': 1762661955447, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XBLOKBW7CZ3DQHPHQVTTNZ7B', 'name': 'projects/ext-datasets/operations/XBLOKBW7CZ3DQHPHQVTTNZ7B'}\n","Grid 11\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762661965446, 'update_timestamp_ms': 1762661965446, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7B7LSH3Q5TGUJZXWCZFEUDR6', 'name': 'projects/ext-datasets/operations/7B7LSH3Q5TGUJZXWCZFEUDR6'}\n","Grid 12\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762661971465, 'update_timestamp_ms': 1762661971465, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AG7MEOWAL22OZPTNULN4JVAL', 'name': 'projects/ext-datasets/operations/AG7MEOWAL22OZPTNULN4JVAL'}\n","Grid 13\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762661982035, 'update_timestamp_ms': 1762661982035, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6OIFXZZ7S5OWH5T26Y7TUDH2', 'name': 'projects/ext-datasets/operations/6OIFXZZ7S5OWH5T26Y7TUDH2'}\n","Grid 14\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762661987865, 'update_timestamp_ms': 1762661987865, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LBZIJZUR2GC67OYO226JFC5S', 'name': 'projects/ext-datasets/operations/LBZIJZUR2GC67OYO226JFC5S'}\n","Grid 15\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762661996872, 'update_timestamp_ms': 1762661996872, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IJG5VRFWREKO3OA73EAYGFQH', 'name': 'projects/ext-datasets/operations/IJG5VRFWREKO3OA73EAYGFQH'}\n","Grid 16\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762662002369, 'update_timestamp_ms': 1762662002369, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IHQZ4Q3HVNEI3HRRVBHYQ5UU', 'name': 'projects/ext-datasets/operations/IHQZ4Q3HVNEI3HRRVBHYQ5UU'}\n","Grid 17\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762662010331, 'update_timestamp_ms': 1762662010331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VDVXED3MPPGPPC5TC5OBBDCU', 'name': 'projects/ext-datasets/operations/VDVXED3MPPGPPC5TC5OBBDCU'}\n","Grid 18\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762662017478, 'update_timestamp_ms': 1762662017478, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CWL6AQYBJIE2Y375UN75KCMF', 'name': 'projects/ext-datasets/operations/CWL6AQYBJIE2Y375UN75KCMF'}\n","Grid 19\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762662025437, 'update_timestamp_ms': 1762662025437, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZR5ETYBGZBIQMS2MIAFOCKMH', 'name': 'projects/ext-datasets/operations/ZR5ETYBGZBIQMS2MIAFOCKMH'}\n","Grid 20\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762662032211, 'update_timestamp_ms': 1762662032211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2R2QB75376OUY2AEW4NUKVEK', 'name': 'projects/ext-datasets/operations/2R2QB75376OUY2AEW4NUKVEK'}\n","Grid 21\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762662039260, 'update_timestamp_ms': 1762662039260, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5UECIWMSVBETOS5XAGANZ6UY', 'name': 'projects/ext-datasets/operations/5UECIWMSVBETOS5XAGANZ6UY'}\n","Grid 22\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762662046370, 'update_timestamp_ms': 1762662046370, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLMQ5HTF43J5UJMA7PDMITTY', 'name': 'projects/ext-datasets/operations/XLMQ5HTF43J5UJMA7PDMITTY'}\n","Grid 23\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762662054979, 'update_timestamp_ms': 1762662054979, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EC2L2OPTK7BHK4T4ETPAMXAT', 'name': 'projects/ext-datasets/operations/EC2L2OPTK7BHK4T4ETPAMXAT'}\n","Grid 24\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762662063110, 'update_timestamp_ms': 1762662063110, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKCHOBTGXB7AUZR5MDMYBAQ3', 'name': 'projects/ext-datasets/operations/SKCHOBTGXB7AUZR5MDMYBAQ3'}\n","Grid 25\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762662073842, 'update_timestamp_ms': 1762662073842, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LNQH3EJVOR4FVPS7CHED6AB6', 'name': 'projects/ext-datasets/operations/LNQH3EJVOR4FVPS7CHED6AB6'}\n","Grid 26\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762662081995, 'update_timestamp_ms': 1762662081995, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6N4Q4XDT2BELBHT64FNLB4SP', 'name': 'projects/ext-datasets/operations/6N4Q4XDT2BELBHT64FNLB4SP'}\n","Grid 27\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762662089326, 'update_timestamp_ms': 1762662089326, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WQJ5ZMGWGSJXV4VBS4JE4ALQ', 'name': 'projects/ext-datasets/operations/WQJ5ZMGWGSJXV4VBS4JE4ALQ'}\n","Grid 28\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762662107477, 'update_timestamp_ms': 1762662107477, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V2AWXUTPUQEKFRSFULUCNE3M', 'name': 'projects/ext-datasets/operations/V2AWXUTPUQEKFRSFULUCNE3M'}\n","Grid 29\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762662114625, 'update_timestamp_ms': 1762662114625, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Q5YQYH5WH3QQYGEATZOOKVJD', 'name': 'projects/ext-datasets/operations/Q5YQYH5WH3QQYGEATZOOKVJD'}\n","Grid 30\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762662121341, 'update_timestamp_ms': 1762662121341, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FPDEWOS5DMAI5N4VOSYA2LLH', 'name': 'projects/ext-datasets/operations/FPDEWOS5DMAI5N4VOSYA2LLH'}\n","Grid 31\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762662131124, 'update_timestamp_ms': 1762662131124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M2EGDWT6C3WHCGLEKCPT3TVK', 'name': 'projects/ext-datasets/operations/M2EGDWT6C3WHCGLEKCPT3TVK'}\n","Grid 32\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762662138013, 'update_timestamp_ms': 1762662138013, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YVX3LIC6VBOVZU23NU7TIA2D', 'name': 'projects/ext-datasets/operations/YVX3LIC6VBOVZU23NU7TIA2D'}\n","Grid 33\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762662146289, 'update_timestamp_ms': 1762662146289, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3MTP4WO7E3SO3XA44ZR3XZPU', 'name': 'projects/ext-datasets/operations/3MTP4WO7E3SO3XA44ZR3XZPU'}\n","Grid 34\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762662150335, 'update_timestamp_ms': 1762662150335, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OVQYXE4A4CF2FNTZD6VOLK2U', 'name': 'projects/ext-datasets/operations/OVQYXE4A4CF2FNTZD6VOLK2U'}\n","Grid 35\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762662158557, 'update_timestamp_ms': 1762662158557, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4G7JMMFDPHV42WUJRG3C3VMP', 'name': 'projects/ext-datasets/operations/4G7JMMFDPHV42WUJRG3C3VMP'}\n","Grid 36\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762662166584, 'update_timestamp_ms': 1762662166584, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6F5H3VPJ4YUG4KEA4J4DVMCY', 'name': 'projects/ext-datasets/operations/6F5H3VPJ4YUG4KEA4J4DVMCY'}\n","Grid 37\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_37_2018', 'priority': 100, 'creation_timestamp_ms': 1762662184617, 'update_timestamp_ms': 1762662184617, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GFXWSLLOAPWVW523H6VW43OV', 'name': 'projects/ext-datasets/operations/GFXWSLLOAPWVW523H6VW43OV'}\n","Grid 38\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_38_2018', 'priority': 100, 'creation_timestamp_ms': 1762662189413, 'update_timestamp_ms': 1762662189413, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C36EBKRXVVTKQBL2QP4HQDUW', 'name': 'projects/ext-datasets/operations/C36EBKRXVVTKQBL2QP4HQDUW'}\n","Grid 39\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_39_2018', 'priority': 100, 'creation_timestamp_ms': 1762662198287, 'update_timestamp_ms': 1762662198287, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YNDDSFXBTYEFGSVNBHSZJFP5', 'name': 'projects/ext-datasets/operations/YNDDSFXBTYEFGSVNBHSZJFP5'}\n","Grid 40\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_40_2018', 'priority': 100, 'creation_timestamp_ms': 1762662205602, 'update_timestamp_ms': 1762662205602, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MJWSGFBY5B6ANDJIHGIPGN7E', 'name': 'projects/ext-datasets/operations/MJWSGFBY5B6ANDJIHGIPGN7E'}\n","Grid 41\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_41_2018', 'priority': 100, 'creation_timestamp_ms': 1762662213239, 'update_timestamp_ms': 1762662213239, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCTYJB3FCOTI4WJ7NEBOMSXC', 'name': 'projects/ext-datasets/operations/DCTYJB3FCOTI4WJ7NEBOMSXC'}\n","Grid 42\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_42_2018', 'priority': 100, 'creation_timestamp_ms': 1762662221619, 'update_timestamp_ms': 1762662221619, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCXANHOQ6REJHDEZBHG7WNCA', 'name': 'projects/ext-datasets/operations/FCXANHOQ6REJHDEZBHG7WNCA'}\n","Grid 43\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_43_2018', 'priority': 100, 'creation_timestamp_ms': 1762662227723, 'update_timestamp_ms': 1762662227723, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KB7QEBRVCYJI7JSJJ6XU7WYB', 'name': 'projects/ext-datasets/operations/KB7QEBRVCYJI7JSJJ6XU7WYB'}\n","Grid 44\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_44_2018', 'priority': 100, 'creation_timestamp_ms': 1762662235717, 'update_timestamp_ms': 1762662235717, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5M26YN4LL22VBANA4ZGNN22G', 'name': 'projects/ext-datasets/operations/5M26YN4LL22VBANA4ZGNN22G'}\n","Grid 45\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_45_2018', 'priority': 100, 'creation_timestamp_ms': 1762662243442, 'update_timestamp_ms': 1762662243442, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UYZWOCUQPRAINGQU5AGWS6HU', 'name': 'projects/ext-datasets/operations/UYZWOCUQPRAINGQU5AGWS6HU'}\n","Grid 46\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_46_2018', 'priority': 100, 'creation_timestamp_ms': 1762662251072, 'update_timestamp_ms': 1762662251072, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2HN7JFZ5ZFCTIND4OUKA3M72', 'name': 'projects/ext-datasets/operations/2HN7JFZ5ZFCTIND4OUKA3M72'}\n","Grid 47\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_47_2018', 'priority': 100, 'creation_timestamp_ms': 1762662255803, 'update_timestamp_ms': 1762662255803, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'M3COZQAWMK27SG3AQNGFZHE6', 'name': 'projects/ext-datasets/operations/M3COZQAWMK27SG3AQNGFZHE6'}\n","Grid 48\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_48_2018', 'priority': 100, 'creation_timestamp_ms': 1762662261699, 'update_timestamp_ms': 1762662261699, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NGYDGB7ZGDUGCXLSMBI5MHEG', 'name': 'projects/ext-datasets/operations/NGYDGB7ZGDUGCXLSMBI5MHEG'}\n","Grid 49\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_49_2018', 'priority': 100, 'creation_timestamp_ms': 1762662270096, 'update_timestamp_ms': 1762662270096, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YEAPN44AOQZSMMZ4AT3VK3B4', 'name': 'projects/ext-datasets/operations/YEAPN44AOQZSMMZ4AT3VK3B4'}\n","Grid 50\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_50_2018', 'priority': 100, 'creation_timestamp_ms': 1762662290667, 'update_timestamp_ms': 1762662290667, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PC7LH7UTGSX3Y33VSUIXHDWY', 'name': 'projects/ext-datasets/operations/PC7LH7UTGSX3Y33VSUIXHDWY'}\n","Grid 51\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_51_2018', 'priority': 100, 'creation_timestamp_ms': 1762662299690, 'update_timestamp_ms': 1762662299690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YRNKM6XCEXVI5E4SVC5QLEGV', 'name': 'projects/ext-datasets/operations/YRNKM6XCEXVI5E4SVC5QLEGV'}\n","Grid 52\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_52_2018', 'priority': 100, 'creation_timestamp_ms': 1762662305843, 'update_timestamp_ms': 1762662305843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OXE6HIN2RFKBCPSJ54XRBJUM', 'name': 'projects/ext-datasets/operations/OXE6HIN2RFKBCPSJ54XRBJUM'}\n","Grid 53\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_53_2018', 'priority': 100, 'creation_timestamp_ms': 1762662314131, 'update_timestamp_ms': 1762662314131, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLGA7DVBZXB4HS2BENXXBXOK', 'name': 'projects/ext-datasets/operations/JLGA7DVBZXB4HS2BENXXBXOK'}\n","Grid 54\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_54_2018', 'priority': 100, 'creation_timestamp_ms': 1762662320564, 'update_timestamp_ms': 1762662320564, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HRK3SGO4WFBUMHTWEFYO4SQ4', 'name': 'projects/ext-datasets/operations/HRK3SGO4WFBUMHTWEFYO4SQ4'}\n","Grid 55\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_55_2018', 'priority': 100, 'creation_timestamp_ms': 1762662328714, 'update_timestamp_ms': 1762662328714, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RLDODOP4KDUTKXOXMONXQUIK', 'name': 'projects/ext-datasets/operations/RLDODOP4KDUTKXOXMONXQUIK'}\n","Grid 56\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_56_2018', 'priority': 100, 'creation_timestamp_ms': 1762662336187, 'update_timestamp_ms': 1762662336187, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'D6BLHDKIXDEQMMV6YKUFQJAL', 'name': 'projects/ext-datasets/operations/D6BLHDKIXDEQMMV6YKUFQJAL'}\n","Grid 57\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_57_2018', 'priority': 100, 'creation_timestamp_ms': 1762662339638, 'update_timestamp_ms': 1762662339638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6NQRBN7LKSGWSC4TS5MEIER', 'name': 'projects/ext-datasets/operations/X6NQRBN7LKSGWSC4TS5MEIER'}\n","Grid 58\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_58_2018', 'priority': 100, 'creation_timestamp_ms': 1762662345627, 'update_timestamp_ms': 1762662345627, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6IW4SBZEZN3T7BUBOZXR23ON', 'name': 'projects/ext-datasets/operations/6IW4SBZEZN3T7BUBOZXR23ON'}\n","Grid 59\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_59_2018', 'priority': 100, 'creation_timestamp_ms': 1762662352081, 'update_timestamp_ms': 1762662352081, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RHSKEKQO2WB5AFAAOGMFM4U6', 'name': 'projects/ext-datasets/operations/RHSKEKQO2WB5AFAAOGMFM4U6'}\n","Grid 60\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_60_2018', 'priority': 100, 'creation_timestamp_ms': 1762662357706, 'update_timestamp_ms': 1762662357706, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7FNZSJ7UQSW5IKMH4CNZPEX4', 'name': 'projects/ext-datasets/operations/7FNZSJ7UQSW5IKMH4CNZPEX4'}\n","Grid 61\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_61_2018', 'priority': 100, 'creation_timestamp_ms': 1762662366119, 'update_timestamp_ms': 1762662366119, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NGV3PBO7LJV3B456NN3BUA72', 'name': 'projects/ext-datasets/operations/NGV3PBO7LJV3B456NN3BUA72'}\n","Grid 62\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_62_2018', 'priority': 100, 'creation_timestamp_ms': 1762662371980, 'update_timestamp_ms': 1762662371980, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6WCVWZQZWWHNIILUOTRD5Q5B', 'name': 'projects/ext-datasets/operations/6WCVWZQZWWHNIILUOTRD5Q5B'}\n","Grid 63\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_63_2018', 'priority': 100, 'creation_timestamp_ms': 1762662379126, 'update_timestamp_ms': 1762662379126, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EDIVZOEFDLZPEVFVNZQRKOFY', 'name': 'projects/ext-datasets/operations/EDIVZOEFDLZPEVFVNZQRKOFY'}\n","Grid 64\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_64_2018', 'priority': 100, 'creation_timestamp_ms': 1762662384858, 'update_timestamp_ms': 1762662384858, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'R5SKSY3IODVXAPZHFZ5VMRRW', 'name': 'projects/ext-datasets/operations/R5SKSY3IODVXAPZHFZ5VMRRW'}\n","Grid 65\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_65_2018', 'priority': 100, 'creation_timestamp_ms': 1762662394129, 'update_timestamp_ms': 1762662394129, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7BJQC7NTRKWRXODRISP4YWUR', 'name': 'projects/ext-datasets/operations/7BJQC7NTRKWRXODRISP4YWUR'}\n","Grid 66\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_66_2018', 'priority': 100, 'creation_timestamp_ms': 1762662402161, 'update_timestamp_ms': 1762662402161, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6YYJFW52P7W6WJA6I6W5LVRL', 'name': 'projects/ext-datasets/operations/6YYJFW52P7W6WJA6I6W5LVRL'}\n","Grid 67\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_67_2018', 'priority': 100, 'creation_timestamp_ms': 1762662410861, 'update_timestamp_ms': 1762662410861, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WR2VL2QTR44KFUXNVN22RCD5', 'name': 'projects/ext-datasets/operations/WR2VL2QTR44KFUXNVN22RCD5'}\n","Grid 68\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_68_2018', 'priority': 100, 'creation_timestamp_ms': 1762662414538, 'update_timestamp_ms': 1762662414538, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7DSU7AISKVNDQ5XHPHTDIL7G', 'name': 'projects/ext-datasets/operations/7DSU7AISKVNDQ5XHPHTDIL7G'}\n","Grid 69\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_69_2018', 'priority': 100, 'creation_timestamp_ms': 1762662423666, 'update_timestamp_ms': 1762662423666, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3AQBCBLOEI35ZASVHUVES5W4', 'name': 'projects/ext-datasets/operations/3AQBCBLOEI35ZASVHUVES5W4'}\n","Grid 70\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_70_2018', 'priority': 100, 'creation_timestamp_ms': 1762662432102, 'update_timestamp_ms': 1762662432102, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HZEYWT53DCO2FB3GMFMD2ZOM', 'name': 'projects/ext-datasets/operations/HZEYWT53DCO2FB3GMFMD2ZOM'}\n","Grid 71\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_71_2018', 'priority': 100, 'creation_timestamp_ms': 1762662441628, 'update_timestamp_ms': 1762662441628, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IXCUHAU3PJ3GUO3RAMWQWWZO', 'name': 'projects/ext-datasets/operations/IXCUHAU3PJ3GUO3RAMWQWWZO'}\n","Grid 72\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_72_2018', 'priority': 100, 'creation_timestamp_ms': 1762662457917, 'update_timestamp_ms': 1762662457917, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6MSA2JK6TZZZCBKMWR4ZEDYW', 'name': 'projects/ext-datasets/operations/6MSA2JK6TZZZCBKMWR4ZEDYW'}\n","Grid 73\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_73_2018', 'priority': 100, 'creation_timestamp_ms': 1762662466701, 'update_timestamp_ms': 1762662466701, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UNFXUXZDVB5WXDVCJXMT6ZYX', 'name': 'projects/ext-datasets/operations/UNFXUXZDVB5WXDVCJXMT6ZYX'}\n","Grid 74\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_74_2018', 'priority': 100, 'creation_timestamp_ms': 1762662471448, 'update_timestamp_ms': 1762662471448, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AISEA5YKT46TYYP4ZUPGWGUU', 'name': 'projects/ext-datasets/operations/AISEA5YKT46TYYP4ZUPGWGUU'}\n","Grid 75\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_75_2018', 'priority': 100, 'creation_timestamp_ms': 1762662479102, 'update_timestamp_ms': 1762662479102, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KM7I4S7HQQNCH3FLWAINJBTP', 'name': 'projects/ext-datasets/operations/KM7I4S7HQQNCH3FLWAINJBTP'}\n","Grid 76\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_76_2018', 'priority': 100, 'creation_timestamp_ms': 1762662485846, 'update_timestamp_ms': 1762662485846, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HEE447VKHQ3H3HFFOEPNJVVM', 'name': 'projects/ext-datasets/operations/HEE447VKHQ3H3HFFOEPNJVVM'}\n","Grid 77\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_77_2018', 'priority': 100, 'creation_timestamp_ms': 1762662489539, 'update_timestamp_ms': 1762662489539, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NWJMXCWHAGTUT6WHE5DZDADQ', 'name': 'projects/ext-datasets/operations/NWJMXCWHAGTUT6WHE5DZDADQ'}\n","Grid 78\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_78_2018', 'priority': 100, 'creation_timestamp_ms': 1762662493898, 'update_timestamp_ms': 1762662493898, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FTPH3L7NDYOHSZLKK3DZM5IO', 'name': 'projects/ext-datasets/operations/FTPH3L7NDYOHSZLKK3DZM5IO'}\n","Grid 79\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_79_2018', 'priority': 100, 'creation_timestamp_ms': 1762662500167, 'update_timestamp_ms': 1762662500167, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QDLUJWKPXMOUDKW2DKKBPBGN', 'name': 'projects/ext-datasets/operations/QDLUJWKPXMOUDKW2DKKBPBGN'}\n","Grid 80\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_80_2018', 'priority': 100, 'creation_timestamp_ms': 1762662511569, 'update_timestamp_ms': 1762662511569, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XWNTVV5AFI737DY6FV5X7K77', 'name': 'projects/ext-datasets/operations/XWNTVV5AFI737DY6FV5X7K77'}\n","Grid 81\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_81_2018', 'priority': 100, 'creation_timestamp_ms': 1762662521065, 'update_timestamp_ms': 1762662521065, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LPMFCHNYD3CSSDV36C2UL4MV', 'name': 'projects/ext-datasets/operations/LPMFCHNYD3CSSDV36C2UL4MV'}\n","Grid 82\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_82_2018', 'priority': 100, 'creation_timestamp_ms': 1762662531383, 'update_timestamp_ms': 1762662531383, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZXADYANV7O4Q56UNXPE75IK', 'name': 'projects/ext-datasets/operations/XZXADYANV7O4Q56UNXPE75IK'}\n","Grid 83\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_83_2018', 'priority': 100, 'creation_timestamp_ms': 1762662548313, 'update_timestamp_ms': 1762662548313, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4WMULPTRLR2VRXIBIYBBDDI', 'name': 'projects/ext-datasets/operations/V4WMULPTRLR2VRXIBIYBBDDI'}\n","Grid 84\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_84_2018', 'priority': 100, 'creation_timestamp_ms': 1762662557900, 'update_timestamp_ms': 1762662557900, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O4FTHLVZODQWGD5GSV3YFWBN', 'name': 'projects/ext-datasets/operations/O4FTHLVZODQWGD5GSV3YFWBN'}\n","Grid 85\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_85_2018', 'priority': 100, 'creation_timestamp_ms': 1762662566782, 'update_timestamp_ms': 1762662566782, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4KC7OIL2XGF2SB2MTKJDDT4Y', 'name': 'projects/ext-datasets/operations/4KC7OIL2XGF2SB2MTKJDDT4Y'}\n","Grid 86\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_86_2018', 'priority': 100, 'creation_timestamp_ms': 1762662581004, 'update_timestamp_ms': 1762662581004, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DNHER2AA743EFZEIZTEEAFHD', 'name': 'projects/ext-datasets/operations/DNHER2AA743EFZEIZTEEAFHD'}\n","Grid 87\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_87_2018', 'priority': 100, 'creation_timestamp_ms': 1762662589639, 'update_timestamp_ms': 1762662589639, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EBZYKLFZDODZ6DGMP57JG5OD', 'name': 'projects/ext-datasets/operations/EBZYKLFZDODZ6DGMP57JG5OD'}\n","Grid 88\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_88_2018', 'priority': 100, 'creation_timestamp_ms': 1762662597190, 'update_timestamp_ms': 1762662597190, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7YVFLQI5JGODS4MSTMUA6JDO', 'name': 'projects/ext-datasets/operations/7YVFLQI5JGODS4MSTMUA6JDO'}\n","Grid 89\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_89_2018', 'priority': 100, 'creation_timestamp_ms': 1762662608586, 'update_timestamp_ms': 1762662608586, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OUNFSYOPKEUTECSJKWHHUKQA', 'name': 'projects/ext-datasets/operations/OUNFSYOPKEUTECSJKWHHUKQA'}\n","Grid 90\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_90_2018', 'priority': 100, 'creation_timestamp_ms': 1762662618337, 'update_timestamp_ms': 1762662618337, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LX6WCRWHFJCGAQ6YBNYZTYM4', 'name': 'projects/ext-datasets/operations/LX6WCRWHFJCGAQ6YBNYZTYM4'}\n","Grid 91\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_91_2018', 'priority': 100, 'creation_timestamp_ms': 1762662625221, 'update_timestamp_ms': 1762662625221, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6U47YJX5BJQ5C5DOO6JEIZUH', 'name': 'projects/ext-datasets/operations/6U47YJX5BJQ5C5DOO6JEIZUH'}\n","Grid 92\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_92_2018', 'priority': 100, 'creation_timestamp_ms': 1762662632164, 'update_timestamp_ms': 1762662632164, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YZUUKUDK5PZZ7FRHLMWYDP46', 'name': 'projects/ext-datasets/operations/YZUUKUDK5PZZ7FRHLMWYDP46'}\n","Grid 93\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_93_2018', 'priority': 100, 'creation_timestamp_ms': 1762662641124, 'update_timestamp_ms': 1762662641124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YPF3EA4T6HC4PZSO4DROILIQ', 'name': 'projects/ext-datasets/operations/YPF3EA4T6HC4PZSO4DROILIQ'}\n","Grid 94\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_94_2018', 'priority': 100, 'creation_timestamp_ms': 1762662649719, 'update_timestamp_ms': 1762662649719, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'X6OZH6FTJ5FTQWHINJV24ZDR', 'name': 'projects/ext-datasets/operations/X6OZH6FTJ5FTQWHINJV24ZDR'}\n","Grid 95\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_95_2018', 'priority': 100, 'creation_timestamp_ms': 1762662657088, 'update_timestamp_ms': 1762662657088, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DBIJGLF3F3V3VKQPR4VCWXHC', 'name': 'projects/ext-datasets/operations/DBIJGLF3F3V3VKQPR4VCWXHC'}\n","Grid 96\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_96_2018', 'priority': 100, 'creation_timestamp_ms': 1762662663908, 'update_timestamp_ms': 1762662663908, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HYHCQT2NV5ARFMF5FLAXTYX7', 'name': 'projects/ext-datasets/operations/HYHCQT2NV5ARFMF5FLAXTYX7'}\n","Grid 97\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_97_2018', 'priority': 100, 'creation_timestamp_ms': 1762662672397, 'update_timestamp_ms': 1762662672397, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YFFOWM2O3E7HBGR26H54Z6B5', 'name': 'projects/ext-datasets/operations/YFFOWM2O3E7HBGR26H54Z6B5'}\n","Grid 98\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_98_2018', 'priority': 100, 'creation_timestamp_ms': 1762662681198, 'update_timestamp_ms': 1762662681198, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZPDE35I5PJWJ5IJIL32IT5T', 'name': 'projects/ext-datasets/operations/AZPDE35I5PJWJ5IJIL32IT5T'}\n","Grid 99\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_99_2018', 'priority': 100, 'creation_timestamp_ms': 1762662688739, 'update_timestamp_ms': 1762662688739, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ER376KGTFSYINJ5TLGNKUCNZ', 'name': 'projects/ext-datasets/operations/ER376KGTFSYINJ5TLGNKUCNZ'}\n","Grid 100\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_100_2018', 'priority': 100, 'creation_timestamp_ms': 1762662691963, 'update_timestamp_ms': 1762662691963, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'J4TM3N3GQZQSZLAA475SN6TH', 'name': 'projects/ext-datasets/operations/J4TM3N3GQZQSZLAA475SN6TH'}\n","Grid 101\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_101_2018', 'priority': 100, 'creation_timestamp_ms': 1762662700337, 'update_timestamp_ms': 1762662700337, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLWJBS3W6UHVJSWAJSL5KVLN', 'name': 'projects/ext-datasets/operations/JLWJBS3W6UHVJSWAJSL5KVLN'}\n","Grid 102\n","curr_year 2018\n","Saving data for Bankura 2018\n","Task Started {'state': 'READY', 'description': 'Bankura_102_2018', 'priority': 100, 'creation_timestamp_ms': 1762662709090, 'update_timestamp_ms': 1762662709090, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UMYBJM2WLTINTWMYYRX5NWUS', 'name': 'projects/ext-datasets/operations/UMYBJM2WLTINTWMYYRX5NWUS'}\n","1470.9396209716797\n","Year 2018, District 12: Barddhaman, grids: 112\n","Grid 0\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762662720295, 'update_timestamp_ms': 1762662720295, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CVYAPFGUFSWB52RYSK5OM4WB', 'name': 'projects/ext-datasets/operations/CVYAPFGUFSWB52RYSK5OM4WB'}\n","Grid 1\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762662725451, 'update_timestamp_ms': 1762662725451, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QSF4HUMEERLP4CPQLVKER4WV', 'name': 'projects/ext-datasets/operations/QSF4HUMEERLP4CPQLVKER4WV'}\n","Grid 2\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762662734484, 'update_timestamp_ms': 1762662734484, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CVY66EKENE6B4NMH2PJSUFCT', 'name': 'projects/ext-datasets/operations/CVY66EKENE6B4NMH2PJSUFCT'}\n","Grid 3\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762662741414, 'update_timestamp_ms': 1762662741414, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YT6ZTJUXFWG5H2MGCEVCUZZQ', 'name': 'projects/ext-datasets/operations/YT6ZTJUXFWG5H2MGCEVCUZZQ'}\n","Grid 4\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762662749134, 'update_timestamp_ms': 1762662749134, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'COCYHFDUQLAUJYXXD23R44BM', 'name': 'projects/ext-datasets/operations/COCYHFDUQLAUJYXXD23R44BM'}\n","Grid 5\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762662753867, 'update_timestamp_ms': 1762662753867, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F5WXMDHXOYC7STHYBR62V7O2', 'name': 'projects/ext-datasets/operations/F5WXMDHXOYC7STHYBR62V7O2'}\n","Grid 6\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762662759975, 'update_timestamp_ms': 1762662759975, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '776SZ2R2H4PBBQYTTUM2LMOE', 'name': 'projects/ext-datasets/operations/776SZ2R2H4PBBQYTTUM2LMOE'}\n","Grid 7\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762662767571, 'update_timestamp_ms': 1762662767571, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZV5M5TW7V6RDUGJUNLMPUC2C', 'name': 'projects/ext-datasets/operations/ZV5M5TW7V6RDUGJUNLMPUC2C'}\n","Grid 8\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762662772693, 'update_timestamp_ms': 1762662772693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5LJZRLN5EUN32TUMHM5Z6S6U', 'name': 'projects/ext-datasets/operations/5LJZRLN5EUN32TUMHM5Z6S6U'}\n","Grid 9\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762662778997, 'update_timestamp_ms': 1762662778997, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WQH7D6YHAGG2TK43TR427DXA', 'name': 'projects/ext-datasets/operations/WQH7D6YHAGG2TK43TR427DXA'}\n","Grid 10\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762662786704, 'update_timestamp_ms': 1762662786704, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O2OF6Y3FXIIOZMQJYAVTDB4T', 'name': 'projects/ext-datasets/operations/O2OF6Y3FXIIOZMQJYAVTDB4T'}\n","Grid 11\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762662793594, 'update_timestamp_ms': 1762662793594, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CUNYJ6XVKOK5JWNBM3PXPKII', 'name': 'projects/ext-datasets/operations/CUNYJ6XVKOK5JWNBM3PXPKII'}\n","Grid 12\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762662797432, 'update_timestamp_ms': 1762662797432, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZY42XPHLSJJTASYEPK3HWTNB', 'name': 'projects/ext-datasets/operations/ZY42XPHLSJJTASYEPK3HWTNB'}\n","Grid 13\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762662805630, 'update_timestamp_ms': 1762662805630, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BQUGNYOPDMOGXW3XUHS5XRMY', 'name': 'projects/ext-datasets/operations/BQUGNYOPDMOGXW3XUHS5XRMY'}\n","Grid 14\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762662813218, 'update_timestamp_ms': 1762662813218, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y3JKMYINEA45AOFFTZIYD6KC', 'name': 'projects/ext-datasets/operations/Y3JKMYINEA45AOFFTZIYD6KC'}\n","Grid 15\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762662818157, 'update_timestamp_ms': 1762662818157, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C3ZJVK7IXOW75XKIDHBDRBZD', 'name': 'projects/ext-datasets/operations/C3ZJVK7IXOW75XKIDHBDRBZD'}\n","Grid 16\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762662826279, 'update_timestamp_ms': 1762662826279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3VRJ37SIYS7Q2T4AYGWTA5UO', 'name': 'projects/ext-datasets/operations/3VRJ37SIYS7Q2T4AYGWTA5UO'}\n","Grid 17\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762662834377, 'update_timestamp_ms': 1762662834377, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NYI4WE744QRXEN5DLPHQCSOA', 'name': 'projects/ext-datasets/operations/NYI4WE744QRXEN5DLPHQCSOA'}\n","Grid 18\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762662838462, 'update_timestamp_ms': 1762662838462, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P73SELSFL5BEZG4LSXWCS34H', 'name': 'projects/ext-datasets/operations/P73SELSFL5BEZG4LSXWCS34H'}\n","Grid 19\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762662843350, 'update_timestamp_ms': 1762662843350, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XZKEYHR7WQ7YK3SAZ5NMBTUA', 'name': 'projects/ext-datasets/operations/XZKEYHR7WQ7YK3SAZ5NMBTUA'}\n","Grid 20\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762662849762, 'update_timestamp_ms': 1762662849762, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NQ573WM4Z25B2UIRGKI4IANW', 'name': 'projects/ext-datasets/operations/NQ573WM4Z25B2UIRGKI4IANW'}\n","Grid 21\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762662857674, 'update_timestamp_ms': 1762662857674, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I5ASBM24ZWGIEIMGWCZWTUI6', 'name': 'projects/ext-datasets/operations/I5ASBM24ZWGIEIMGWCZWTUI6'}\n","Grid 22\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762662864953, 'update_timestamp_ms': 1762662864953, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S5CG73D3JKYNCF7KBIL3Q5H4', 'name': 'projects/ext-datasets/operations/S5CG73D3JKYNCF7KBIL3Q5H4'}\n","Grid 23\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762662871848, 'update_timestamp_ms': 1762662871848, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W642R3OWEAVV57FVAS3K467P', 'name': 'projects/ext-datasets/operations/W642R3OWEAVV57FVAS3K467P'}\n","Grid 24\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762662876462, 'update_timestamp_ms': 1762662876462, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'W62H5AE2JSAQTYZNIN7LLOQS', 'name': 'projects/ext-datasets/operations/W62H5AE2JSAQTYZNIN7LLOQS'}\n","Grid 25\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762662885779, 'update_timestamp_ms': 1762662885779, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ACYPIOUA4CU4XHTXKEWY5MOG', 'name': 'projects/ext-datasets/operations/ACYPIOUA4CU4XHTXKEWY5MOG'}\n","Grid 26\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762662893529, 'update_timestamp_ms': 1762662893529, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OUQPTYB24CYAL7Q6VZHTYC4I', 'name': 'projects/ext-datasets/operations/OUQPTYB24CYAL7Q6VZHTYC4I'}\n","Grid 27\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762662901313, 'update_timestamp_ms': 1762662901313, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'U5CDX3PVNJRXX7SJQU6FYSPF', 'name': 'projects/ext-datasets/operations/U5CDX3PVNJRXX7SJQU6FYSPF'}\n","Grid 28\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762662910250, 'update_timestamp_ms': 1762662910250, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SSA55FB26DNDZYFC6ENMNSGT', 'name': 'projects/ext-datasets/operations/SSA55FB26DNDZYFC6ENMNSGT'}\n","Grid 29\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762662917244, 'update_timestamp_ms': 1762662917244, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ASA5WKSDA4IR5ZQUJRH7TLLX', 'name': 'projects/ext-datasets/operations/ASA5WKSDA4IR5ZQUJRH7TLLX'}\n","Grid 30\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762662921261, 'update_timestamp_ms': 1762662921261, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HWJKKJLJLX7C2VCCFXM27FR3', 'name': 'projects/ext-datasets/operations/HWJKKJLJLX7C2VCCFXM27FR3'}\n","Grid 31\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762662929748, 'update_timestamp_ms': 1762662929748, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y7QCW5U5SUG2YQRW723RJWAD', 'name': 'projects/ext-datasets/operations/Y7QCW5U5SUG2YQRW723RJWAD'}\n","Grid 32\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762662935935, 'update_timestamp_ms': 1762662935935, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CJGX6XIEY3LRQNJFUL3VV2L2', 'name': 'projects/ext-datasets/operations/CJGX6XIEY3LRQNJFUL3VV2L2'}\n","Grid 33\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762662943825, 'update_timestamp_ms': 1762662943825, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OPUGTR4BYJQ3ZKPYRJG5PZVQ', 'name': 'projects/ext-datasets/operations/OPUGTR4BYJQ3ZKPYRJG5PZVQ'}\n","Grid 34\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762662950206, 'update_timestamp_ms': 1762662950206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OYRCYIHMQTX7VXUIWWUZIX6K', 'name': 'projects/ext-datasets/operations/OYRCYIHMQTX7VXUIWWUZIX6K'}\n","Grid 35\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762662958440, 'update_timestamp_ms': 1762662958440, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6MGJ33DRKPT4Y74IXCKBIB5J', 'name': 'projects/ext-datasets/operations/6MGJ33DRKPT4Y74IXCKBIB5J'}\n","Grid 36\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762662963435, 'update_timestamp_ms': 1762662963435, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCVUFYMLGVUTMYZQBP567VO2', 'name': 'projects/ext-datasets/operations/FCVUFYMLGVUTMYZQBP567VO2'}\n","Grid 37\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_37_2018', 'priority': 100, 'creation_timestamp_ms': 1762662968436, 'update_timestamp_ms': 1762662968436, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'E6JZE4FE2W5WUUATWZMRDNTK', 'name': 'projects/ext-datasets/operations/E6JZE4FE2W5WUUATWZMRDNTK'}\n","Grid 38\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_38_2018', 'priority': 100, 'creation_timestamp_ms': 1762662973081, 'update_timestamp_ms': 1762662973081, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJ67UTGBXBH47VQMEOAT2YAX', 'name': 'projects/ext-datasets/operations/YJ67UTGBXBH47VQMEOAT2YAX'}\n","Grid 39\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_39_2018', 'priority': 100, 'creation_timestamp_ms': 1762662980971, 'update_timestamp_ms': 1762662980971, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BK6DT5O47SHMGFWAB5ANZJ3C', 'name': 'projects/ext-datasets/operations/BK6DT5O47SHMGFWAB5ANZJ3C'}\n","Grid 40\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_40_2018', 'priority': 100, 'creation_timestamp_ms': 1762662990232, 'update_timestamp_ms': 1762662990232, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MA4JSGLARA5IL3IZBMOSAMPQ', 'name': 'projects/ext-datasets/operations/MA4JSGLARA5IL3IZBMOSAMPQ'}\n","Grid 41\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_41_2018', 'priority': 100, 'creation_timestamp_ms': 1762662994422, 'update_timestamp_ms': 1762662994422, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WZXBNEPQ3XMKAAEVULH7H2D2', 'name': 'projects/ext-datasets/operations/WZXBNEPQ3XMKAAEVULH7H2D2'}\n","Grid 42\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_42_2018', 'priority': 100, 'creation_timestamp_ms': 1762662998487, 'update_timestamp_ms': 1762662998487, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SYCOWKQES63QQRNNEAAQSZUN', 'name': 'projects/ext-datasets/operations/SYCOWKQES63QQRNNEAAQSZUN'}\n","Grid 43\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_43_2018', 'priority': 100, 'creation_timestamp_ms': 1762663003545, 'update_timestamp_ms': 1762663003545, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DSVIOMBC7IONT3SLT5I6A7IG', 'name': 'projects/ext-datasets/operations/DSVIOMBC7IONT3SLT5I6A7IG'}\n","Grid 44\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_44_2018', 'priority': 100, 'creation_timestamp_ms': 1762663007458, 'update_timestamp_ms': 1762663007458, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WJ6XRMS4T2PZOJBHRDYZTWUN', 'name': 'projects/ext-datasets/operations/WJ6XRMS4T2PZOJBHRDYZTWUN'}\n","Grid 45\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_45_2018', 'priority': 100, 'creation_timestamp_ms': 1762663015024, 'update_timestamp_ms': 1762663015024, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VUMY6LTNLYIPQIH6HNXOH2DT', 'name': 'projects/ext-datasets/operations/VUMY6LTNLYIPQIH6HNXOH2DT'}\n","Grid 46\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_46_2018', 'priority': 100, 'creation_timestamp_ms': 1762663022934, 'update_timestamp_ms': 1762663022934, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5MYOP6VCGKLOQ2BRLAPTD5VU', 'name': 'projects/ext-datasets/operations/5MYOP6VCGKLOQ2BRLAPTD5VU'}\n","Grid 47\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_47_2018', 'priority': 100, 'creation_timestamp_ms': 1762663031299, 'update_timestamp_ms': 1762663031299, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Z4JZ6IXCRQUSVQT22IYRDRXP', 'name': 'projects/ext-datasets/operations/Z4JZ6IXCRQUSVQT22IYRDRXP'}\n","Grid 48\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_48_2018', 'priority': 100, 'creation_timestamp_ms': 1762663041569, 'update_timestamp_ms': 1762663041569, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H37NQ7RTYBXWI62MU57FCIYK', 'name': 'projects/ext-datasets/operations/H37NQ7RTYBXWI62MU57FCIYK'}\n","Grid 49\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_49_2018', 'priority': 100, 'creation_timestamp_ms': 1762663048632, 'update_timestamp_ms': 1762663048632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OJQN3MJGBMNGZSLBFCIDKSHN', 'name': 'projects/ext-datasets/operations/OJQN3MJGBMNGZSLBFCIDKSHN'}\n","Grid 50\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_50_2018', 'priority': 100, 'creation_timestamp_ms': 1762663056130, 'update_timestamp_ms': 1762663056130, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZM72542SRA25SZ7H7YOIUGSR', 'name': 'projects/ext-datasets/operations/ZM72542SRA25SZ7H7YOIUGSR'}\n","Grid 51\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_51_2018', 'priority': 100, 'creation_timestamp_ms': 1762663064008, 'update_timestamp_ms': 1762663064008, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H6PKJHG5OTAASPDE2X3BQXRJ', 'name': 'projects/ext-datasets/operations/H6PKJHG5OTAASPDE2X3BQXRJ'}\n","Grid 52\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_52_2018', 'priority': 100, 'creation_timestamp_ms': 1762663073818, 'update_timestamp_ms': 1762663073818, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HBANIKF2AUIVEE7LN2YJ3HRG', 'name': 'projects/ext-datasets/operations/HBANIKF2AUIVEE7LN2YJ3HRG'}\n","Grid 53\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_53_2018', 'priority': 100, 'creation_timestamp_ms': 1762663081691, 'update_timestamp_ms': 1762663081691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AB6P57FWLTZ2TDXPKXL656OW', 'name': 'projects/ext-datasets/operations/AB6P57FWLTZ2TDXPKXL656OW'}\n","Grid 54\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_54_2018', 'priority': 100, 'creation_timestamp_ms': 1762663089843, 'update_timestamp_ms': 1762663089843, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YC4KZ5YVVGY3GJSGAEEIU4VQ', 'name': 'projects/ext-datasets/operations/YC4KZ5YVVGY3GJSGAEEIU4VQ'}\n","Grid 55\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_55_2018', 'priority': 100, 'creation_timestamp_ms': 1762663096447, 'update_timestamp_ms': 1762663096447, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RYBNFECFMNKJLSVQH5KKG26K', 'name': 'projects/ext-datasets/operations/RYBNFECFMNKJLSVQH5KKG26K'}\n","Grid 56\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_56_2018', 'priority': 100, 'creation_timestamp_ms': 1762663108432, 'update_timestamp_ms': 1762663108432, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5AKLGISYOJY536SSIE4J5XPV', 'name': 'projects/ext-datasets/operations/5AKLGISYOJY536SSIE4J5XPV'}\n","Grid 57\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_57_2018', 'priority': 100, 'creation_timestamp_ms': 1762663116314, 'update_timestamp_ms': 1762663116314, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '65FXQIDVL7AAKPCQSXDIJVWL', 'name': 'projects/ext-datasets/operations/65FXQIDVL7AAKPCQSXDIJVWL'}\n","Grid 58\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_58_2018', 'priority': 100, 'creation_timestamp_ms': 1762663122438, 'update_timestamp_ms': 1762663122438, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YBDAFJBLFXJTACPQYMGOQBMK', 'name': 'projects/ext-datasets/operations/YBDAFJBLFXJTACPQYMGOQBMK'}\n","Grid 59\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_59_2018', 'priority': 100, 'creation_timestamp_ms': 1762663131585, 'update_timestamp_ms': 1762663131585, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AB6LY3PVHGZ747TMIB4YXZQ7', 'name': 'projects/ext-datasets/operations/AB6LY3PVHGZ747TMIB4YXZQ7'}\n","Grid 60\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_60_2018', 'priority': 100, 'creation_timestamp_ms': 1762663135551, 'update_timestamp_ms': 1762663135551, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5JDMOFKKXGHEK7LEIC77LH5A', 'name': 'projects/ext-datasets/operations/5JDMOFKKXGHEK7LEIC77LH5A'}\n","Grid 61\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_61_2018', 'priority': 100, 'creation_timestamp_ms': 1762663146649, 'update_timestamp_ms': 1762663146649, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AZEP66S23AF6WRDZ4LBK36TC', 'name': 'projects/ext-datasets/operations/AZEP66S23AF6WRDZ4LBK36TC'}\n","Grid 62\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_62_2018', 'priority': 100, 'creation_timestamp_ms': 1762663153661, 'update_timestamp_ms': 1762663153661, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VTRIUL6HUXUIV535Z3ECYNG4', 'name': 'projects/ext-datasets/operations/VTRIUL6HUXUIV535Z3ECYNG4'}\n","Grid 63\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_63_2018', 'priority': 100, 'creation_timestamp_ms': 1762663160000, 'update_timestamp_ms': 1762663160000, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AEC6PK6LKUXF5JF6ENX375BT', 'name': 'projects/ext-datasets/operations/AEC6PK6LKUXF5JF6ENX375BT'}\n","Grid 64\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_64_2018', 'priority': 100, 'creation_timestamp_ms': 1762663166893, 'update_timestamp_ms': 1762663166893, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AJGFGVSX5IGYBQ3F47E6PG7P', 'name': 'projects/ext-datasets/operations/AJGFGVSX5IGYBQ3F47E6PG7P'}\n","Grid 65\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_65_2018', 'priority': 100, 'creation_timestamp_ms': 1762663173953, 'update_timestamp_ms': 1762663173953, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DCZ3C5BFZU4OJXKNOW6DAMRL', 'name': 'projects/ext-datasets/operations/DCZ3C5BFZU4OJXKNOW6DAMRL'}\n","Grid 66\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_66_2018', 'priority': 100, 'creation_timestamp_ms': 1762663182967, 'update_timestamp_ms': 1762663182967, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XLGDIO5DJPDONI2WYGYF3BLU', 'name': 'projects/ext-datasets/operations/XLGDIO5DJPDONI2WYGYF3BLU'}\n","Grid 67\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_67_2018', 'priority': 100, 'creation_timestamp_ms': 1762663190118, 'update_timestamp_ms': 1762663190118, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYFQNFSPOFRECNPSHKFO5QAA', 'name': 'projects/ext-datasets/operations/XYFQNFSPOFRECNPSHKFO5QAA'}\n","Grid 68\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_68_2018', 'priority': 100, 'creation_timestamp_ms': 1762663194061, 'update_timestamp_ms': 1762663194061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WA7YEKTAQQLLTSRWAF7XRFUG', 'name': 'projects/ext-datasets/operations/WA7YEKTAQQLLTSRWAF7XRFUG'}\n","Grid 69\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_69_2018', 'priority': 100, 'creation_timestamp_ms': 1762663201149, 'update_timestamp_ms': 1762663201149, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VTZHVVRZY6I3DSFZ4ETCDI3A', 'name': 'projects/ext-datasets/operations/VTZHVVRZY6I3DSFZ4ETCDI3A'}\n","Grid 70\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_70_2018', 'priority': 100, 'creation_timestamp_ms': 1762663206455, 'update_timestamp_ms': 1762663206455, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RC4A3BRD7HKBCMHNTWDM7KIX', 'name': 'projects/ext-datasets/operations/RC4A3BRD7HKBCMHNTWDM7KIX'}\n","Grid 71\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_71_2018', 'priority': 100, 'creation_timestamp_ms': 1762663215535, 'update_timestamp_ms': 1762663215535, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WJVSAIOQZCFK7GYNGOOJZTPT', 'name': 'projects/ext-datasets/operations/WJVSAIOQZCFK7GYNGOOJZTPT'}\n","Grid 72\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_72_2018', 'priority': 100, 'creation_timestamp_ms': 1762663222778, 'update_timestamp_ms': 1762663222778, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NV7WT3N7XF7IOBORBI3VFM2K', 'name': 'projects/ext-datasets/operations/NV7WT3N7XF7IOBORBI3VFM2K'}\n","Grid 73\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_73_2018', 'priority': 100, 'creation_timestamp_ms': 1762663227215, 'update_timestamp_ms': 1762663227215, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6GA356K45UAKYMSM6T37BUF7', 'name': 'projects/ext-datasets/operations/6GA356K45UAKYMSM6T37BUF7'}\n","Grid 74\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_74_2018', 'priority': 100, 'creation_timestamp_ms': 1762663234448, 'update_timestamp_ms': 1762663234448, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OLWSBKKVMBWHQ64BNHTYWUM4', 'name': 'projects/ext-datasets/operations/OLWSBKKVMBWHQ64BNHTYWUM4'}\n","Grid 75\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_75_2018', 'priority': 100, 'creation_timestamp_ms': 1762663244824, 'update_timestamp_ms': 1762663244824, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RUBCPN5V42II5GI53VMEOHWG', 'name': 'projects/ext-datasets/operations/RUBCPN5V42II5GI53VMEOHWG'}\n","Grid 76\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_76_2018', 'priority': 100, 'creation_timestamp_ms': 1762663250937, 'update_timestamp_ms': 1762663250937, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HE2V5OOESE3NE7ABPGABA2XR', 'name': 'projects/ext-datasets/operations/HE2V5OOESE3NE7ABPGABA2XR'}\n","Grid 77\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_77_2018', 'priority': 100, 'creation_timestamp_ms': 1762663259804, 'update_timestamp_ms': 1762663259804, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L3DC3VJTRX36H3TCSQX4PYUZ', 'name': 'projects/ext-datasets/operations/L3DC3VJTRX36H3TCSQX4PYUZ'}\n","Grid 78\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_78_2018', 'priority': 100, 'creation_timestamp_ms': 1762663267880, 'update_timestamp_ms': 1762663267880, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ULONDXUUNB5HF2LA777GPOHE', 'name': 'projects/ext-datasets/operations/ULONDXUUNB5HF2LA777GPOHE'}\n","Grid 79\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_79_2018', 'priority': 100, 'creation_timestamp_ms': 1762663275114, 'update_timestamp_ms': 1762663275114, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VA5VHD2OWYAHA43AE4UH34WB', 'name': 'projects/ext-datasets/operations/VA5VHD2OWYAHA43AE4UH34WB'}\n","Grid 80\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_80_2018', 'priority': 100, 'creation_timestamp_ms': 1762663282179, 'update_timestamp_ms': 1762663282179, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5I5RISHZ67Y5E74MDD5PBTSX', 'name': 'projects/ext-datasets/operations/5I5RISHZ67Y5E74MDD5PBTSX'}\n","Grid 81\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_81_2018', 'priority': 100, 'creation_timestamp_ms': 1762663288154, 'update_timestamp_ms': 1762663288154, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T4HS6R3X6PEJSRUF53ZXJCFP', 'name': 'projects/ext-datasets/operations/T4HS6R3X6PEJSRUF53ZXJCFP'}\n","Grid 82\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_82_2018', 'priority': 100, 'creation_timestamp_ms': 1762663295485, 'update_timestamp_ms': 1762663295485, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A5PNFUVXZGFQM4BRSEDSFXXZ', 'name': 'projects/ext-datasets/operations/A5PNFUVXZGFQM4BRSEDSFXXZ'}\n","Grid 83\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_83_2018', 'priority': 100, 'creation_timestamp_ms': 1762663302995, 'update_timestamp_ms': 1762663302995, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2A3MCB6W3ITEZZPSWLPDGVHL', 'name': 'projects/ext-datasets/operations/2A3MCB6W3ITEZZPSWLPDGVHL'}\n","Grid 84\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_84_2018', 'priority': 100, 'creation_timestamp_ms': 1762663311352, 'update_timestamp_ms': 1762663311352, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KLFS36SLEME2NACN5TYIRV2O', 'name': 'projects/ext-datasets/operations/KLFS36SLEME2NACN5TYIRV2O'}\n","Grid 85\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_85_2018', 'priority': 100, 'creation_timestamp_ms': 1762663320281, 'update_timestamp_ms': 1762663320281, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '635EVSMRKTJNVKVMSCWHZRI5', 'name': 'projects/ext-datasets/operations/635EVSMRKTJNVKVMSCWHZRI5'}\n","Grid 86\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_86_2018', 'priority': 100, 'creation_timestamp_ms': 1762663327089, 'update_timestamp_ms': 1762663327089, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2KJYEKDKZLN7DT3FHHQF5Q7T', 'name': 'projects/ext-datasets/operations/2KJYEKDKZLN7DT3FHHQF5Q7T'}\n","Grid 87\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_87_2018', 'priority': 100, 'creation_timestamp_ms': 1762663335646, 'update_timestamp_ms': 1762663335646, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HYMXNPZCURLDUHXHMZW4IRFB', 'name': 'projects/ext-datasets/operations/HYMXNPZCURLDUHXHMZW4IRFB'}\n","Grid 88\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_88_2018', 'priority': 100, 'creation_timestamp_ms': 1762663340319, 'update_timestamp_ms': 1762663340319, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJTO5G3CDCRCHQGAJ33TFNHL', 'name': 'projects/ext-datasets/operations/YJTO5G3CDCRCHQGAJ33TFNHL'}\n","Grid 89\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_89_2018', 'priority': 100, 'creation_timestamp_ms': 1762663348259, 'update_timestamp_ms': 1762663348259, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DR4LZPVI52LQAZBSBKEMX6CF', 'name': 'projects/ext-datasets/operations/DR4LZPVI52LQAZBSBKEMX6CF'}\n","Grid 90\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_90_2018', 'priority': 100, 'creation_timestamp_ms': 1762663355018, 'update_timestamp_ms': 1762663355018, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JYNU56WQSWHVJCXNU4Q3YSBN', 'name': 'projects/ext-datasets/operations/JYNU56WQSWHVJCXNU4Q3YSBN'}\n","Grid 91\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_91_2018', 'priority': 100, 'creation_timestamp_ms': 1762663361507, 'update_timestamp_ms': 1762663361507, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NYOFRCDWKY6XN57GICRHY4YB', 'name': 'projects/ext-datasets/operations/NYOFRCDWKY6XN57GICRHY4YB'}\n","Grid 92\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_92_2018', 'priority': 100, 'creation_timestamp_ms': 1762663369331, 'update_timestamp_ms': 1762663369331, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6DXI2S35UHUF474BBJOGMKR3', 'name': 'projects/ext-datasets/operations/6DXI2S35UHUF474BBJOGMKR3'}\n","Grid 93\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_93_2018', 'priority': 100, 'creation_timestamp_ms': 1762663375915, 'update_timestamp_ms': 1762663375915, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'C4E2A3T3VFQBMPWKVZS5XKUA', 'name': 'projects/ext-datasets/operations/C4E2A3T3VFQBMPWKVZS5XKUA'}\n","Grid 94\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_94_2018', 'priority': 100, 'creation_timestamp_ms': 1762663382522, 'update_timestamp_ms': 1762663382522, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QKYZUU5OH3B7ODQEERSJ577G', 'name': 'projects/ext-datasets/operations/QKYZUU5OH3B7ODQEERSJ577G'}\n","Grid 95\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_95_2018', 'priority': 100, 'creation_timestamp_ms': 1762663391225, 'update_timestamp_ms': 1762663391225, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MF2FN325BEDATDQ3Q36ALVWJ', 'name': 'projects/ext-datasets/operations/MF2FN325BEDATDQ3Q36ALVWJ'}\n","Grid 96\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_96_2018', 'priority': 100, 'creation_timestamp_ms': 1762663400147, 'update_timestamp_ms': 1762663400147, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'S5T4MFZXEOIYBUX2UNZ6VCKA', 'name': 'projects/ext-datasets/operations/S5T4MFZXEOIYBUX2UNZ6VCKA'}\n","Grid 97\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_97_2018', 'priority': 100, 'creation_timestamp_ms': 1762663407099, 'update_timestamp_ms': 1762663407099, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UARCOETQB7CQ4PVNKAIPLJDN', 'name': 'projects/ext-datasets/operations/UARCOETQB7CQ4PVNKAIPLJDN'}\n","Grid 98\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_98_2018', 'priority': 100, 'creation_timestamp_ms': 1762663415117, 'update_timestamp_ms': 1762663415117, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I7UQA5G2IBIOLNAJSDMI5QH7', 'name': 'projects/ext-datasets/operations/I7UQA5G2IBIOLNAJSDMI5QH7'}\n","Grid 99\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_99_2018', 'priority': 100, 'creation_timestamp_ms': 1762663418758, 'update_timestamp_ms': 1762663418758, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EOJAWWDCL556YQVZTBZXZSB7', 'name': 'projects/ext-datasets/operations/EOJAWWDCL556YQVZTBZXZSB7'}\n","Grid 100\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_100_2018', 'priority': 100, 'creation_timestamp_ms': 1762663422228, 'update_timestamp_ms': 1762663422228, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MGULUVTSCMVEGLIRYY5MGGZX', 'name': 'projects/ext-datasets/operations/MGULUVTSCMVEGLIRYY5MGGZX'}\n","Grid 101\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_101_2018', 'priority': 100, 'creation_timestamp_ms': 1762663429666, 'update_timestamp_ms': 1762663429666, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGGGEUAVN5QJWIM3KNVEBROD', 'name': 'projects/ext-datasets/operations/QGGGEUAVN5QJWIM3KNVEBROD'}\n","Grid 102\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_102_2018', 'priority': 100, 'creation_timestamp_ms': 1762663435992, 'update_timestamp_ms': 1762663435992, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IRKN3Q2SUO57GS6GBRKUHUAR', 'name': 'projects/ext-datasets/operations/IRKN3Q2SUO57GS6GBRKUHUAR'}\n","Grid 103\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_103_2018', 'priority': 100, 'creation_timestamp_ms': 1762663443638, 'update_timestamp_ms': 1762663443638, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6N5C7M3TD73HF72B4TSEDWRP', 'name': 'projects/ext-datasets/operations/6N5C7M3TD73HF72B4TSEDWRP'}\n","Grid 104\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_104_2018', 'priority': 100, 'creation_timestamp_ms': 1762663448017, 'update_timestamp_ms': 1762663448017, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LX4OUSI7VBMLE7MEW5ABRB5G', 'name': 'projects/ext-datasets/operations/LX4OUSI7VBMLE7MEW5ABRB5G'}\n","Grid 105\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_105_2018', 'priority': 100, 'creation_timestamp_ms': 1762663455659, 'update_timestamp_ms': 1762663455659, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4R4HCC4REODQHC7PIRJ6OJSW', 'name': 'projects/ext-datasets/operations/4R4HCC4REODQHC7PIRJ6OJSW'}\n","Grid 106\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_106_2018', 'priority': 100, 'creation_timestamp_ms': 1762663464144, 'update_timestamp_ms': 1762663464144, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLKJ7YXCPRAIQXOTVQFQDHGL', 'name': 'projects/ext-datasets/operations/JLKJ7YXCPRAIQXOTVQFQDHGL'}\n","Grid 107\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_107_2018', 'priority': 100, 'creation_timestamp_ms': 1762663471980, 'update_timestamp_ms': 1762663471980, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CPXTOZCKCJQPL22BOY4HJKB2', 'name': 'projects/ext-datasets/operations/CPXTOZCKCJQPL22BOY4HJKB2'}\n","Grid 108\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_108_2018', 'priority': 100, 'creation_timestamp_ms': 1762663480112, 'update_timestamp_ms': 1762663480112, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7WQUBZI63PAYLPWRN7TI7DUO', 'name': 'projects/ext-datasets/operations/7WQUBZI63PAYLPWRN7TI7DUO'}\n","Grid 109\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_109_2018', 'priority': 100, 'creation_timestamp_ms': 1762663484678, 'update_timestamp_ms': 1762663484678, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '26277CAVBGAF6SO3IDWCUQ23', 'name': 'projects/ext-datasets/operations/26277CAVBGAF6SO3IDWCUQ23'}\n","Grid 110\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_110_2018', 'priority': 100, 'creation_timestamp_ms': 1762663493443, 'update_timestamp_ms': 1762663493443, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GUVFSMFTRMKQQ66SYTHYTY7M', 'name': 'projects/ext-datasets/operations/GUVFSMFTRMKQQ66SYTHYTY7M'}\n","Grid 111\n","curr_year 2018\n","Saving data for Barddhaman 2018\n","Task Started {'state': 'READY', 'description': 'Barddhaman_111_2018', 'priority': 100, 'creation_timestamp_ms': 1762663501199, 'update_timestamp_ms': 1762663501199, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BPIT2V4T7LJAIQ7SHCXRH623', 'name': 'projects/ext-datasets/operations/BPIT2V4T7LJAIQ7SHCXRH623'}\n","2263.010017156601\n","Year 2018, District 13: Birbhum, grids: 76\n","Grid 0\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762663511593, 'update_timestamp_ms': 1762663511593, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H2JM3MGIBTGUZZ6QWRTHS5WC', 'name': 'projects/ext-datasets/operations/H2JM3MGIBTGUZZ6QWRTHS5WC'}\n","Grid 1\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762663517428, 'update_timestamp_ms': 1762663517428, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OM7PMNB6W5GCYJSLARMRBMMH', 'name': 'projects/ext-datasets/operations/OM7PMNB6W5GCYJSLARMRBMMH'}\n","Grid 2\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762663523539, 'update_timestamp_ms': 1762663523539, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YKL6TAP2ZX3RUDTEFRAE72SP', 'name': 'projects/ext-datasets/operations/YKL6TAP2ZX3RUDTEFRAE72SP'}\n","Grid 3\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762663528193, 'update_timestamp_ms': 1762663528193, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'CYAWF4ZRFEB3FQYH2XQDDLVP', 'name': 'projects/ext-datasets/operations/CYAWF4ZRFEB3FQYH2XQDDLVP'}\n","Grid 4\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762663533339, 'update_timestamp_ms': 1762663533339, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'H3DJUDHAMRAA6ZC2WJS2LITN', 'name': 'projects/ext-datasets/operations/H3DJUDHAMRAA6ZC2WJS2LITN'}\n","Grid 5\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762663537707, 'update_timestamp_ms': 1762663537707, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4CYTSCWBPT7UV557XNCHSE6D', 'name': 'projects/ext-datasets/operations/4CYTSCWBPT7UV557XNCHSE6D'}\n","Grid 6\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762663541366, 'update_timestamp_ms': 1762663541366, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JADB7APCIEYRKWLKQTDD3XDB', 'name': 'projects/ext-datasets/operations/JADB7APCIEYRKWLKQTDD3XDB'}\n","Grid 7\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762663548264, 'update_timestamp_ms': 1762663548264, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LIR5JNBB3JRRYRRGHSSILNLM', 'name': 'projects/ext-datasets/operations/LIR5JNBB3JRRYRRGHSSILNLM'}\n","Grid 8\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762663554849, 'update_timestamp_ms': 1762663554849, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZQMJOQSKYN46CWTGYVECLQP2', 'name': 'projects/ext-datasets/operations/ZQMJOQSKYN46CWTGYVECLQP2'}\n","Grid 9\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762663560776, 'update_timestamp_ms': 1762663560776, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QUAJVKJAADMX5B64YMBYMCCT', 'name': 'projects/ext-datasets/operations/QUAJVKJAADMX5B64YMBYMCCT'}\n","Grid 10\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762663564605, 'update_timestamp_ms': 1762663564605, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZGQMYXQBNSUCG7JOQNNLGMHQ', 'name': 'projects/ext-datasets/operations/ZGQMYXQBNSUCG7JOQNNLGMHQ'}\n","Grid 11\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762663574206, 'update_timestamp_ms': 1762663574206, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BOHTBJI7X4RBKWCTAH2SGO42', 'name': 'projects/ext-datasets/operations/BOHTBJI7X4RBKWCTAH2SGO42'}\n","Grid 12\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762663578321, 'update_timestamp_ms': 1762663578321, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QOIF6NPM4NX7WXYVFTOD62HI', 'name': 'projects/ext-datasets/operations/QOIF6NPM4NX7WXYVFTOD62HI'}\n","Grid 13\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762663585003, 'update_timestamp_ms': 1762663585003, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FEWZDVACV3MM73VU56O6QKCR', 'name': 'projects/ext-datasets/operations/FEWZDVACV3MM73VU56O6QKCR'}\n","Grid 14\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762663592253, 'update_timestamp_ms': 1762663592253, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AEGN7KBLVW7TDCGDJZE3UPRK', 'name': 'projects/ext-datasets/operations/AEGN7KBLVW7TDCGDJZE3UPRK'}\n","Grid 15\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762663600087, 'update_timestamp_ms': 1762663600087, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6A35UOECIKO7IDU7VASDOPA2', 'name': 'projects/ext-datasets/operations/6A35UOECIKO7IDU7VASDOPA2'}\n","Grid 16\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762663607741, 'update_timestamp_ms': 1762663607741, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QUMNIBMBMU77XUHLLJNEWZLO', 'name': 'projects/ext-datasets/operations/QUMNIBMBMU77XUHLLJNEWZLO'}\n","Grid 17\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762663615561, 'update_timestamp_ms': 1762663615561, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GCARZBAT5AWWRPH3OJW4BSVN', 'name': 'projects/ext-datasets/operations/GCARZBAT5AWWRPH3OJW4BSVN'}\n","Grid 18\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762663625473, 'update_timestamp_ms': 1762663625473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXXV6TUVDZRCNLGCDAIRSLTY', 'name': 'projects/ext-datasets/operations/WXXV6TUVDZRCNLGCDAIRSLTY'}\n","Grid 19\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762663633418, 'update_timestamp_ms': 1762663633418, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NOOPAHRKEMANFDATGNQPBLF5', 'name': 'projects/ext-datasets/operations/NOOPAHRKEMANFDATGNQPBLF5'}\n","Grid 20\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762663640734, 'update_timestamp_ms': 1762663640734, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JC2KD7HRDGJJ4NGFRJ456FPF', 'name': 'projects/ext-datasets/operations/JC2KD7HRDGJJ4NGFRJ456FPF'}\n","Grid 21\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762663646211, 'update_timestamp_ms': 1762663646211, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QTLTMLASNBBJ3K6P5N5ZLAMG', 'name': 'projects/ext-datasets/operations/QTLTMLASNBBJ3K6P5N5ZLAMG'}\n","Grid 22\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762663650001, 'update_timestamp_ms': 1762663650001, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'BMUV7BHDMUGYIR2DNNMAOXVY', 'name': 'projects/ext-datasets/operations/BMUV7BHDMUGYIR2DNNMAOXVY'}\n","Grid 23\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762663658530, 'update_timestamp_ms': 1762663658530, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G5Q323CK5XIMMXQ7R32EOCOY', 'name': 'projects/ext-datasets/operations/G5Q323CK5XIMMXQ7R32EOCOY'}\n","Grid 24\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762663665453, 'update_timestamp_ms': 1762663665453, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K2EXPBLVMPOQTMDKWNVEAGBV', 'name': 'projects/ext-datasets/operations/K2EXPBLVMPOQTMDKWNVEAGBV'}\n","Grid 25\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762663673125, 'update_timestamp_ms': 1762663673125, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NAAECTEI52Q7LNPEVTHHD7UJ', 'name': 'projects/ext-datasets/operations/NAAECTEI52Q7LNPEVTHHD7UJ'}\n","Grid 26\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762663679756, 'update_timestamp_ms': 1762663679756, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MWN2KXYJJ3YY5VETVU5DAGFX', 'name': 'projects/ext-datasets/operations/MWN2KXYJJ3YY5VETVU5DAGFX'}\n","Grid 27\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762663686456, 'update_timestamp_ms': 1762663686456, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3AFPA3QT2ZYJ7UQXU7WDTA5X', 'name': 'projects/ext-datasets/operations/3AFPA3QT2ZYJ7UQXU7WDTA5X'}\n","Grid 28\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762663692690, 'update_timestamp_ms': 1762663692690, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DB7DD2TIVCR5ZDNZFPEIGEP5', 'name': 'projects/ext-datasets/operations/DB7DD2TIVCR5ZDNZFPEIGEP5'}\n","Grid 29\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762663698208, 'update_timestamp_ms': 1762663698208, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QPE57Y37LNDBQHGESKKDRBVQ', 'name': 'projects/ext-datasets/operations/QPE57Y37LNDBQHGESKKDRBVQ'}\n","Grid 30\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762663702674, 'update_timestamp_ms': 1762663702674, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'Y5I4DFMH7NH3XBFOXEXQPXKL', 'name': 'projects/ext-datasets/operations/Y5I4DFMH7NH3XBFOXEXQPXKL'}\n","Grid 31\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762663711360, 'update_timestamp_ms': 1762663711360, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PWM5YTRG3DPYRZGLX4QFGZT5', 'name': 'projects/ext-datasets/operations/PWM5YTRG3DPYRZGLX4QFGZT5'}\n","Grid 32\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762663720094, 'update_timestamp_ms': 1762663720094, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FIES3SUJYX3NLYX64UETMUTG', 'name': 'projects/ext-datasets/operations/FIES3SUJYX3NLYX64UETMUTG'}\n","Grid 33\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762663728790, 'update_timestamp_ms': 1762663728790, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SC6VMP7WP5RHNJFSQ747BCQ2', 'name': 'projects/ext-datasets/operations/SC6VMP7WP5RHNJFSQ747BCQ2'}\n","Grid 34\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762663735030, 'update_timestamp_ms': 1762663735030, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'G6O3OU37XUQOKE4R5B6FXCYP', 'name': 'projects/ext-datasets/operations/G6O3OU37XUQOKE4R5B6FXCYP'}\n","Grid 35\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762663744081, 'update_timestamp_ms': 1762663744081, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7MP5LZ7IH4O5TWXMEBP7JVIS', 'name': 'projects/ext-datasets/operations/7MP5LZ7IH4O5TWXMEBP7JVIS'}\n","Grid 36\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762663751712, 'update_timestamp_ms': 1762663751712, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'USS54YJTU7N4VZ5VGUMYYT7P', 'name': 'projects/ext-datasets/operations/USS54YJTU7N4VZ5VGUMYYT7P'}\n","Grid 37\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_37_2018', 'priority': 100, 'creation_timestamp_ms': 1762663761134, 'update_timestamp_ms': 1762663761134, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ECL6WSNZMH5ICPE4VTV72YRL', 'name': 'projects/ext-datasets/operations/ECL6WSNZMH5ICPE4VTV72YRL'}\n","Grid 38\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_38_2018', 'priority': 100, 'creation_timestamp_ms': 1762663767823, 'update_timestamp_ms': 1762663767823, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EB2POV4JCEZ6QQ56VB7BCKJG', 'name': 'projects/ext-datasets/operations/EB2POV4JCEZ6QQ56VB7BCKJG'}\n","Grid 39\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_39_2018', 'priority': 100, 'creation_timestamp_ms': 1762663776544, 'update_timestamp_ms': 1762663776544, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NHY7CBINS7J2ZJTF4LFPZ4UV', 'name': 'projects/ext-datasets/operations/NHY7CBINS7J2ZJTF4LFPZ4UV'}\n","Grid 40\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_40_2018', 'priority': 100, 'creation_timestamp_ms': 1762663781778, 'update_timestamp_ms': 1762663781778, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TEDC6SJ6XJVK2XVAIW7XKFUD', 'name': 'projects/ext-datasets/operations/TEDC6SJ6XJVK2XVAIW7XKFUD'}\n","Grid 41\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_41_2018', 'priority': 100, 'creation_timestamp_ms': 1762663789800, 'update_timestamp_ms': 1762663789800, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KS5GS7FHXPGJONAIHTEVHYC7', 'name': 'projects/ext-datasets/operations/KS5GS7FHXPGJONAIHTEVHYC7'}\n","Grid 42\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_42_2018', 'priority': 100, 'creation_timestamp_ms': 1762663796702, 'update_timestamp_ms': 1762663796702, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PSF7PEPBP6GDPHXHGVAGWECG', 'name': 'projects/ext-datasets/operations/PSF7PEPBP6GDPHXHGVAGWECG'}\n","Grid 43\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_43_2018', 'priority': 100, 'creation_timestamp_ms': 1762663805676, 'update_timestamp_ms': 1762663805676, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WWNGLA4RHU5YPSV4HOQZBDUC', 'name': 'projects/ext-datasets/operations/WWNGLA4RHU5YPSV4HOQZBDUC'}\n","Grid 44\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_44_2018', 'priority': 100, 'creation_timestamp_ms': 1762663810992, 'update_timestamp_ms': 1762663810992, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A3MHZDDTEW4RGWEDX5UCQMZH', 'name': 'projects/ext-datasets/operations/A3MHZDDTEW4RGWEDX5UCQMZH'}\n","Grid 45\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_45_2018', 'priority': 100, 'creation_timestamp_ms': 1762663819632, 'update_timestamp_ms': 1762663819632, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WD6EHBLYUYLE5226KXJ45MUD', 'name': 'projects/ext-datasets/operations/WD6EHBLYUYLE5226KXJ45MUD'}\n","Grid 46\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_46_2018', 'priority': 100, 'creation_timestamp_ms': 1762663826176, 'update_timestamp_ms': 1762663826176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VKXFLHYNJWRUNQFS4ZA3G6AZ', 'name': 'projects/ext-datasets/operations/VKXFLHYNJWRUNQFS4ZA3G6AZ'}\n","Grid 47\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_47_2018', 'priority': 100, 'creation_timestamp_ms': 1762663833140, 'update_timestamp_ms': 1762663833140, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PDHXEB24S7SAVVKYDYL2C4YE', 'name': 'projects/ext-datasets/operations/PDHXEB24S7SAVVKYDYL2C4YE'}\n","Grid 48\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_48_2018', 'priority': 100, 'creation_timestamp_ms': 1762663841579, 'update_timestamp_ms': 1762663841579, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SH5IKCYI6ORNBHBUQYG5CE5X', 'name': 'projects/ext-datasets/operations/SH5IKCYI6ORNBHBUQYG5CE5X'}\n","Grid 49\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_49_2018', 'priority': 100, 'creation_timestamp_ms': 1762663849742, 'update_timestamp_ms': 1762663849742, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WXGHZKERQ3WE3HTYGOVT6RVE', 'name': 'projects/ext-datasets/operations/WXGHZKERQ3WE3HTYGOVT6RVE'}\n","Grid 50\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_50_2018', 'priority': 100, 'creation_timestamp_ms': 1762663857103, 'update_timestamp_ms': 1762663857103, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'B3JD2OLYY4LQTWG67I2ZUMNY', 'name': 'projects/ext-datasets/operations/B3JD2OLYY4LQTWG67I2ZUMNY'}\n","Grid 51\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_51_2018', 'priority': 100, 'creation_timestamp_ms': 1762663863121, 'update_timestamp_ms': 1762663863121, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZMLF5A446ZHJUZCDKADL5QNA', 'name': 'projects/ext-datasets/operations/ZMLF5A446ZHJUZCDKADL5QNA'}\n","Grid 52\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_52_2018', 'priority': 100, 'creation_timestamp_ms': 1762663870721, 'update_timestamp_ms': 1762663870721, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '56OHJBDKRWXYNPQHH6MFMBG3', 'name': 'projects/ext-datasets/operations/56OHJBDKRWXYNPQHH6MFMBG3'}\n","Grid 53\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_53_2018', 'priority': 100, 'creation_timestamp_ms': 1762663874076, 'update_timestamp_ms': 1762663874076, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RU46ZESGMS4Q3ATK26TVNIHS', 'name': 'projects/ext-datasets/operations/RU46ZESGMS4Q3ATK26TVNIHS'}\n","Grid 54\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_54_2018', 'priority': 100, 'creation_timestamp_ms': 1762663883001, 'update_timestamp_ms': 1762663883001, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '65TAOCLGJN6DU7XGB4GZKRKK', 'name': 'projects/ext-datasets/operations/65TAOCLGJN6DU7XGB4GZKRKK'}\n","Grid 55\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_55_2018', 'priority': 100, 'creation_timestamp_ms': 1762663893940, 'update_timestamp_ms': 1762663893940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T5A7UTYK2VM3UFMM3MJ6BXRW', 'name': 'projects/ext-datasets/operations/T5A7UTYK2VM3UFMM3MJ6BXRW'}\n","Grid 56\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_56_2018', 'priority': 100, 'creation_timestamp_ms': 1762663899357, 'update_timestamp_ms': 1762663899357, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'P6K3Q6R7ZC3MERGKZ6QXYM2N', 'name': 'projects/ext-datasets/operations/P6K3Q6R7ZC3MERGKZ6QXYM2N'}\n","Grid 57\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_57_2018', 'priority': 100, 'creation_timestamp_ms': 1762663907279, 'update_timestamp_ms': 1762663907279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RJH7DNMXHIH6PDL66LZFJCKS', 'name': 'projects/ext-datasets/operations/RJH7DNMXHIH6PDL66LZFJCKS'}\n","Grid 58\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_58_2018', 'priority': 100, 'creation_timestamp_ms': 1762663912561, 'update_timestamp_ms': 1762663912561, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4MXJ5EPJBGOMAWEAK6BDHVGE', 'name': 'projects/ext-datasets/operations/4MXJ5EPJBGOMAWEAK6BDHVGE'}\n","Grid 59\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_59_2018', 'priority': 100, 'creation_timestamp_ms': 1762663920998, 'update_timestamp_ms': 1762663920998, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '57HVEPIIR4JEKBG7J4T2FRLD', 'name': 'projects/ext-datasets/operations/57HVEPIIR4JEKBG7J4T2FRLD'}\n","Grid 60\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_60_2018', 'priority': 100, 'creation_timestamp_ms': 1762663925300, 'update_timestamp_ms': 1762663925300, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZB6N4YQM353T4CZBYLVUAH3D', 'name': 'projects/ext-datasets/operations/ZB6N4YQM353T4CZBYLVUAH3D'}\n","Grid 61\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_61_2018', 'priority': 100, 'creation_timestamp_ms': 1762663933407, 'update_timestamp_ms': 1762663933407, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VHTIGKFDTV7OMYGIBLDCAV22', 'name': 'projects/ext-datasets/operations/VHTIGKFDTV7OMYGIBLDCAV22'}\n","Grid 62\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_62_2018', 'priority': 100, 'creation_timestamp_ms': 1762663942021, 'update_timestamp_ms': 1762663942021, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NH2SNA63OXC76IN3WHWLEZ5T', 'name': 'projects/ext-datasets/operations/NH2SNA63OXC76IN3WHWLEZ5T'}\n","Grid 63\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_63_2018', 'priority': 100, 'creation_timestamp_ms': 1762663949713, 'update_timestamp_ms': 1762663949713, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NFW6EP2F7TWXCGZTGFKSNJJA', 'name': 'projects/ext-datasets/operations/NFW6EP2F7TWXCGZTGFKSNJJA'}\n","Grid 64\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_64_2018', 'priority': 100, 'creation_timestamp_ms': 1762663972275, 'update_timestamp_ms': 1762663972275, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IH6EHYSF6RICKGLTJXLEJTQT', 'name': 'projects/ext-datasets/operations/IH6EHYSF6RICKGLTJXLEJTQT'}\n","Grid 65\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_65_2018', 'priority': 100, 'creation_timestamp_ms': 1762663978337, 'update_timestamp_ms': 1762663978337, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IMD2EJILY3GBGP4ODN56RI3B', 'name': 'projects/ext-datasets/operations/IMD2EJILY3GBGP4ODN56RI3B'}\n","Grid 66\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_66_2018', 'priority': 100, 'creation_timestamp_ms': 1762663988697, 'update_timestamp_ms': 1762663988697, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YWQTVH5IYEM4S2OKPSPWIAPH', 'name': 'projects/ext-datasets/operations/YWQTVH5IYEM4S2OKPSPWIAPH'}\n","Grid 67\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_67_2018', 'priority': 100, 'creation_timestamp_ms': 1762663996373, 'update_timestamp_ms': 1762663996373, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '27N3EXCI3VARLUI2SDOTE5BR', 'name': 'projects/ext-datasets/operations/27N3EXCI3VARLUI2SDOTE5BR'}\n","Grid 68\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_68_2018', 'priority': 100, 'creation_timestamp_ms': 1762664004815, 'update_timestamp_ms': 1762664004815, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VL5AF6S4AOX7G7VGACADXMVJ', 'name': 'projects/ext-datasets/operations/VL5AF6S4AOX7G7VGACADXMVJ'}\n","Grid 69\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_69_2018', 'priority': 100, 'creation_timestamp_ms': 1762664014708, 'update_timestamp_ms': 1762664014708, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'VLXNSDOSANSXYUJEFDHAPQ3B', 'name': 'projects/ext-datasets/operations/VLXNSDOSANSXYUJEFDHAPQ3B'}\n","Grid 70\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_70_2018', 'priority': 100, 'creation_timestamp_ms': 1762664024423, 'update_timestamp_ms': 1762664024423, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'T35LURRRU3UHVLSHWZ66PHIU', 'name': 'projects/ext-datasets/operations/T35LURRRU3UHVLSHWZ66PHIU'}\n","Grid 71\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_71_2018', 'priority': 100, 'creation_timestamp_ms': 1762664032431, 'update_timestamp_ms': 1762664032431, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FYRKQWWRXKIXHX6X3DMGVIGE', 'name': 'projects/ext-datasets/operations/FYRKQWWRXKIXHX6X3DMGVIGE'}\n","Grid 72\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_72_2018', 'priority': 100, 'creation_timestamp_ms': 1762664036998, 'update_timestamp_ms': 1762664036998, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'HILZGFL3KWGDCJA4OPKU2FHR', 'name': 'projects/ext-datasets/operations/HILZGFL3KWGDCJA4OPKU2FHR'}\n","Grid 73\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_73_2018', 'priority': 100, 'creation_timestamp_ms': 1762664045448, 'update_timestamp_ms': 1762664045448, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JAOF2L5EJ7VBNBN5GYFKVKBQ', 'name': 'projects/ext-datasets/operations/JAOF2L5EJ7VBNBN5GYFKVKBQ'}\n","Grid 74\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_74_2018', 'priority': 100, 'creation_timestamp_ms': 1762664051637, 'update_timestamp_ms': 1762664051637, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AGA2LYPNRCWEDRBYJWRAPOLH', 'name': 'projects/ext-datasets/operations/AGA2LYPNRCWEDRBYJWRAPOLH'}\n","Grid 75\n","curr_year 2018\n","Saving data for Birbhum 2018\n","Task Started {'state': 'READY', 'description': 'Birbhum_75_2018', 'priority': 100, 'creation_timestamp_ms': 1762664059346, 'update_timestamp_ms': 1762664059346, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YO4SQXJZQKL4ZMHHZXF3BFNU', 'name': 'projects/ext-datasets/operations/YO4SQXJZQKL4ZMHHZXF3BFNU'}\n","2821.194188594818\n","Year 2018, District 14: Dakshin Dinajpur, grids: 37\n","Grid 0\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664073044, 'update_timestamp_ms': 1762664073044, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WRSC7TBNVGKGUOVTS2YX5GSS', 'name': 'projects/ext-datasets/operations/WRSC7TBNVGKGUOVTS2YX5GSS'}\n","Grid 1\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664080530, 'update_timestamp_ms': 1762664080530, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZR4WASTTV4KDNTKVN7M4YDLR', 'name': 'projects/ext-datasets/operations/ZR4WASTTV4KDNTKVN7M4YDLR'}\n","Grid 2\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762664088096, 'update_timestamp_ms': 1762664088096, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A2R342BUIFTPK3BF4B37PWVH', 'name': 'projects/ext-datasets/operations/A2R342BUIFTPK3BF4B37PWVH'}\n","Grid 3\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762664096150, 'update_timestamp_ms': 1762664096150, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FCNGYVV6INADTDCGBVXAZZY5', 'name': 'projects/ext-datasets/operations/FCNGYVV6INADTDCGBVXAZZY5'}\n","Grid 4\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762664103831, 'update_timestamp_ms': 1762664103831, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '62464TYAT2PKXK5YCIFPFIXQ', 'name': 'projects/ext-datasets/operations/62464TYAT2PKXK5YCIFPFIXQ'}\n","Grid 5\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762664112856, 'update_timestamp_ms': 1762664112856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EOTDLUXVVPCIHRDRPOCJ7AKP', 'name': 'projects/ext-datasets/operations/EOTDLUXVVPCIHRDRPOCJ7AKP'}\n","Grid 6\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762664118061, 'update_timestamp_ms': 1762664118061, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'WYKD5KNHUOYOZFWMDXNBSXCA', 'name': 'projects/ext-datasets/operations/WYKD5KNHUOYOZFWMDXNBSXCA'}\n","Grid 7\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762664122228, 'update_timestamp_ms': 1762664122228, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '53UAS25DUR45DOW7S2ZIHAZE', 'name': 'projects/ext-datasets/operations/53UAS25DUR45DOW7S2ZIHAZE'}\n","Grid 8\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762664129592, 'update_timestamp_ms': 1762664129592, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LKLX7A5H2IISEGQOMIUDJIZM', 'name': 'projects/ext-datasets/operations/LKLX7A5H2IISEGQOMIUDJIZM'}\n","Grid 9\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762664138306, 'update_timestamp_ms': 1762664138306, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ORDPCTPYHGFF2733LNV3ZPJG', 'name': 'projects/ext-datasets/operations/ORDPCTPYHGFF2733LNV3ZPJG'}\n","Grid 10\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762664148417, 'update_timestamp_ms': 1762664148417, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZ3ABRVLLCDG2T72JU7OIEDH', 'name': 'projects/ext-datasets/operations/KZ3ABRVLLCDG2T72JU7OIEDH'}\n","Grid 11\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762664155360, 'update_timestamp_ms': 1762664155360, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ATP2S4CRFW667RUCR4KU522Z', 'name': 'projects/ext-datasets/operations/ATP2S4CRFW667RUCR4KU522Z'}\n","Grid 12\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762664162579, 'update_timestamp_ms': 1762664162579, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IWAHODRCOHSRS2ICK5ES4PLW', 'name': 'projects/ext-datasets/operations/IWAHODRCOHSRS2ICK5ES4PLW'}\n","Grid 13\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762664170862, 'update_timestamp_ms': 1762664170862, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A3SMQDKTQZFSFDD4YFSNOC5U', 'name': 'projects/ext-datasets/operations/A3SMQDKTQZFSFDD4YFSNOC5U'}\n","Grid 14\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762664183395, 'update_timestamp_ms': 1762664183395, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'I4SEFYNGSRJX2CA3MXLUSE7L', 'name': 'projects/ext-datasets/operations/I4SEFYNGSRJX2CA3MXLUSE7L'}\n","Grid 15\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762664191470, 'update_timestamp_ms': 1762664191470, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZNXGGZKBUGUTRCAK4O7K2ORW', 'name': 'projects/ext-datasets/operations/ZNXGGZKBUGUTRCAK4O7K2ORW'}\n","Grid 16\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762664196031, 'update_timestamp_ms': 1762664196031, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2TJN6PNP3HR77NNJ5R2PVVW5', 'name': 'projects/ext-datasets/operations/2TJN6PNP3HR77NNJ5R2PVVW5'}\n","Grid 17\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762664202924, 'update_timestamp_ms': 1762664202924, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'L7AM2H3WPXAYWGDJTRM3HYFO', 'name': 'projects/ext-datasets/operations/L7AM2H3WPXAYWGDJTRM3HYFO'}\n","Grid 18\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762664217745, 'update_timestamp_ms': 1762664217745, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UXFAB6GZRTW2HJIHSQO72GOT', 'name': 'projects/ext-datasets/operations/UXFAB6GZRTW2HJIHSQO72GOT'}\n","Grid 19\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762664226176, 'update_timestamp_ms': 1762664226176, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XDKZM4CYU7JXGIMQF3YY4PWD', 'name': 'projects/ext-datasets/operations/XDKZM4CYU7JXGIMQF3YY4PWD'}\n","Grid 20\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762664233704, 'update_timestamp_ms': 1762664233704, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '2SZDRF7WC5X7656JH7KDZ3WF', 'name': 'projects/ext-datasets/operations/2SZDRF7WC5X7656JH7KDZ3WF'}\n","Grid 21\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762664248279, 'update_timestamp_ms': 1762664248279, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3KD3BG3HIHJUROC6RCMQP4S4', 'name': 'projects/ext-datasets/operations/3KD3BG3HIHJUROC6RCMQP4S4'}\n","Grid 22\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762664255705, 'update_timestamp_ms': 1762664255705, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XVSNVNMLA4BPOVAVCGDCBSUP', 'name': 'projects/ext-datasets/operations/XVSNVNMLA4BPOVAVCGDCBSUP'}\n","Grid 23\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762664271104, 'update_timestamp_ms': 1762664271104, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KK2XZ6YFYF7H6IQ4Z3V2K6FR', 'name': 'projects/ext-datasets/operations/KK2XZ6YFYF7H6IQ4Z3V2K6FR'}\n","Grid 24\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762664279095, 'update_timestamp_ms': 1762664279095, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PL52ONVJZD7PKQNS2TBFND6J', 'name': 'projects/ext-datasets/operations/PL52ONVJZD7PKQNS2TBFND6J'}\n","Grid 25\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762664284347, 'update_timestamp_ms': 1762664284347, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UVMEMENXU3HOR43V6NHO6OM6', 'name': 'projects/ext-datasets/operations/UVMEMENXU3HOR43V6NHO6OM6'}\n","Grid 26\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762664292053, 'update_timestamp_ms': 1762664292053, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5VGYNHRU5MM77ZVACEZAI6SY', 'name': 'projects/ext-datasets/operations/5VGYNHRU5MM77ZVACEZAI6SY'}\n","Grid 27\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762664298908, 'update_timestamp_ms': 1762664298908, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6TV5UZ564K3RZ6RULWKZZSFN', 'name': 'projects/ext-datasets/operations/6TV5UZ564K3RZ6RULWKZZSFN'}\n","Grid 28\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762664303868, 'update_timestamp_ms': 1762664303868, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PPRLKYSXBFI6F3XSSB2CALXJ', 'name': 'projects/ext-datasets/operations/PPRLKYSXBFI6F3XSSB2CALXJ'}\n","Grid 29\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762664311473, 'update_timestamp_ms': 1762664311473, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4QKGB674VY46L6HHLFTAUDHK', 'name': 'projects/ext-datasets/operations/4QKGB674VY46L6HHLFTAUDHK'}\n","Grid 30\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_30_2018', 'priority': 100, 'creation_timestamp_ms': 1762664318310, 'update_timestamp_ms': 1762664318310, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4F7BB7DJRPVBHCEVQ6ZV6OSN', 'name': 'projects/ext-datasets/operations/4F7BB7DJRPVBHCEVQ6ZV6OSN'}\n","Grid 31\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_31_2018', 'priority': 100, 'creation_timestamp_ms': 1762664325371, 'update_timestamp_ms': 1762664325371, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LFXTJ4MDTBICB2J54A6NII7A', 'name': 'projects/ext-datasets/operations/LFXTJ4MDTBICB2J54A6NII7A'}\n","Grid 32\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_32_2018', 'priority': 100, 'creation_timestamp_ms': 1762664331713, 'update_timestamp_ms': 1762664331713, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'UGEKFPPPWSI6S2QECYLXUAIS', 'name': 'projects/ext-datasets/operations/UGEKFPPPWSI6S2QECYLXUAIS'}\n","Grid 33\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_33_2018', 'priority': 100, 'creation_timestamp_ms': 1762664340044, 'update_timestamp_ms': 1762664340044, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'OKWS5AKSZBNIJMDQZPX3DO5I', 'name': 'projects/ext-datasets/operations/OKWS5AKSZBNIJMDQZPX3DO5I'}\n","Grid 34\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_34_2018', 'priority': 100, 'creation_timestamp_ms': 1762664348038, 'update_timestamp_ms': 1762664348038, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '7B6CUKVIVPZLZH36SJJDIZC4', 'name': 'projects/ext-datasets/operations/7B6CUKVIVPZLZH36SJJDIZC4'}\n","Grid 35\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_35_2018', 'priority': 100, 'creation_timestamp_ms': 1762664358407, 'update_timestamp_ms': 1762664358407, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'GGKEM47OSNSJ4M2QVJL3CLD4', 'name': 'projects/ext-datasets/operations/GGKEM47OSNSJ4M2QVJL3CLD4'}\n","Grid 36\n","curr_year 2018\n","Saving data for Dakshin Dinajpur 2018\n","Task Started {'state': 'READY', 'description': 'Dakshin Dinajpur_36_2018', 'priority': 100, 'creation_timestamp_ms': 1762664364512, 'update_timestamp_ms': 1762664364512, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZI7LHFRUCDLSECQFOS6JAI2Q', 'name': 'projects/ext-datasets/operations/ZI7LHFRUCDLSECQFOS6JAI2Q'}\n","3126.3451647758484\n","Year 2018, District 15: Darjiling, grids: 2\n","Grid 0\n","curr_year 2018\n","Saving data for Darjiling 2018\n","Task Started {'state': 'READY', 'description': 'Darjiling_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664376817, 'update_timestamp_ms': 1762664376817, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFZCUUXASEZZKFGGUCP2P5GU', 'name': 'projects/ext-datasets/operations/IFZCUUXASEZZKFGGUCP2P5GU'}\n","Grid 1\n","curr_year 2018\n","Saving data for Darjiling 2018\n","Task Started {'state': 'READY', 'description': 'Darjiling_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664383693, 'update_timestamp_ms': 1762664383693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '44DTTT2UGSGPC5PJYRDSK4EV', 'name': 'projects/ext-datasets/operations/44DTTT2UGSGPC5PJYRDSK4EV'}\n","3145.506931781769\n","Year 2018, District 16: Haora, grids: 30\n","Grid 0\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664398092, 'update_timestamp_ms': 1762664398092, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4AJORJDRNG6LP53KMGIYCXMH', 'name': 'projects/ext-datasets/operations/4AJORJDRNG6LP53KMGIYCXMH'}\n","Grid 1\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664405353, 'update_timestamp_ms': 1762664405353, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DMGRBT4HG5WKSSM2CQDCFQGW', 'name': 'projects/ext-datasets/operations/DMGRBT4HG5WKSSM2CQDCFQGW'}\n","Grid 2\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762664412695, 'update_timestamp_ms': 1762664412695, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XU43F4PQAWPNJRLWOC3PZTTZ', 'name': 'projects/ext-datasets/operations/XU43F4PQAWPNJRLWOC3PZTTZ'}\n","Grid 3\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762664419564, 'update_timestamp_ms': 1762664419564, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'MM4MFBBIP46U75JCTAWEF325', 'name': 'projects/ext-datasets/operations/MM4MFBBIP46U75JCTAWEF325'}\n","Grid 4\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762664427124, 'update_timestamp_ms': 1762664427124, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IFIN6KZ4KF4JYO3WIOIZQEQK', 'name': 'projects/ext-datasets/operations/IFIN6KZ4KF4JYO3WIOIZQEQK'}\n","Grid 5\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762664434906, 'update_timestamp_ms': 1762664434906, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'PEZIF46GZ753LJRMW5V7EGWX', 'name': 'projects/ext-datasets/operations/PEZIF46GZ753LJRMW5V7EGWX'}\n","Grid 6\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762664443151, 'update_timestamp_ms': 1762664443151, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'DLVEWDCZKMIJQPVDADOVYUBT', 'name': 'projects/ext-datasets/operations/DLVEWDCZKMIJQPVDADOVYUBT'}\n","Grid 7\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762664448495, 'update_timestamp_ms': 1762664448495, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'K4QSIV3GJKSAUILFTVMPTPW3', 'name': 'projects/ext-datasets/operations/K4QSIV3GJKSAUILFTVMPTPW3'}\n","Grid 8\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762664453529, 'update_timestamp_ms': 1762664453529, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'SKMRID4ZKASKWRSPD5KOPS55', 'name': 'projects/ext-datasets/operations/SKMRID4ZKASKWRSPD5KOPS55'}\n","Grid 9\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762664463309, 'update_timestamp_ms': 1762664463309, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'YJTSZOTEFBWLQ2VR36PMUHCV', 'name': 'projects/ext-datasets/operations/YJTSZOTEFBWLQ2VR36PMUHCV'}\n","Grid 10\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762664469940, 'update_timestamp_ms': 1762664469940, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LR5DDCWXWBLZWTTPTR6AB3HM', 'name': 'projects/ext-datasets/operations/LR5DDCWXWBLZWTTPTR6AB3HM'}\n","Grid 11\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762664478105, 'update_timestamp_ms': 1762664478105, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QWBZ24PODX3TCUEW7SK56EKU', 'name': 'projects/ext-datasets/operations/QWBZ24PODX3TCUEW7SK56EKU'}\n","Grid 12\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762664485766, 'update_timestamp_ms': 1762664485766, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6HQP2DXYEJ3RCR3PPY5SOHPZ', 'name': 'projects/ext-datasets/operations/6HQP2DXYEJ3RCR3PPY5SOHPZ'}\n","Grid 13\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762664493474, 'update_timestamp_ms': 1762664493474, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'V4CGS5URTKASIAASJOEYUTP7', 'name': 'projects/ext-datasets/operations/V4CGS5URTKASIAASJOEYUTP7'}\n","Grid 14\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762664500714, 'update_timestamp_ms': 1762664500714, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JWHRKPTAWQLVGGKDBABX36A3', 'name': 'projects/ext-datasets/operations/JWHRKPTAWQLVGGKDBABX36A3'}\n","Grid 15\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762664504652, 'update_timestamp_ms': 1762664504652, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'IK765SYM32CH3J4YCKTH5YMT', 'name': 'projects/ext-datasets/operations/IK765SYM32CH3J4YCKTH5YMT'}\n","Grid 16\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762664512845, 'update_timestamp_ms': 1762664512845, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '66G25QAKNUVJJZU6K4GIAZLI', 'name': 'projects/ext-datasets/operations/66G25QAKNUVJJZU6K4GIAZLI'}\n","Grid 17\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762664519636, 'update_timestamp_ms': 1762664519636, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4Q4APFMPM37CWZNDNBQIIVL6', 'name': 'projects/ext-datasets/operations/4Q4APFMPM37CWZNDNBQIIVL6'}\n","Grid 18\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762664525286, 'update_timestamp_ms': 1762664525286, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KKHIYOLL6BV2VSDOLG2LTKTP', 'name': 'projects/ext-datasets/operations/KKHIYOLL6BV2VSDOLG2LTKTP'}\n","Grid 19\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762664536675, 'update_timestamp_ms': 1762664536675, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'JLT5QIEXY5TB2EBUKWLXSZYP', 'name': 'projects/ext-datasets/operations/JLT5QIEXY5TB2EBUKWLXSZYP'}\n","Grid 20\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_20_2018', 'priority': 100, 'creation_timestamp_ms': 1762664544203, 'update_timestamp_ms': 1762664544203, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FHJRHJSOOMUO6PMBUJS2TS4F', 'name': 'projects/ext-datasets/operations/FHJRHJSOOMUO6PMBUJS2TS4F'}\n","Grid 21\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_21_2018', 'priority': 100, 'creation_timestamp_ms': 1762664552129, 'update_timestamp_ms': 1762664552129, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '224FAS3URVBHBNTR2DMQO7LZ', 'name': 'projects/ext-datasets/operations/224FAS3URVBHBNTR2DMQO7LZ'}\n","Grid 22\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_22_2018', 'priority': 100, 'creation_timestamp_ms': 1762664559936, 'update_timestamp_ms': 1762664559936, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'F2O57WV3QQRWIR3FEDBM2OLS', 'name': 'projects/ext-datasets/operations/F2O57WV3QQRWIR3FEDBM2OLS'}\n","Grid 23\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_23_2018', 'priority': 100, 'creation_timestamp_ms': 1762664567483, 'update_timestamp_ms': 1762664567483, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'NLSB5T3BA7WDJLPXRUWT7XCE', 'name': 'projects/ext-datasets/operations/NLSB5T3BA7WDJLPXRUWT7XCE'}\n","Grid 24\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_24_2018', 'priority': 100, 'creation_timestamp_ms': 1762664575693, 'update_timestamp_ms': 1762664575693, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'RHY37T5Q2OQC6Q4K6WTPDKWY', 'name': 'projects/ext-datasets/operations/RHY37T5Q2OQC6Q4K6WTPDKWY'}\n","Grid 25\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_25_2018', 'priority': 100, 'creation_timestamp_ms': 1762664584856, 'update_timestamp_ms': 1762664584856, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZYOYSH4L2WLKLGRUYDRO2IIL', 'name': 'projects/ext-datasets/operations/ZYOYSH4L2WLKLGRUYDRO2IIL'}\n","Grid 26\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_26_2018', 'priority': 100, 'creation_timestamp_ms': 1762664593327, 'update_timestamp_ms': 1762664593327, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XUSJK6T5NUNK33IWDEWIKVQS', 'name': 'projects/ext-datasets/operations/XUSJK6T5NUNK33IWDEWIKVQS'}\n","Grid 27\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_27_2018', 'priority': 100, 'creation_timestamp_ms': 1762664601828, 'update_timestamp_ms': 1762664601828, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZS2KGFD5LLDWONYEZARCUXMF', 'name': 'projects/ext-datasets/operations/ZS2KGFD5LLDWONYEZARCUXMF'}\n","Grid 28\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_28_2018', 'priority': 100, 'creation_timestamp_ms': 1762664609121, 'update_timestamp_ms': 1762664609121, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ZDTE4SYNHNMOT7XJC6EUM7KI', 'name': 'projects/ext-datasets/operations/ZDTE4SYNHNMOT7XJC6EUM7KI'}\n","Grid 29\n","curr_year 2018\n","Saving data for Haora 2018\n","Task Started {'state': 'READY', 'description': 'Haora_29_2018', 'priority': 100, 'creation_timestamp_ms': 1762664618077, 'update_timestamp_ms': 1762664618077, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'KZOPAN6335LX4MQDYKBGBIOI', 'name': 'projects/ext-datasets/operations/KZOPAN6335LX4MQDYKBGBIOI'}\n","3379.8888437747955\n","Year 2018, District 17: Hugli, grids: 55\n","Grid 0\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_0_2018', 'priority': 100, 'creation_timestamp_ms': 1762664632691, 'update_timestamp_ms': 1762664632691, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '4LSJGFY2HUKLUUD25DVIEWCH', 'name': 'projects/ext-datasets/operations/4LSJGFY2HUKLUUD25DVIEWCH'}\n","Grid 1\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_1_2018', 'priority': 100, 'creation_timestamp_ms': 1762664641441, 'update_timestamp_ms': 1762664641441, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'A4WXJXOMDX4Y4U3JSNH7CAOP', 'name': 'projects/ext-datasets/operations/A4WXJXOMDX4Y4U3JSNH7CAOP'}\n","Grid 2\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_2_2018', 'priority': 100, 'creation_timestamp_ms': 1762664648491, 'update_timestamp_ms': 1762664648491, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QCOZ2SE6IBF23LRZOWNIK7I5', 'name': 'projects/ext-datasets/operations/QCOZ2SE6IBF23LRZOWNIK7I5'}\n","Grid 3\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_3_2018', 'priority': 100, 'creation_timestamp_ms': 1762664655077, 'update_timestamp_ms': 1762664655077, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'QGY3SXOYPZJRM6CCJVD7N44O', 'name': 'projects/ext-datasets/operations/QGY3SXOYPZJRM6CCJVD7N44O'}\n","Grid 4\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_4_2018', 'priority': 100, 'creation_timestamp_ms': 1762664658729, 'update_timestamp_ms': 1762664658729, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'FEG7VEPW6AEF4TBAUNISMCVN', 'name': 'projects/ext-datasets/operations/FEG7VEPW6AEF4TBAUNISMCVN'}\n","Grid 5\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_5_2018', 'priority': 100, 'creation_timestamp_ms': 1762664667503, 'update_timestamp_ms': 1762664667503, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '6LFNZ5RUDRONF3QYFFTRTY2W', 'name': 'projects/ext-datasets/operations/6LFNZ5RUDRONF3QYFFTRTY2W'}\n","Grid 6\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_6_2018', 'priority': 100, 'creation_timestamp_ms': 1762664676185, 'update_timestamp_ms': 1762664676185, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'O23VN4G5WXMZZGQ5NIRKZDNF', 'name': 'projects/ext-datasets/operations/O23VN4G5WXMZZGQ5NIRKZDNF'}\n","Grid 7\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_7_2018', 'priority': 100, 'creation_timestamp_ms': 1762664684106, 'update_timestamp_ms': 1762664684106, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'AWFJ6TBAYZGDS4FGMAXOTC7I', 'name': 'projects/ext-datasets/operations/AWFJ6TBAYZGDS4FGMAXOTC7I'}\n","Grid 8\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_8_2018', 'priority': 100, 'creation_timestamp_ms': 1762664690851, 'update_timestamp_ms': 1762664690851, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5I3ID4OIFJKVQZMU3HUI3XJ5', 'name': 'projects/ext-datasets/operations/5I3ID4OIFJKVQZMU3HUI3XJ5'}\n","Grid 9\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_9_2018', 'priority': 100, 'creation_timestamp_ms': 1762664695983, 'update_timestamp_ms': 1762664695983, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XWFMYBV4GJQDNXQGETMD6PE6', 'name': 'projects/ext-datasets/operations/XWFMYBV4GJQDNXQGETMD6PE6'}\n","Grid 10\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_10_2018', 'priority': 100, 'creation_timestamp_ms': 1762664701159, 'update_timestamp_ms': 1762664701159, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TF7RAOWBKSVHM6VZ6DS6ZH2A', 'name': 'projects/ext-datasets/operations/TF7RAOWBKSVHM6VZ6DS6ZH2A'}\n","Grid 11\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_11_2018', 'priority': 100, 'creation_timestamp_ms': 1762664711085, 'update_timestamp_ms': 1762664711085, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '55HGWTLODW7NRPMW4EFOJ643', 'name': 'projects/ext-datasets/operations/55HGWTLODW7NRPMW4EFOJ643'}\n","Grid 12\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_12_2018', 'priority': 100, 'creation_timestamp_ms': 1762664718074, 'update_timestamp_ms': 1762664718074, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XEYISSMQHOBBT45CSUTN5CC5', 'name': 'projects/ext-datasets/operations/XEYISSMQHOBBT45CSUTN5CC5'}\n","Grid 13\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_13_2018', 'priority': 100, 'creation_timestamp_ms': 1762664723363, 'update_timestamp_ms': 1762664723363, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'XYDJYVBHE5MEXHJNFV6RI5D4', 'name': 'projects/ext-datasets/operations/XYDJYVBHE5MEXHJNFV6RI5D4'}\n","Grid 14\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_14_2018', 'priority': 100, 'creation_timestamp_ms': 1762664730830, 'update_timestamp_ms': 1762664730830, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '3D3I4DWRJUNUFQ3KMFCRPR65', 'name': 'projects/ext-datasets/operations/3D3I4DWRJUNUFQ3KMFCRPR65'}\n","Grid 15\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_15_2018', 'priority': 100, 'creation_timestamp_ms': 1762664735785, 'update_timestamp_ms': 1762664735785, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': '5YR6LDIBSLFGY2JJNJHTLOSY', 'name': 'projects/ext-datasets/operations/5YR6LDIBSLFGY2JJNJHTLOSY'}\n","Grid 16\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_16_2018', 'priority': 100, 'creation_timestamp_ms': 1762664736650, 'update_timestamp_ms': 1762664736650, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'TFVTOFLDYMIPGB7HHLZ3JDZL', 'name': 'projects/ext-datasets/operations/TFVTOFLDYMIPGB7HHLZ3JDZL'}\n","Grid 17\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_17_2018', 'priority': 100, 'creation_timestamp_ms': 1762664737289, 'update_timestamp_ms': 1762664737289, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'EYOMWWQPIQVBDRE4YDUWDWL3', 'name': 'projects/ext-datasets/operations/EYOMWWQPIQVBDRE4YDUWDWL3'}\n","Grid 18\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_18_2018', 'priority': 100, 'creation_timestamp_ms': 1762664743037, 'update_timestamp_ms': 1762664743037, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'LQRI6YRY4TXUSC4GKF6LYOE6', 'name': 'projects/ext-datasets/operations/LQRI6YRY4TXUSC4GKF6LYOE6'}\n","Grid 19\n","curr_year 2018\n","Saving data for Hugli 2018\n","Task Started {'state': 'READY', 'description': 'Hugli_19_2018', 'priority': 100, 'creation_timestamp_ms': 1762664744354, 'update_timestamp_ms': 1762664744354, 'start_timestamp_ms': 0, 'task_type': 'EXPORT_FEATURES', 'id': 'ML45MRARB7WAQXAZWBJI43YD', 'name': 'projects/ext-datasets/operations/ML45MRARB7WAQXAZWBJI43YD'}\n","Grid 20\n","curr_year 2018\n"]}],"source":["# years = ['2016']\n","for curr_year in years:\n","\n"," total_time = 0\n","\n"," dist_cnt = 0\n"," for district in dist_list:\n","\n"," # if dist_cnt < 3:\n"," # dist_cnt += 1\n"," # continue\n","\n"," start_time = time.time()\n","\n"," district_aoi = india_district_boundary.filter(ee.Filter.eq('Name', district)).geometry()\n"," district_aoi = district_aoi.intersection(agrozone)\n"," features = createGrids(district_aoi)\n","\n"," print(f'Year {curr_year}, District {dist_cnt}: {district}, grids: {len(features)}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{curr_year}/'\n"," os.makedirs(path, exist_ok=True)\n","\n"," df = pd.DataFrame()\n"," df['grid_num'] = list(range(len(features)))\n"," tree_points_list = []\n","\n"," i = 0\n"," for feature in features:\n"," print(f'Grid {i}')\n","\n"," coord = feature['geometry']['coordinates'][0]\n"," aoi = ee.Geometry.Polygon(coord)\n"," aoi = aoi.intersection(district_aoi)\n","\n"," img_name = district + \"_\" + str(i) + \"_\" + str(curr_year)\n","\n"," start_date = START_DATE[int(curr_year)]\n"," end_date = END_DATE[int(curr_year)]\n","\n"," # Get tree cover using both Dynamic World and IndiaSat (union)\n"," tree_cover = get_tree_cover(aoi, curr_year, scale=25)\n","\n"," season = 'kharif'\n"," try:\n"," image = get_s1_image(aoi, start_date[season], end_date[season])\n"," image = image.updateMask(tree_cover)\n"," except Exception as exp:\n"," print(\"S1 Error occured: \", season, exp)\n","\n"," try:\n"," s2_data = get_s2_image(aoi, start_date[season], end_date[season])\n"," s2_data = s2_data.updateMask(tree_cover)\n"," image = image.addBands(s2_data).select(s1_bands + s2_bands)\n"," image = image.rename([band + '_kharif' for band in s1_bands + s2_bands])\n"," except Exception as exp:\n"," print(\"S2 Error occured: \", season, exp)\n","\n"," for season in ['rabi', 'zaid']:\n"," try:\n"," s1_data = get_s1_image(aoi, start_date[season], end_date[season])\n"," s1_data = s1_data.updateMask(tree_cover)\n","\n"," except Exception as exp:\n"," print(\"S1 Error occured: \", season, exp)\n","\n"," try:\n"," s2_data = get_s2_image(aoi, start_date[season], end_date[season])\n"," s2_data = s2_data.updateMask(tree_cover)\n"," image_merged = s1_data.addBands(s2_data).select(s1_bands + s2_bands)\n"," image_merged = image_merged.rename([band + '_' + season for band in s1_bands + s2_bands])\n"," image = image.addBands(image_merged)\n","\n"," except Exception as exp:\n"," print(\"S2 Error occured: \", season, exp)\n","\n"," sample_points = image.sample(\n"," region = aoi,\n"," scale = 25,\n"," factor = 1,\n"," tileScale = 10,\n"," geometries = True\n"," )\n","\n"," sample_tree_cover = tree_cover.sample(\n"," region = aoi,\n"," scale = 25,\n"," factor = 1,\n"," tileScale = 10,\n"," geometries = True\n"," )\n","\n"," try:\n"," tree_points_list.append(sample_tree_cover.size().getInfo())\n"," except:\n"," tree_points_list.append(0)\n","\n"," try:\n"," task = save_data_csv(sample_points, img_name, district, curr_year)\n"," prev_task = task\n"," # tasks[curr_year][district] = task\n","\n"," except Exception as e:\n"," print(e)\n"," while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," # time.sleep(10)\n"," continue\n"," task = save_data_csv(sample_points, img_name, path, curr_year)\n"," # tasks[curr_year][district] = task\n"," prev_task = task\n","\n"," i += 1\n","\n"," df['tree_points'] = tree_points_list\n"," df.to_csv(path + 'tree_cover_points.csv', index=False)\n","\n"," dist_cnt += 1\n"," end_time = time.time()\n","\n"," total_time += (end_time - start_time)\n"," print(total_time)\n","\n"," print(\"Waiting for last task to be completed...\")\n"," while prev_task.status()['state'] != 'COMPLETED' and prev_task.status()['state'] != 'FAILED':\n"," continue\n"," print(\"Last task completed!\")\n","\n"," total_time += (time.time() - end_time)\n"," print(\"Total Time Taken:\", total_time)"]}],"metadata":{"colab":{"collapsed_sections":["snxgyeoRno-K"],"provenance":[{"file_id":"10rsYxkf8VtyBQYo6nDSxJeQg-Kr0OKDh","timestamp":1758123472823}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb b/utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb new file mode 100644 index 00000000..3b0c650b --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/predict_ccd_ch_results.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":3189,"status":"ok","timestamp":1775402326200,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"kMqXI9xEx9Ij"},"outputs":[],"source":["\n","import pandas as pd\n","from glob import glob\n","import joblib\n","import json\n","import time\n","from copy import deepcopy\n","import os\n","import re\n","import numpy as np\n","import ee\n","import ast"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":10,"status":"ok","timestamp":1771429377492,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"EX11mwSCcEls","outputId":"03f4e89d-5d52-4780-b0a8-6f7bf4864843"},"outputs":[{"name":"stdout","output_type":"stream","text":["Drive not mounted, so nothing to flush and unmount.\n"]}],"source":["# from google.colab import drive\n","# drive.flush_and_unmount()"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":22349,"status":"ok","timestamp":1775402348561,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"3AzHO9S6xtlx","outputId":"dc759937-64e1-4993-b5ea-30d480ae29d2"},"outputs":[{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":3,"metadata":{"executionInfo":{"elapsed":1,"status":"ok","timestamp":1775402348576,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"6v5AhpfqyCIX"},"outputs":[],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":4,"metadata":{"executionInfo":{"elapsed":2,"status":"ok","timestamp":1775402354345,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"27BoVlCPyGq5"},"outputs":[],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}\n"]},{"cell_type":"code","execution_count":5,"metadata":{"executionInfo":{"elapsed":2,"status":"ok","timestamp":1775402361003,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"z9qLXf0PyK7B"},"outputs":[],"source":["# agroclimatic_zone = 'Eastern Plateau & Hills Region'\n","agroclimatic_zone = 'Southern Plateau and Hills Region'\n","# agroclimatic_zone = \"East Coast Plains & Hills Region\"\n","# agroclimatic_zone = 'Western Plateau and Hills Region'\n","# agroclimatic_zone = \"Central Plateau & Hills Region\"\n","# agroclimatic_zone = 'Lower Gangetic Plain Region'\n","# agroclimatic_zone = 'Middle Gangetic Plain Region'\n","# agroclimatic_zone = 'Eastern Himalayan Region'\n","# agroclimatic_zone = 'Western Himalayan Region'\n","# agroclimatic_zone = 'Trans Gangetic Plain Region'\n","# agroclimatic_zone = \"Upper Gangetic Plain Region\"\n","# agroclimatic_zone = \"West Coast Plains & Ghat Region\" # Model not available\n","# agroclimatic_zone = 'Gujarat Plains & Hills Region' # Model not available\n","# agroclimatic_zone = 'Western Dry Region' # Model not available"]},{"cell_type":"code","execution_count":6,"metadata":{"executionInfo":{"elapsed":3,"status":"ok","timestamp":1775402369694,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"LNPicKBZyQfD"},"outputs":[],"source":["# Set the list of years for which to rename folders\n","years = ['2017']# ['2016', '2017', '2018','2019','2020','2021','2022','2023','2024']"]},{"cell_type":"markdown","metadata":{"id":"P_Php5yrbPm8"},"source":["# CHM"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":13,"status":"ok","timestamp":1775402390205,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"-m5EML9zyVX4","outputId":"444a0e1c-35ef-4049-9799-7eba9924b2f6"},"outputs":[{"output_type":"stream","name":"stdout","text":["['Anantapur']\n"]}],"source":["df = pd.read_csv(f'/content/drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = list(df['Name'])\n","dist_list = ['Anantapur']\n","print(dist_list)"]},{"cell_type":"code","execution_count":9,"metadata":{"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775402392754,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"tMOnWtVNygfj"},"outputs":[],"source":["# Set the root directory to the specified folder\n","root_dir = '/content/drive/MyDrive/'\n","os.chdir(root_dir)"]},{"cell_type":"code","execution_count":10,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":108,"status":"ok","timestamp":1775402395585,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ybIqOkpbykty","outputId":"76b07e97-7cca-4407-a346-174ea2639b37"},"outputs":[{"output_type":"stream","name":"stdout","text":["/content/drive/MyDrive\n"]}],"source":["! pwd"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":28,"status":"ok","timestamp":1775402397122,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"iiwNIME5yo3A","outputId":"9d48c10e-ba93-4a7b-eb20-3633974cfd7f"},"outputs":[{"output_type":"stream","name":"stdout","text":["core-stack-docs\n","kapil_test\n","Colab Notebooks\n","Pan_india_maps_screenshots\n","ATCF_ch_5jun25\n","Google Earth\n","Datasets\n","WBC_data\n","Apache\n","tiff_export\n","Pan_india\n","Aquifer_vector\n","Aquifer_export\n","TreeHealth\n","MGPR\n","pennar_lulc_23_24\n","LGPR1\n","lcw\n","Untitled form (File responses)\n","change_test\n","ET_downloads\n","core_stack_manual\n","WHR_2024\n","WHR_2023\n","WHR_2022\n","WHR_2021\n","WHR_2020\n","WHR_2019\n","WHR_2018\n","WHR_2017\n","WHR_2016\n","EPAHR_2016\n","EPAHR_2017\n","EPAHR_2018\n","EPAHR_2019\n","EPAHR_2020\n","EPAHR_2021\n","EPAHR_2022\n","EPAHR_2023\n","SPAHR_2016\n","SPAHR_2017\n","SPAHR_2018\n","SPAHR_2019\n","SPAHR_2020\n","SPAHR_2021\n","SPAHR_2022\n","SPAHR_2023\n","ECPHR_2016\n","ECPHR_2017\n","ECPHR_2018\n","ECPHR_2019\n","ECPHR_2020\n","ECPHR_2021\n","ECPHR_2022\n","ECPHR_2023\n","WPAHR_2016\n","WPAHR_2017\n","WPAHR_2018\n","WPAHR_2019\n","WPAHR_2020\n","WPAHR_2021\n","WPAHR_2022\n","WPAHR_2023\n","CPAHR_2016\n","CPAHR_2017\n","CPAHR_2018\n","CPAHR_2019\n","CPAHR_2020\n","CPAHR_2021\n","CPAHR_2022\n","CPAHR_2023\n","LGPR_2016\n","MGPR_2016\n","EHR_2016\n","EHR_2017\n","EHR_2018\n","EHR_2019\n","EHR_2020\n","EHR_2021\n","EHR_2022\n","EHR_2023\n","UGPR_2016\n","UGPR_2017\n","UGPR_2018\n","UGPR_2019\n","UGPR_2020\n","UGPR_2021\n","UGPR_2022\n","UGPR_2023\n","TGPR_2016\n","TGPR_2017\n","TGPR_2018\n","TGPR_2019\n","TGPR_2020\n","TGPR_2021\n","TGPR_2022\n","TGPR_2023\n","WCPGR_2016\n","WCPGR_2017\n","WCPGR_2018\n","WCPGR_2019\n","WCPGR_2020\n","WCPGR_2021\n","WCPGR_2022\n","WCPGR_2023\n","GPHR_2016\n","GPHR_2017\n","GPHR_2018\n","GPHR_2019\n","GPHR_2020\n","GPHR_2021\n","GPHR_2022\n","GPHR_2023\n","WDR_2016\n","WDR_2017\n","WDR_2018\n","WDR_2019\n","WDR_2020\n","WDR_2021\n","WDR_2022\n","WDR_2023\n","LGPR_2017\n","LGPR_2018\n","LGPR_2019\n","LGPR_2020\n","LGPR_2021\n","LGPR_2022\n","LGPR_2023\n","EHR_2024\n","WDR_2024\n","UGPR_2024\n","hydrological_boundaries\n","TGPR_2024\n","MGPR_2024\n","LGPR_2024\n","EPAHR_2024\n","SPAHR_2024\n","ECPHR_2024\n","WPAHR_2024\n","CPAHR_2024\n","MGPR_2017\n","MGPR_2018\n","MGPR_2019\n","MGPR_2020\n","MGPR_2021\n","MGPR_2022\n","MGPR_2023\n","mws_tmp\n","FOSS - CoRE Stack Material\n","wassan_boundaries\n","demand validtor\n"]}],"source":["# Get the current directory\n","current_directory = os.getcwd()\n","\n","# List all folders in the current directory\n","folders = [f for f in os.listdir(current_directory) if os.path.isdir(os.path.join(current_directory, f))]\n","\n","# Print the list of folders\n","# print(\"Folders in the current directory:\")\n","\n","for folder in folders:\n"," print(folder)"]},{"cell_type":"code","execution_count":12,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775402400124,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"VeRQDIsyysC-","outputId":"c9b23c9f-78c3-4baa-9bae-44dde8cb023c"},"outputs":[{"output_type":"stream","name":"stdout","text":["Folders starting with 'SPAHR':\n","SPAHR_2016\n","SPAHR_2017\n","SPAHR_2018\n","SPAHR_2019\n","SPAHR_2020\n","SPAHR_2021\n","SPAHR_2022\n","SPAHR_2023\n","SPAHR_2024\n"]}],"source":["# Regular expression pattern to match folder names starting with a particular pattern\n","pattern = re.compile(r'^' + f'{agroclimaticZone_acronym_dict[agroclimatic_zone]}')\n","\n","# Filter folders based on the pattern\n","matching_folders = [folder for folder in folders if pattern.match(folder)]\n","\n","# Print the matching folders\n","print(f\"Folders starting with '{agroclimaticZone_acronym_dict[agroclimatic_zone]}':\")\n","for folder in matching_folders:\n"," print(folder)"]},{"cell_type":"code","execution_count":13,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":11,"status":"ok","timestamp":1775402403714,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"KxX7a9qMywMW","outputId":"a0873f64-5add-4fde-9974-66029b019278"},"outputs":[{"output_type":"stream","name":"stdout","text":["2017\n","district_year_folders--> ['SPAHR_2017']\n"]}],"source":["# Leh (Ladkh) is the only district encountered having special character '(' and ')' in it. That's why its handled in a special way as seen here\n","\n","# Transfer files from duplicate folders to original folder\n","\n","dist_num = 0\n","# for district in dist_list:\n","# print(dist_num)\n","for year in years:\n"," print(year)\n","\n"," # orig_district = district\n"," # if district == 'Leh (Ladakh)':\n"," # district = 'Leh'\n","\n"," # pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + district + '_' + year)\n"," pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + year)\n"," district_year_folders = [folder for folder in matching_folders if pattern.match(folder)]\n"," print(\"district_year_folders-->\", district_year_folders)\n","\n"," # district = orig_district\n","\n"," while len(district_year_folders) > 1:\n"," source_folder = district_year_folders[0]\n"," destination_folder = district_year_folders[1]\n","\n"," files_to_move = os.listdir(source_folder)\n"," for file_name in files_to_move:\n"," source_path = os.path.join(source_folder, file_name)\n"," destination_path = os.path.join(destination_folder, file_name)\n"," os.rename(source_path, destination_path)\n","\n"," del district_year_folders[0]\n"," if len(district_year_folders) > 0:\n"," current_folder_name = district_year_folders[0]\n"," # new_folder_name = f'{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{district}_{year}'\n"," new_folder_name = f'{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{year}'\n"," os.rename(current_folder_name, new_folder_name)\n","\n"," # dist_num += 1"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775402407191,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"VjlSG5iPQkyP","outputId":"c10595ee-e3dd-47b0-dae2-9ec79f181cfd"},"outputs":[{"output_type":"stream","name":"stdout","text":["SPAHR_2017\n"]}],"source":["# Check all folders with more than 0 files\n","dist_num = 0\n","# for district in dist_list:\n","# print(dist_num)\n","for year in years:\n"," # orig_district = district\n"," # if district == 'Leh (Ladakh)':\n"," # district = 'Leh'\n","\n"," # Re-scan the current directory for folders after renaming\n"," current_directory = os.getcwd()\n"," folders = [f for f in os.listdir(current_directory) if os.path.isdir(os.path.join(current_directory, f))]\n"," # pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + district + '_' + year)\n"," pattern = re.compile(r'^' + agroclimaticZone_acronym_dict[agroclimatic_zone] + '_' + year)\n"," district_year_folders = [folder for folder in folders if pattern.match(folder)]\n","\n"," # district = orig_district\n","\n"," for folder in district_year_folders:\n"," if len(os.listdir(folder)) > 0:\n"," print(folder)\n","\n"," # dist_num += 1"]},{"cell_type":"code","execution_count":15,"metadata":{"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775402410054,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"aFKnyEbHRJku"},"outputs":[],"source":["# Change back to parent directory\n","os.chdir(os.path.dirname(os.getcwd()))"]},{"cell_type":"code","execution_count":16,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":15,"status":"ok","timestamp":1775402411295,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"dnodhGcxRbTn","outputId":"718cbfc3-4389-437a-dd2c-cdbf4d80c238"},"outputs":[{"output_type":"stream","name":"stdout","text":["/content/drive\n"]}],"source":["! pwd"]},{"cell_type":"code","execution_count":17,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":409,"status":"ok","timestamp":1775402413224,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"FPdn0lZ5bYDc","outputId":"c2b874d4-4a66-4752-a84d-570a5f6817ed"},"outputs":[{"output_type":"stream","name":"stdout","text":["666\n"]}],"source":["df = pd.read_csv('/content/drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n","print(df.shape[0])"]},{"cell_type":"code","execution_count":18,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775402414562,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"LvDi-4okc42F","outputId":"5b51c09f-7082-40a8-d336-bb9db8948a24"},"outputs":[{"output_type":"stream","name":"stdout","text":[" District \\\n","0 Nicobar Islands \n","1 North and Middle Andaman \n","2 South Andaman \n","3 Anantapur \n","4 Chittoor \n",".. ... \n","661 Pashchim Medinipur \n","662 Purba Medinipur \n","663 Puruliya \n","664 South 24 Parganas \n","665 Uttar Dinajpur \n","\n"," IntersectingZones \\\n","0 [] \n","1 [] \n","2 [] \n","3 [Southern Plateau and Hills Region] \n","4 [Southern Plateau and Hills Region, East Coast... \n",".. ... \n","661 [East Coast Plains & Hills Region, Eastern Pla... \n","662 [East Coast Plains & Hills Region, Lower Gange... \n","663 [Eastern Plateau & Hills Region, Lower Gangeti... \n","664 [Lower Gangetic Plain Region] \n","665 [Middle Gangetic Plain Region, Lower Gangetic ... \n","\n"," IntersectingAreas \\\n","0 [] \n","1 [] \n","2 [] \n","3 [19296696352.156364] \n","4 [11507580448.526262, 3718273353.5141] \n",".. ... \n","661 [27781135.047970004, 88721717.34314527, 930766... \n","662 [10359493.555347942, 3981478759.978645] \n","663 [6266123538.885962, 5557396.356690076] \n","664 [5541282840.37764] \n","665 [73899445.66614598, 3263671615.4575567, 414168... \n","\n"," AgroclimaticZone \n","0 NaN \n","1 NaN \n","2 NaN \n","3 Southern Plateau and Hills Region \n","4 Southern Plateau and Hills Region \n",".. ... \n","661 Lower Gangetic Plain Region \n","662 Lower Gangetic Plain Region \n","663 Eastern Plateau & Hills Region \n","664 Lower Gangetic Plain Region \n","665 Lower Gangetic Plain Region \n","\n","[666 rows x 4 columns]\n"]}],"source":["# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)\n","print(df)"]},{"cell_type":"code","execution_count":19,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":16,"status":"ok","timestamp":1775402418359,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ghNTHMVrc7bV","outputId":"0e6921a1-078b-458b-986b-9878e5c93da8"},"outputs":[{"output_type":"stream","name":"stdout","text":[" District IntersectingZones\n","3 Anantapur [Southern Plateau and Hills Region]\n"]}],"source":["district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n","district_mapping_df= district_mapping_df[district_mapping_df['District'].isin(dist_list)] ## Added extra\n","print(district_mapping_df)"]},{"cell_type":"code","execution_count":20,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"executionInfo":{"elapsed":18,"status":"ok","timestamp":1775402420291,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9LAuCvPWdAmC","outputId":"defbc02c-e8a6-4a3b-d209-3e585b601b6c"},"outputs":[{"output_type":"stream","name":"stdout","text":["0 Anantapur ['Southern Plateau and Hills Region']\n"]}],"source":["\n","# print(district_mapping_df['IntersectingZones'][165])\n","# district_mapping_df['IntersectingZones']\n","i = 0\n","for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," print(i, district, zones)\n"," i += 1\n"," # for zone in zones:\n"," # if zone not in agroclimaticZone_acronym_dict:\n"," # print(district, zone)"]},{"cell_type":"code","execution_count":21,"metadata":{"executionInfo":{"elapsed":20,"status":"ok","timestamp":1775402421855,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"bE9YcsyVdMGK"},"outputs":[],"source":["agroclimatic_zone_model_path_mapping_rh98 = {'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Plateau_Hills_Region_correct_toa_rh98_24.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/East_Coast_Plains_Hills_Region_correct_toa_rh98_23.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Himalayan_Region_correct_toa_rh98_30.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Himalayan_Region_correct_toa_rh98_25.joblib',\n"," 'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Central_Plateau_Hills_Region_correct_toa_rh98_23.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Southern_Plateau_and_Hills_Region_correct_toa_rh98_23.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Plateau_and_Hills_Region_correct_toa_rh98_24.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Upper_Gangetic_Plain_Region_correct_toa_rh98_29.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Middle_Gangetic_Plain_Region_correct_toa_rh98_24.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Trans_Gangetic_Plain_Region_correct_toa_rh98_21.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Lower_Gangetic_Plain_Region_correct_toa_rh98_17.joblib'}\n","\n","agroclimatic_zone_model_path_mapping_rh75 = {'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Plateau_Hills_Region_correct_toa_rh75_17.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/East_Coast_Plains_Hills_Region_correct_toa_rh75_16.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Himalayan_Region_correct_toa_rh75_20.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Himalayan_Region_correct_toa_rh75_18.joblib',\n"," 'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Central_Plateau_Hills_Region_correct_toa_rh75_16.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Southern_Plateau_and_Hills_Region_correct_toa_rh75_16.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Plateau_and_Hills_Region_correct_toa_rh75_17.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Upper_Gangetic_Plain_Region_correct_toa_rh75_22.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Middle_Gangetic_Plain_Region_correct_toa_rh75_17.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Trans_Gangetic_Plain_Region_correct_toa_rh75_15.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Lower_Gangetic_Plain_Region_correct_toa_rh75_12.joblib'}\n","\n","agroclimatic_zone_model_path_mapping_rh50 = {'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Plateau_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/East_Coast_Plains_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Himalayan_Region_correct_toa_rh50_14.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Eastern_Himalayan_Region_correct_toa_rh50_13.joblib',\n"," 'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Central_Plateau_Hills_Region_correct_toa_rh50_11.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Southern_Plateau_and_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Western_Plateau_and_Hills_Region_correct_toa_rh50_12.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Upper_Gangetic_Plain_Region_correct_toa_rh50_17.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Middle_Gangetic_Plain_Region_correct_toa_rh50_12.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Trans_Gangetic_Plain_Region_correct_toa_rh50_11.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/chm_final/Lower_Gangetic_Plain_Region_correct_toa_rh50_9.joblib'}"]},{"cell_type":"code","execution_count":22,"metadata":{"collapsed":true,"executionInfo":{"elapsed":4349,"status":"ok","timestamp":1775402428454,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"NFPKdyt3dQao","colab":{"base_uri":"https://localhost:8080/"},"outputId":"2e35db01-1b8c-4c2a-a44d-c4b6abaa79ce"},"outputs":[{"output_type":"stream","name":"stderr","text":["/usr/lib/python3.12/pickle.py:1760: UserWarning: [15:20:26] WARNING: /__w/xgboost/xgboost/src/collective/../data/../common/error_msg.h:83: If you are loading a serialized model (like pickle in Python, RDS in R) or\n","configuration generated by an older version of XGBoost, please export the model by calling\n","`Booster.save_model` from that version first, then load it back in current version. See:\n","\n"," https://xgboost.readthedocs.io/en/stable/tutorials/saving_model.html\n","\n","for more details about differences between saving model and serializing.\n","\n"," setstate(state)\n"]}],"source":["MODEL_PATH_rh98 = agroclimatic_zone_model_path_mapping_rh98[agroclimatic_zone]\n","model_rh98 = joblib.load(MODEL_PATH_rh98)\n","\n","MODEL_PATH_rh75 = agroclimatic_zone_model_path_mapping_rh75[agroclimatic_zone]\n","model_rh75 = joblib.load(MODEL_PATH_rh75)\n","\n","MODEL_PATH_rh50 = agroclimatic_zone_model_path_mapping_rh50[agroclimatic_zone]\n","model_rh50 = joblib.load(MODEL_PATH_rh50)"]},{"cell_type":"code","execution_count":23,"metadata":{"executionInfo":{"elapsed":5,"status":"ok","timestamp":1775402428461,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"43wGQ6lvfkRR"},"outputs":[],"source":["if hasattr(model_rh98, 'feature_names_in_'):\n"," features_rh98 = model_rh98.feature_names_in_\n","\n","if hasattr(model_rh75, 'feature_names_in_'):\n"," features_rh75 = model_rh75.feature_names_in_\n","\n","if hasattr(model_rh50, 'feature_names_in_'):\n"," features_rh50 = model_rh50.feature_names_in_\n"]},{"cell_type":"code","execution_count":24,"metadata":{"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775402429003,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"YzC0_-8hfnB4"},"outputs":[],"source":["seasons = ['kharif', 'rabi', 'zaid']"]},{"cell_type":"code","execution_count":25,"metadata":{"executionInfo":{"elapsed":3,"status":"ok","timestamp":1775402430860,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"C2X1usS0fqgK"},"outputs":[],"source":["def add_s1_indices(df):\n"," for season in seasons:\n"," # SAR Indices\n"," df[f'VV_VH_Ratio_{season}'] = df[f'VV_{season}'] / df[f'VH_{season}']\n"," df[f'VH_VV_Ratio_{season}'] = df[f'VH_{season}'] / df[f'VV_{season}']\n"," df[f'SAR_NDVI_{season}'] = (df[f'VH_{season}'] - df[f'VV_{season}']) / (df[f'VH_{season}'] + df[f'VV_{season}'])\n"," df[f'SAR_DVI_{season}'] = df[f'VH_{season}'] - df[f'VV_{season}']\n"," df[f'SAR_SVI_{season}'] = df[f'VH_{season}'] + df[f'VV_{season}']\n"," df[f'SAR_RDVI_{season}'] = (df[f'VH_{season}'] / df[f'VV_{season}']) - (df[f'VV_{season}'] / df[f'VH_{season}'])\n"," df[f'SAR_NRDVI_{season}'] = ((df[f'VH_{season}']/df[f'VV_{season}'] - df[f'VV_{season}']/df[f'VH_{season}']) / (df[f'VH_{season}']/df[f'VV_{season}'] + df[f'VV_{season}']/df[f'VH_{season}']))\n"," df[f'SAR_SSDVI_{season}'] = df[f'VH_{season}']**2 - df[f'VV_{season}']**2\n","\n","def add_s2_indices(df):\n"," for season in seasons:\n"," # Optical Indices\n"," df[f'NDVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'])\n"," df[f'NDWI_{season}'] = (df[f'B8_{season}'] - df[f'B12_{season}']) / (df[f'B8_{season}'] + df[f'B12_{season}'])\n"," df[f'EVI_{season}'] = (2.5 * ((df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + 6*df[f'B4_{season}'] - 7.5*df[f'B2_{season}'] + 1)))\n"," df[f'OSAVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'] + 0.16)\n"," df[f'ARVI_{season}'] = (df[f'B8_{season}'] - 2*df[f'B4_{season}'] + df[f'B2_{season}']) / (df[f'B8_{season}'] + 2*df[f'B4_{season}'] + df[f'B2_{season}'])\n"," df[f'VARI_{season}'] = (df[f'B3_{season}'] - df[f'B4_{season}']) / (df[f'B3_{season}'] + df[f'B4_{season}'] - df[f'B2_{season}'])\n"]},{"cell_type":"code","execution_count":26,"metadata":{"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775402433241,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"iI0adElOftju"},"outputs":[],"source":["# def get_csv_data(fileName):\n","# data = pd.DataFrame()\n","# try:\n","# data = pd.read_csv(fileName)\n","# except Exception as exp:\n","# print(\"Error reading file \", fileName, \" - \", exp)\n","# return data\n","\n","def get_csv_chunks(fileName, chunksize=100_000):\n"," \"\"\"Yield DataFrame chunks from a CSV file.\"\"\"\n"," try:\n"," for chunk in pd.read_csv(fileName, chunksize=chunksize):\n"," yield chunk\n"," except Exception as exp:\n"," print(\"Error reading file \", fileName, \" - \", exp)\n"," return []\n"]},{"cell_type":"code","execution_count":27,"metadata":{"executionInfo":{"elapsed":2,"status":"ok","timestamp":1775402436472,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"BSIdWoUzfw7S"},"outputs":[],"source":["# For Canopy Height\n","def pipeline(fileName, chunksize=100000):\n"," res_chunks = [] # collect processed results\n","\n"," for df in get_csv_chunks(fileName, chunksize):\n"," if len(df) == 0:\n"," continue\n","\n"," # Add indices\n"," add_s1_indices(df)\n"," add_s2_indices(df)\n","\n"," # Geo column\n"," res_df = pd.DataFrame()\n"," res_df['.geo'] = df['.geo']\n","\n"," # Predictions\n"," pred_y_98 = model_rh98.predict(df[features_rh98])\n"," pred_y_75 = model_rh75.predict(df[features_rh75])\n"," pred_y_50 = model_rh50.predict(df[features_rh50])\n","\n"," res_df['rh98_class'] = pred_y_98\n"," res_df['rh75_class'] = pred_y_75\n"," res_df['rh50_class'] = pred_y_50\n","\n"," res_chunks.append(res_df)\n","\n"," if res_chunks:\n"," return pd.concat(res_chunks, ignore_index=True)\n"," else:\n"," return pd.DataFrame(columns=['.geo', 'rh98_class', 'rh75_class', 'rh50_class'])\n","\n","# def pipeline(fileName):\n","# # print(fileName)\n","\n","# df = get_csv_data(fileName)\n","\n","# if (len(df) == 0):\n","# return df\n","\n","# add_s1_indices(df)\n","# add_s2_indices(df)\n","\n","# geoList = list(df['.geo'])\n","# res_df = pd.DataFrame()\n","# res_df['.geo'] = geoList\n","\n","# test_df = df[features_rh98]\n","# pred_y_98 = list(model_rh98.predict(test_df))\n","# test_df = df[features_rh75]\n","# pred_y_75 = list(model_rh75.predict(test_df))\n","# test_df = df[features_rh50]\n","# pred_y_50 = list(model_rh50.predict(test_df))\n","\n","# res_df['rh98_class'] = pred_y_98\n","# res_df['rh75_class'] = pred_y_75\n","# res_df['rh50_class'] = pred_y_50\n","\n","# return res_df"]},{"cell_type":"code","execution_count":30,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"R7GDTVjcf1Vw","outputId":"c0389988-dd39-4d7e-b22a-43b92fc67fae","executionInfo":{"status":"ok","timestamp":1775402686426,"user_tz":-330,"elapsed":53554,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["Anantapur\n","['Southern Plateau and Hills Region']\n","\n","0 District: Anantapur, Zone: Southern Plateau and Hills Region, Year: 2017\n","no. of files: 1 \n","\n"]}],"source":["for year in years:\n"," dist_num = 0\n","\n"," for ind in district_mapping_df.index:\n"," # if dist_num < 14:\n"," # dist_num += 1\n"," # continue\n"," district = district_mapping_df.loc[ind, 'District']\n"," print(district)\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," print(zones)\n"," merged_df = pd.DataFrame()\n"," for zone in zones:\n"," print(f'\\n{dist_num} District: {district}, Zone: {zone}, Year: {year}')\n"," # dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[zone]}_{district}_{year}/'\n"," dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[zone]}_{year}'\n"," files = glob(f\"{dist_data_path}/{district}_{year}_all_grids.csv\")\n"," print(\"no. of files:\", len(files), '\\n')\n"," for fileName in files:\n"," df = pipeline(fileName,chunksize=100000)\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n","\n"," merged_df.to_csv(f'/content/drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm.csv', index=False)\n"," dist_num += 1"]},{"cell_type":"markdown","metadata":{"id":"vkgtoxewcscO"},"source":["# CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"651ANiZ4ctgL"},"outputs":[],"source":["df = pd.read_csv(f'/content/drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = list(df['Name'])\n","# dist_list = ['Yamunanagar'] ## Added extra"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":55,"status":"ok","timestamp":1768374034034,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"UvH9HrjK4w1E","outputId":"576ff54b-1b7a-4122-e215-f128724e1e1b"},"outputs":[{"name":"stdout","output_type":"stream","text":["86\n","['Araria', 'Arwal', 'AurangabadB', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar', 'Darbhanga', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur', 'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger', 'Muzaffarpur', 'Nalanda', 'Nawada', 'Pashchim Champaran', 'Patna', 'Purba Champaran', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur', 'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali', 'BalrampurC', 'Chatra', 'Deoghar', 'Dumka', 'Garhwa', 'Giridih', 'Godda', 'Hazaribagh', 'Kodarma', 'Pakur', 'Palamu', 'Sahibganj', 'Rewa', 'Singrauli', 'Allahabad', 'Ambedkar Nagar', 'Amethi', 'Azamgarh', 'Bahraich', 'Ballia', 'Balrampur', 'Barabanki', 'Basti', 'Chandauli', 'Deoria', 'Faizabad', 'Ghazipur', 'Gonda', 'Gorakhpur', 'Jaunpur', 'Kushinagar', 'Lakhimpur Kheri', 'Maharajganj', 'Mau', 'Mirzapur', 'Pratapgarhup', 'Sant Kabir Nagar', 'Sant Ravi Das Nagar', 'Shravasti', 'Siddharth Nagar', 'Sitapur', 'Sonbhadra', 'Sultanpur', 'Varanasi', 'Darjiling', 'Maldah', 'Murshidabad', 'Uttar Dinajpur']\n"]}],"source":["print(len(dist_list))\n","print(dist_list)"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"bMLuCQdz482a"},"outputs":[],"source":["agroclimatic_zone_model_path_mapping = {'Central Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Central_Plateau_Hills_Region_toa_monthly_cover_51.joblib',\n"," 'Lower Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Lower_Gangetic_Plain_Region_toa_monthly_cover_48.joblib',\n"," 'Middle Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Middle_Gangetic_Plain_Region_toa_monthly_cover_50.joblib',\n"," 'Eastern Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Eastern_Himalayan_Region_toa_monthly_cover_86.joblib',\n"," 'Western Himalayan Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Western_Himalayan_Region_toa_monthly_cover_78.joblib',\n"," 'Upper Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Upper_Gangetic_Plain_Region_toa_monthly_cover_67.joblib',\n"," 'Trans Gangetic Plain Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Trans_Gangetic_Plain_Region_toa_monthly_cover_55.joblib',\n"," 'East Coast Plains & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/East_Coast_Plains_Hills_Region_toa_monthly_cover_67.joblib',\n"," 'Eastern Plateau & Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Eastern_Plateau_Hills_Region_toa_monthly_cover_60.joblib',\n"," 'Western Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Western_Plateau_and_Hills_Region_toa_monthly_cover_57.joblib',\n"," 'Southern Plateau and Hills Region': '/content/drive/MyDrive/TreeHealth/best_models/corrected/Southern_Plateau_and_Hills_Region_toa_monthly_cover_62.joblib'}\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":2483,"status":"ok","timestamp":1768375137014,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"df76932e5Hzw","outputId":"a802cb3b-506c-4404-ce7c-08fc46161e67"},"outputs":[{"name":"stderr","output_type":"stream","text":["/usr/lib/python3.12/pickle.py:1760: UserWarning: [07:18:56] WARNING: /workspace/src/collective/../data/../common/error_msg.h:83: If you are loading a serialized model (like pickle in Python, RDS in R) or\n","configuration generated by an older version of XGBoost, please export the model by calling\n","`Booster.save_model` from that version first, then load it back in current version. See:\n","\n"," https://xgboost.readthedocs.io/en/stable/tutorials/saving_model.html\n","\n","for more details about differences between saving model and serializing.\n","\n"," setstate(state)\n"]}],"source":["MODEL_PATH_cc = agroclimatic_zone_model_path_mapping[agroclimatic_zone]\n","model_cc = joblib.load(MODEL_PATH_cc)"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"tlVIdXqx5Lcr"},"outputs":[],"source":["if hasattr(model_cc, 'feature_names_in_'):\n"," features_cc = model_cc.feature_names_in_"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"r1sgggp16NBx"},"outputs":[],"source":["seasons = ['kharif', 'rabi', 'zaid']\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"_gp52vxf6P5X"},"outputs":[],"source":["def add_s1_indices(df):\n"," for season in seasons:\n"," # SAR Indices\n"," df[f'VV_VH_Ratio_{season}'] = df[f'VV_{season}'] / df[f'VH_{season}']\n"," df[f'VH_VV_Ratio_{season}'] = df[f'VH_{season}'] / df[f'VV_{season}']\n"," df[f'SAR_NDVI_{season}'] = (df[f'VH_{season}'] - df[f'VV_{season}']) / (df[f'VH_{season}'] + df[f'VV_{season}'])\n"," df[f'SAR_DVI_{season}'] = df[f'VH_{season}'] - df[f'VV_{season}']\n"," df[f'SAR_SVI_{season}'] = df[f'VH_{season}'] + df[f'VV_{season}']\n"," df[f'SAR_RDVI_{season}'] = (df[f'VH_{season}'] / df[f'VV_{season}']) - (df[f'VV_{season}'] / df[f'VH_{season}'])\n"," df[f'SAR_NRDVI_{season}'] = ((df[f'VH_{season}']/df[f'VV_{season}'] - df[f'VV_{season}']/df[f'VH_{season}']) / (df[f'VH_{season}']/df[f'VV_{season}'] + df[f'VV_{season}']/df[f'VH_{season}']))\n"," df[f'SAR_SSDVI_{season}'] = df[f'VH_{season}']**2 - df[f'VV_{season}']**2\n","\n","def add_s2_indices(df):\n"," for season in seasons:\n"," # Optical Indices\n"," df[f'NDVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'])\n"," df[f'NDWI_{season}'] = (df[f'B8_{season}'] - df[f'B12_{season}']) / (df[f'B8_{season}'] + df[f'B12_{season}'])\n"," df[f'EVI_{season}'] = (2.5 * ((df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + 6*df[f'B4_{season}'] - 7.5*df[f'B2_{season}'] + 1)))\n"," df[f'OSAVI_{season}'] = (df[f'B8_{season}'] - df[f'B4_{season}']) / (df[f'B8_{season}'] + df[f'B4_{season}'] + 0.16)\n"," df[f'ARVI_{season}'] = (df[f'B8_{season}'] - 2*df[f'B4_{season}'] + df[f'B2_{season}']) / (df[f'B8_{season}'] + 2*df[f'B4_{season}'] + df[f'B2_{season}'])\n"," df[f'VARI_{season}'] = (df[f'B3_{season}'] - df[f'B4_{season}']) / (df[f'B3_{season}'] + df[f'B4_{season}'] - df[f'B2_{season}'])\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"Vg9Yf9nC6Sug"},"outputs":[],"source":["# def get_csv_data(fileName):\n","# data = pd.DataFrame()\n","# try:\n","# data = pd.read_csv(fileName)\n","# except Exception as exp:\n","# print(\"Error reading file \", fileName, \" - \", exp)\n","# return data\n","\n","def get_csv_chunks(fileName, chunksize=100_000):\n"," \"\"\"Yield DataFrame chunks from a CSV file.\"\"\"\n"," try:\n"," for chunk in pd.read_csv(fileName, chunksize=chunksize):\n"," yield chunk\n"," except Exception as exp:\n"," print(\"Error reading file \", fileName, \" - \", exp)\n"," return []"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"FYuZQqc36WKZ"},"outputs":[],"source":["# For Canopy Cover\n","def pipeline(fileName, chunksize=100000):\n"," res_chunks = [] # collect processed results\n","\n"," for df in get_csv_chunks(fileName, chunksize):\n"," if len(df) == 0:\n"," continue\n","\n"," # Add indices\n"," add_s1_indices(df)\n"," add_s2_indices(df)\n","\n"," # Geo column\n"," res_df = pd.DataFrame()\n"," res_df['.geo'] = list(df['.geo'])\n","\n"," for month in range(1,13):\n"," df['month_sin'] = [np.sin(2 * np.pi * month / 12)] * len(df)\n"," df['month_cos'] = [np.cos(2 * np.pi * month / 12)] * len(df)\n","\n"," test_df = df[features_cc]\n"," pred_y_cc = list(model_cc.predict(test_df))\n"," res_df[f'cc_{month}'] = pred_y_cc\n","\n"," res_chunks.append(res_df)\n","\n"," if res_chunks:\n"," return pd.concat(res_chunks, ignore_index=True)\n"," else:\n"," return pd.DataFrame(columns=['.geo'])\n","\n","# def pipeline(fileName):\n","\n","# print(fileName)\n","\n","# df = get_csv_data(fileName)\n","\n","# if (len(df) == 0):\n","# return df\n","\n","# add_s1_indices(df)\n","# add_s2_indices(df)\n","\n","# geoList = list(df['.geo'])\n","# res_df = pd.DataFrame()\n","# res_df['.geo'] = geoList\n","\n","# for month in range(1,13):\n","# df['month_sin'] = [np.sin(2 * np.pi * month / 12)] * len(df)\n","# df['month_cos'] = [np.cos(2 * np.pi * month / 12)] * len(df)\n","\n","# test_df = df[features_cc]\n","# pred_y_cc = list(model_cc.predict(test_df))\n","# res_df[f'cc_{month}'] = pred_y_cc\n","\n","# return res_df\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":22162462,"status":"ok","timestamp":1768405622097,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9bXlL4RT6Yzo","outputId":"e89c99f5-0fb8-4a44-9c4b-651d13b8d8ea"},"outputs":[{"name":"stdout","output_type":"stream","text":["\n"," 0 Araria 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1976372\n","\n"," 1 Arwal 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 192050\n","\n"," 2 AurangabadB 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 931520\n","\n"," 3 Banka 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1794108\n","\n"," 4 Begusarai 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 925926\n","\n"," 5 Bhagalpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1017500\n","\n"," 6 Bhojpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 528965\n","\n"," 7 Buxar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 319698\n","\n"," 8 Darbhanga 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1482059\n","\n"," 9 Gaya 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2347878\n","\n"," 10 Gopalganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 960264\n","\n"," 11 Jamui 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1946203\n","\n"," 12 Jehanabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 328318\n","\n"," 13 Kaimur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2050428\n","\n"," 14 Katihar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1742730\n","\n"," 15 Khagaria 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 763589\n","\n"," 16 Kishanganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1080963\n","\n"," 17 Lakhisarai 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 574227\n","\n"," 18 Madhepura 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1485046\n","\n"," 19 Madhubani 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2275458\n","\n"," 20 Munger 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 788345\n","\n"," 21 Muzaffarpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2058580\n","\n"," 22 Nalanda 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 828451\n","\n"," 23 Nawada 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1456076\n","\n"," 24 Pashchim Champaran 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 5057819\n","\n"," 25 Patna 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 988946\n","\n"," 26 Purba Champaran 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2114183\n","\n"," 27 Purnia 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2585720\n","\n"," 28 Rohtas 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1802430\n","\n"," 29 Saharsa 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1403136\n","\n"," 30 Samastipur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1863560\n","\n"," 31 Saran 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1191174\n","\n"," 32 Sheikhpura 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 164235\n","\n"," 33 Sheohar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 382768\n","\n"," 34 Sitamarhi 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1455242\n","\n"," 35 Siwan 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 942499\n","\n"," 36 Supaul 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1771433\n","\n"," 37 Vaishali 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1229291\n","\n"," 38 BalrampurC 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 28983\n","\n"," 39 Chatra 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 25378\n","\n"," 40 Deoghar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 11084\n","\n"," 41 Dumka 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 7096\n","\n"," 42 Garhwa 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 36756\n","\n"," 43 Giridih 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 7006\n","\n"," 44 Godda 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1900116\n","\n"," 45 Hazaribagh 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 19337\n","\n"," 46 Kodarma 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 41050\n","\n"," 47 Pakur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 30727\n","\n"," 48 Palamu 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 11864\n","\n"," 49 Sahibganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2025865\n","\n"," 50 Rewa 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 18058\n","\n"," 51 Singrauli 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 12480\n","\n"," 52 Allahabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 4270\n","\n"," 53 Ambedkar Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 923541\n","\n"," 54 Amethi 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 13299\n","\n"," 55 Azamgarh 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1182006\n","\n"," 56 Bahraich 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2344420\n","\n"," 57 Ballia 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 670018\n","\n"," 58 Balrampur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2339692\n","\n"," 59 Barabanki 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 423163\n","\n"," 60 Basti 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1061160\n","\n"," 61 Chandauli 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1213119\n","\n"," 62 Deoria 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 682125\n","\n"," 63 Faizabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1241511\n","\n"," 64 Ghazipur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 796377\n","\n"," 65 Gonda 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2413423\n","\n"," 66 Gorakhpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 772243\n","\n"," 67 Jaunpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1447752\n","\n"," 68 Kushinagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 2092368\n","\n"," 69 Lakhimpur Kheri 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 411\n","\n"," 70 Maharajganj 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1562109\n","\n"," 71 Mau 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 601321\n","\n"," 72 Mirzapur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1617620\n","\n"," 73 Pratapgarhup 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 596\n","\n"," 74 Sant Kabir Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 365259\n","\n"," 75 Sant Ravi Das Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 312896\n","\n"," 76 Shravasti 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 1762200\n","\n"," 77 Siddharth Nagar 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 843547\n","\n"," 78 Sitapur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 42\n","\n"," 79 Sonbhadra 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 5833979\n","\n"," 80 Sultanpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 16803\n","\n"," 81 Varanasi 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 552786\n","\n"," 82 Darjiling 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 16059\n","\n"," 83 Maldah 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 19259\n","\n"," 84 Murshidabad 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 4328\n","\n"," 85 Uttar Dinajpur 2021\n","/content/drive/MyDrive/MGPR_2021\n","no. of files: 1 \n","\n","merged_df 47370\n","\n"," 0 Araria 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2190491\n","\n"," 1 Arwal 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 161470\n","\n"," 2 AurangabadB 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 835319\n","\n"," 3 Banka 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1584408\n","\n"," 4 Begusarai 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 824518\n","\n"," 5 Bhagalpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 905853\n","\n"," 6 Bhojpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 488248\n","\n"," 7 Buxar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 305113\n","\n"," 8 Darbhanga 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1331421\n","\n"," 9 Gaya 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2161066\n","\n"," 10 Gopalganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 861413\n","\n"," 11 Jamui 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1876656\n","\n"," 12 Jehanabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 299666\n","\n"," 13 Kaimur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1941287\n","\n"," 14 Katihar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1475151\n","\n"," 15 Khagaria 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 588876\n","\n"," 16 Kishanganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1179706\n","\n"," 17 Lakhisarai 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 539734\n","\n"," 18 Madhepura 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1432748\n","\n"," 19 Madhubani 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2590746\n","\n"," 20 Munger 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 757651\n","\n"," 21 Muzaffarpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1891473\n","\n"," 22 Nalanda 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 758015\n","\n"," 23 Nawada 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1309715\n","\n"," 24 Pashchim Champaran 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 4314974\n","\n"," 25 Patna 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 908794\n","\n"," 26 Purba Champaran 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1949197\n","\n"," 27 Purnia 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2201352\n","\n"," 28 Rohtas 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1735872\n","\n"," 29 Saharsa 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1163729\n","\n"," 30 Samastipur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1492263\n","\n"," 31 Saran 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 952250\n","\n"," 32 Sheikhpura 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 127904\n","\n"," 33 Sheohar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 373246\n","\n"," 34 Sitamarhi 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1418026\n","\n"," 35 Siwan 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 735087\n","\n"," 36 Supaul 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2279975\n","\n"," 37 Vaishali 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1067125\n","\n"," 38 BalrampurC 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 29894\n","\n"," 39 Chatra 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 23811\n","\n"," 40 Deoghar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 10025\n","\n"," 41 Dumka 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 5602\n","\n"," 42 Garhwa 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 35540\n","\n"," 43 Giridih 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 7097\n","\n"," 44 Godda 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1689963\n","\n"," 45 Hazaribagh 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 18602\n","\n"," 46 Kodarma 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 40475\n","\n"," 47 Pakur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 30062\n","\n"," 48 Palamu 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 10438\n","\n"," 49 Sahibganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1858377\n","\n"," 50 Rewa 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 18511\n","\n"," 51 Singrauli 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 12442\n","\n"," 52 Allahabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 4125\n","\n"," 53 Ambedkar Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 878644\n","\n"," 54 Amethi 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 12882\n","\n"," 55 Azamgarh 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1144098\n","\n"," 56 Bahraich 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2281433\n","\n"," 57 Ballia 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 626531\n","\n"," 58 Balrampur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2413530\n","\n"," 59 Barabanki 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 397978\n","\n"," 60 Basti 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1032498\n","\n"," 61 Chandauli 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1129638\n","\n"," 62 Deoria 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 669973\n","\n"," 63 Faizabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1181990\n","\n"," 64 Ghazipur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 731143\n","\n"," 65 Gonda 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 2484101\n","\n"," 66 Gorakhpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 745270\n","\n"," 67 Jaunpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1444477\n","\n"," 68 Kushinagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1983578\n","\n"," 69 Lakhimpur Kheri 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 405\n","\n"," 70 Maharajganj 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1535072\n","\n"," 71 Mau 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 547318\n","\n"," 72 Mirzapur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1555020\n","\n"," 73 Pratapgarhup 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 569\n","\n"," 74 Sant Kabir Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 347484\n","\n"," 75 Sant Ravi Das Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 308932\n","\n"," 76 Shravasti 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 1696175\n","\n"," 77 Siddharth Nagar 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 815397\n","\n"," 78 Sitapur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 49\n","\n"," 79 Sonbhadra 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 5575006\n","\n"," 80 Sultanpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 17301\n","\n"," 81 Varanasi 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 530860\n","\n"," 82 Darjiling 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 15889\n","\n"," 83 Maldah 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 15725\n","\n"," 84 Murshidabad 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 3520\n","\n"," 85 Uttar Dinajpur 2022\n","/content/drive/MyDrive/MGPR_2022\n","no. of files: 1 \n","\n","merged_df 49835\n","\n"," 0 Araria 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1969950\n","\n"," 1 Arwal 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 172959\n","\n"," 2 AurangabadB 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 919333\n","\n"," 3 Banka 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1719181\n","\n"," 4 Begusarai 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 826079\n","\n"," 5 Bhagalpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 947866\n","\n"," 6 Bhojpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 493242\n","\n"," 7 Buxar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 315131\n","\n"," 8 Darbhanga 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1446552\n","\n"," 9 Gaya 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2250105\n","\n"," 10 Gopalganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 884977\n","\n"," 11 Jamui 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2049016\n","\n"," 12 Jehanabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 295487\n","\n"," 13 Kaimur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1891094\n","\n"," 14 Katihar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1527107\n","\n"," 15 Khagaria 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 552961\n","\n"," 16 Kishanganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1069558\n","\n"," 17 Lakhisarai 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 574275\n","\n"," 18 Madhepura 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1326965\n","\n"," 19 Madhubani 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2572389\n","\n"," 20 Munger 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 793397\n","\n"," 21 Muzaffarpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1974039\n","\n"," 22 Nalanda 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 760888\n","\n"," 23 Nawada 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1415526\n","\n"," 24 Pashchim Champaran 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 4607789\n","\n"," 25 Patna 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 878887\n","\n"," 26 Purba Champaran 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1975397\n","\n"," 27 Purnia 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2036293\n","\n"," 28 Rohtas 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1740932\n","\n"," 29 Saharsa 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1172223\n","\n"," 30 Samastipur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1579828\n","\n"," 31 Saran 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 967225\n","\n"," 32 Sheikhpura 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 139694\n","\n"," 33 Sheohar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 350852\n","\n"," 34 Sitamarhi 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1253511\n","\n"," 35 Siwan 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 748663\n","\n"," 36 Supaul 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1911496\n","\n"," 37 Vaishali 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1108101\n","\n"," 38 BalrampurC 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 29650\n","\n"," 39 Chatra 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 25041\n","\n"," 40 Deoghar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 10766\n","\n"," 41 Dumka 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 6322\n","\n"," 42 Garhwa 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 38526\n","\n"," 43 Giridih 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 7574\n","\n"," 44 Godda 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1777243\n","\n"," 45 Hazaribagh 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 18805\n","\n"," 46 Kodarma 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 40709\n","\n"," 47 Pakur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 29874\n","\n"," 48 Palamu 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 12084\n","\n"," 49 Sahibganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1923750\n","\n"," 50 Rewa 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 18630\n","\n"," 51 Singrauli 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 10951\n","\n"," 52 Allahabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 3656\n","\n"," 53 Ambedkar Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 820179\n","\n"," 54 Amethi 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 12372\n","\n"," 55 Azamgarh 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1082775\n","\n"," 56 Bahraich 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2313869\n","\n"," 57 Ballia 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 644920\n","\n"," 58 Balrampur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2341159\n","\n"," 59 Barabanki 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 379511\n","\n"," 60 Basti 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1041620\n","\n"," 61 Chandauli 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1108648\n","\n"," 62 Deoria 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 686270\n","\n"," 63 Faizabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1132587\n","\n"," 64 Ghazipur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 708669\n","\n"," 65 Gonda 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 2413059\n","\n"," 66 Gorakhpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 713471\n","\n"," 67 Jaunpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1373721\n","\n"," 68 Kushinagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1938508\n","\n"," 69 Lakhimpur Kheri 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 417\n","\n"," 70 Maharajganj 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1461027\n","\n"," 71 Mau 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 535649\n","\n"," 72 Mirzapur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1452187\n","\n"," 73 Pratapgarhup 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 560\n","\n"," 74 Sant Kabir Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 333709\n","\n"," 75 Sant Ravi Das Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 300748\n","\n"," 76 Shravasti 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 1667482\n","\n"," 77 Siddharth Nagar 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 780608\n","\n"," 78 Sitapur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 40\n","\n"," 79 Sonbhadra 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 5561742\n","\n"," 80 Sultanpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 15874\n","\n"," 81 Varanasi 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 464081\n","\n"," 82 Darjiling 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 15156\n","\n"," 83 Maldah 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 18893\n","\n"," 84 Murshidabad 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 3990\n","\n"," 85 Uttar Dinajpur 2023\n","/content/drive/MyDrive/MGPR_2023\n","no. of files: 1 \n","\n","merged_df 43326\n"]}],"source":["for year in years:\n"," dist_num = 0\n"," for district in dist_list:\n"," # if dist_num < 53:\n"," # dist_num += 1\n"," # continue\n"," print('\\n', dist_num, district, year)\n"," # dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{district}_{year}/'\n"," dist_data_path = f'/content/drive/MyDrive/{agroclimaticZone_acronym_dict[agroclimatic_zone]}_{year}'\n"," print(dist_data_path)\n"," # files = glob(dist_data_path + \"*.csv\")\n"," files = glob(f\"{dist_data_path}/{district}_{year}_all_grids.csv\")\n"," print(\"no. of files:\", len(files), '\\n')\n"," merged_df = pd.DataFrame()\n"," for fileName in files:\n"," df = pipeline(fileName, chunksize=100000)\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n"," print(\"merged_df\", len(merged_df))\n"," merged_df.to_csv(f'/content/drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc.csv', index=False)\n"," dist_num += 1"]}],"metadata":{"colab":{"collapsed_sections":["vkgtoxewcscO"],"provenance":[{"file_id":"1EZVgV-JFERHxOKGgBNKweDaXnmAGoo83","timestamp":1758129019932}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb b/utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb new file mode 100644 index 00000000..ef71a618 --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/trees_corrections.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":5831,"status":"ok","timestamp":1774795294300,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"IYRi4f4mL668"},"outputs":[],"source":["import pandas as pd\n","import joblib\n","import time\n","from glob import glob\n","import numpy as np\n","import statistics as st\n","import matplotlib.pyplot as plt\n","import ee\n","import ast"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":27519,"status":"ok","timestamp":1774795330023,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qvEjcCWgLuTg","outputId":"4a74a17d-e4cc-4cc5-ebc0-286d81d6035e"},"outputs":[{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":3,"metadata":{"executionInfo":{"elapsed":15,"status":"ok","timestamp":1774795330024,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"fORWmS6aL-gR"},"outputs":[],"source":["acz_list = [\n"," 'Western Himalayan Region',\n"," # 'Eastern Himalayan Region',\n"," # 'Lower Gangetic Plain Region',\n"," # 'Middle Gangetic Plain Region',\n"," # 'Upper Gangetic Plain Region',\n"," # 'Trans Gangetic Plain Region',\n"," # 'Eastern Plateau & Hills Region',\n"," # 'Central Plateau & Hills Region',\n"," # 'Western Plateau and Hills Region',\n"," # 'Southern Plateau and Hills Region',\n"," # 'East Coast Plains & Hills Region'\n"," ]"]},{"cell_type":"code","execution_count":4,"metadata":{"executionInfo":{"elapsed":6,"status":"ok","timestamp":1774795342043,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"6Fiwos-UMDhQ"},"outputs":[],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}"]},{"cell_type":"code","execution_count":18,"metadata":{"executionInfo":{"elapsed":47,"status":"ok","timestamp":1774807519586,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"5T_YvOd-MK1m"},"outputs":[],"source":["# if AY 2023's data is being added, set year = '2023'\n","# No correction for 2016, starts from 2017\n","# (For correcting 2017, set to year to 2019)\n","year = '2024'\n","\n","# We need to correct data from year_2 and year_1\n","year_1 = int(year)-1\n","year_2 = int(year)-2\n","year_3 = int(year)-3\n","year_4 = int(year)-4"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":1284,"status":"ok","timestamp":1774715945110,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"3qECVxwWVRAP","outputId":"20eaa902-dbb3-4630-fc49-85b41ca7d54b"},"outputs":[{"output_type":"stream","name":"stdout","text":["2\n","dist_list: ['Katihar', 'Kishanganj']\n"]}],"source":["# df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{acz_list[0]}.csv')\n","# dist_list = list(df['Name'])\n","# dist_list = ['Katihar', 'Kishanganj']\n","# print(len(dist_list))\n","# print(f'dist_list: {dist_list}')"]},{"cell_type":"markdown","metadata":{"id":"OpBWXM1TMVAs"},"source":["# Data Correction - CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"pNuZ_qZtMWEj"},"outputs":[],"source":["def ccd_corrections_2017(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # In case the no. of columns are too less for any corrections to be performed\n"," if n < 5:\n"," print(f\"Number of columns are {n}, which is too less to perform any corrections..\")\n"," correction_df = pd.DataFrame(columns=columns)\n"," return correction_df\n","\n"," # Correcting the second (2017) year\n"," correction_df = df[(df[columns[n-1]] == df[columns[n-2]]) & (df[columns[n-2]] == df[columns[n-4]]) & (df[columns[n-3]] !=df[columns[n-1]])]\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n"," correction_df.loc[(correction_df[columns[n-1]] == correction_df[columns[n-2]]) &\n"," (correction_df[columns[n-2]] == correction_df[columns[n-4]]) &\n"," (correction_df[columns[n-3]] != correction_df[columns[n-1]]), columns[n-3]] = correction_df[columns[n-1]]\n","\n"," return correction_df\n","\n","\n","def corrections(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # In case the no. of columns are too less for any corrections to be performed\n"," if n < 5:\n"," correction_df = pd.DataFrame(columns=columns)\n"," return correction_df\n","\n"," # Correcting the second last year\n"," correction_df = df[(df[columns[n-1]] == df[columns[n-3]]) & (df[columns[n-3]] == df[columns[n-4]]) & (df[columns[n-2]] != df[columns[n-1]])]\n"," correction_df.drop_duplicates(inplace=True)\n"," print(\"correction_df 1\", correction_df)\n"," # Correcting the middle year\n"," new_df = df[(df[columns[n-5]] == df[columns[n-4]]) & (df[columns[n-4]] == df[columns[n-2]]) & (df[columns[n-2]] == df[columns[n-1]]) & (df[columns[n-3]] != df[columns[n-5]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n"," print(\"correction_df 2\", correction_df)\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n"," correction_df.loc[(correction_df[columns[n-1]] == correction_df[columns[n-3]]) & (correction_df[columns[n-3]] == correction_df[columns[n-4]]) &\n"," (correction_df[columns[n-2]] != correction_df[columns[n-1]]), columns[n-2]] = correction_df[columns[n-1]]\n"," correction_df.loc[(correction_df[columns[n-5]] == correction_df[columns[n-4]]) & (correction_df[columns[n-4]] == correction_df[columns[n-2]]) &\n"," (correction_df[columns[n-2]] == correction_df[columns[n-1]]) & (correction_df[columns[n-3]] != correction_df[columns[n-5]]), columns[n-3]] = correction_df[columns[n-5]]\n","\n"," return correction_df"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":517},"executionInfo":{"elapsed":20422,"status":"error","timestamp":1771511098161,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"kZsJ0sCEMcQo","outputId":"e3b8da73-1c52-49c8-ea41-db204f58acc8"},"outputs":[{"name":"stdout","output_type":"stream","text":["East Coast Plains & Hills Region\n","len(dist_list): 68\n","0 Chittoor\n"]},{"ename":"KeyboardInterrupt","evalue":"","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-50941476.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 36\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 37\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 38\u001b[0;31m \u001b[0mdf_3\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 39\u001b[0m \u001b[0mdf_3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdrop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcolumnList\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minplace\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0mdf_3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrename\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0mband\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'cover_class'\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minplace\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36mread_csv\u001b[0;34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)\u001b[0m\n\u001b[1;32m 1024\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwds_defaults\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1025\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1026\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_read\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1027\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1028\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36m_read\u001b[0;34m(filepath_or_buffer, kwds)\u001b[0m\n\u001b[1;32m 624\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 625\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mparser\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 626\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mparser\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnrows\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 627\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 628\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/readers.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self, nrows)\u001b[0m\n\u001b[1;32m 1921\u001b[0m \u001b[0mcolumns\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1922\u001b[0m \u001b[0mcol_dict\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1923\u001b[0;31m \u001b[0;34m)\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m \u001b[0;31m# type: ignore[attr-defined]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1924\u001b[0m \u001b[0mnrows\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1925\u001b[0m )\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/parsers/c_parser_wrapper.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self, nrows)\u001b[0m\n\u001b[1;32m 232\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 233\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlow_memory\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 234\u001b[0;31m \u001b[0mchunks\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_reader\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_low_memory\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnrows\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 235\u001b[0m \u001b[0;31m# destructive to chunks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 236\u001b[0m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_concatenate_chunks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchunks\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader.read_low_memory\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._read_rows\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._tokenize_rows\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.TextReader._check_tokenize_status\u001b[0;34m()\u001b[0m\n","\u001b[0;32mparsers.pyx\u001b[0m in \u001b[0;36mpandas._libs.parsers.raise_parser_error\u001b[0;34m()\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/codecs.py\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(self, input, final)\u001b[0m\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "]}],"source":["for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n"," dist_list = list(df['Name'])\n"," print(f'len(dist_list): {len(dist_list)}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/'\n","\n"," total_corrections = 0\n"," total_length = 0\n","\n"," for i in range(len(dist_list)):\n","\n"," # if i != 11:\n"," # continue\n","\n"," print(i, dist_list[i])\n"," file_4 = path + dist_list[i] + f\"/{year_4}/result_monthly_cc.csv\"\n"," file_3 = path + dist_list[i] + f\"/{year_3}/result_monthly_cc.csv\"\n"," file_2 = path + dist_list[i] + f\"/{year_2}/result_monthly_cc.csv\"\n"," file_1 = path + dist_list[i] + f\"/{year_1}/result_monthly_cc.csv\"\n"," file_0 = path + dist_list[i] + f\"/{year}/result_monthly_cc.csv\"\n","\n"," band = best_month_dict[agroclimatic_zone]\n"," columnList = ['cc_1', 'cc_2', 'cc_3', 'cc_4', 'cc_5', 'cc_6', 'cc_7', 'cc_8', 'cc_9', 'cc_10', 'cc_11', 'cc_12']\n"," columnList.remove(band)\n","\n"," try:\n"," df_4 = pd.read_csv(file_4)\n"," df_4.drop(columns=columnList, inplace=True)\n"," df_4.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_4 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," try:\n"," df_3 = pd.read_csv(file_3)\n"," df_3.drop(columns=columnList, inplace=True)\n"," df_3.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_3 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_4.rename(columns={'cover_class': f'cc_{year_4}'}, inplace=True)\n"," df_3.rename(columns={'cover_class': f'cc_{year_3}'}, inplace=True)\n"," merged_df = pd.merge(df_4, df_3, on='.geo', how='outer')\n"," del(df_4)\n"," del(df_3)\n","\n"," try:\n"," df_2 = pd.read_csv(file_2)\n"," df_2.drop(columns=columnList, inplace=True)\n"," df_2.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_2 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_2.rename(columns={'cover_class': f'cc_{year_2}'}, inplace=True)\n"," merged_df = pd.merge(merged_df, df_2, on='.geo', how='outer')\n"," del(df_2)\n","\n"," try:\n"," df_1 = pd.read_csv(file_1)\n"," df_1.drop(columns=columnList, inplace=True)\n"," df_1.rename(columns={band: 'cover_class'}, inplace=True)\n"," except Exception as e:\n"," print(e)\n"," df_1 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_1.rename(columns={'cover_class': f'cc_{year_1}'}, inplace=True)\n"," merged_df = pd.merge(merged_df, df_1, on='.geo', how='outer')\n"," del(df_1)\n","\n"," try:\n"," df_0 = pd.read_csv(file_0)\n"," df_0.drop(columns=columnList, inplace=True)\n"," df_0.rename(columns={band: 'cover_class'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_0 = pd.DataFrame(columns=['.geo', 'cover_class'])\n","\n"," df_0.rename(columns={'cover_class': f'cc_{year}'}, inplace=True)\n"," merged_df = pd.merge(merged_df, df_0, on='.geo', how='outer')\n"," del(df_0)\n","\n","\n"," merged_df = merged_df[['.geo', f'cc_{year_4}', f'cc_{year_3}', f'cc_{year_2}', f'cc_{year_1}', f'cc_{year}']]\n"," print(merged_df)\n"," total_length += len(merged_df)\n"," print(\"Length of merged_df:\", len(merged_df))\n"," print(\"Total Length:\", total_length)\n"," if year == '2019':\n"," print(\"Correcting 2017\")\n"," correction_df = ccd_corrections_2017(merged_df)\n"," else:\n"," correction_df = corrections(merged_df)\n","\n"," del(merged_df)\n"," total_corrections += len(correction_df)\n"," print(f'Correction_df length: {len(correction_df)}')\n"," print(f'Total Corrections: {total_corrections}')\n"," if len(correction_df) > 0:\n"," # if year != last_year:\n"," correction_year_2 = correction_df[['.geo', f'cc_{year_2}']]\n"," correction_year_2.to_csv(f'{path}{dist_list[i]}/{year_2}/result_monthly_cc_corrections.csv', index=False)\n"," if year != '2019':\n"," correction_year_1 = correction_df[['.geo', f'cc_{year_1}']]\n"," correction_year_1.to_csv(f'{path}{dist_list[i]}/{year_1}/result_monthly_cc_corrections.csv', index=False) # Comment when correcting 2017\n","\n"," del(correction_df)"]},{"cell_type":"markdown","metadata":{"id":"Tw7xfx5BO1eH"},"source":[" # Data Correction - CH"]},{"cell_type":"code","execution_count":6,"metadata":{"executionInfo":{"elapsed":20,"status":"ok","timestamp":1774795365410,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qqU0JIe4O40i"},"outputs":[],"source":["# Column Order\n","# ['.geo',\n","# 'rh98_{year_4}', 'rh98_{year_3}', 'rh98_{year_2}', 'rh98_{year_1}', 'rh98_{year}',\n","# 'rh75_{year_4}', 'rh75_{year_3}', 'rh75_{year_2}', 'rh75_{year_1}', 'rh75_{year}',\n","# 'rh50_{year_4}', 'rh50_{year_3}', 'rh50_{year_2}', 'rh50_{year_1}', 'rh50_{year}']\n","\n","\n","def ch_corrections(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # Correcting the second last year - rh98\n"," correction_df = df[(df[columns[5]] == df[columns[3]]) & (df[columns[3]] == df[columns[2]]) & (df[columns[4]] != df[columns[5]])]\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the middle year - rh98\n"," new_df = df[(df[columns[1]] == df[columns[2]]) & (df[columns[2]] == df[columns[4]]) & (df[columns[4]] == df[columns[5]]) & (df[columns[3]] != df[columns[1]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n","\n","\n"," # Correcting the second last year - rh75\n"," new_df = df[(df[columns[10]] == df[columns[8]]) & (df[columns[8]] == df[columns[7]]) & (df[columns[9]] != df[columns[10]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the middle year - rh75\n"," new_df = df[(df[columns[6]] == df[columns[7]]) & (df[columns[7]] == df[columns[9]]) & (df[columns[9]] == df[columns[10]]) & (df[columns[8]] != df[columns[6]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n","\n","\n"," # Correcting the second last year - rh50\n"," new_df = df[(df[columns[15]] == df[columns[13]]) & (df[columns[13]] == df[columns[12]]) & (df[columns[14]] != df[columns[15]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the middle year - rh50\n"," new_df = df[(df[columns[11]] == df[columns[12]]) & (df[columns[12]] == df[columns[14]]) & (df[columns[14]] == df[columns[15]]) & (df[columns[13]] != df[columns[11]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," correction_df.drop_duplicates(inplace=True)\n"," del(new_df)\n","\n","\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n","\n"," # rh98\n"," correction_df.loc[(correction_df[columns[5]] == correction_df[columns[3]]) & (correction_df[columns[3]] == correction_df[columns[2]]) &\n"," (correction_df[columns[4]] != correction_df[columns[5]]), columns[4]] = correction_df[columns[5]]\n"," correction_df.loc[(correction_df[columns[1]] == correction_df[columns[2]]) & (correction_df[columns[2]] == correction_df[columns[4]]) &\n"," (correction_df[columns[4]] == correction_df[columns[5]]) & (correction_df[columns[3]] != correction_df[columns[1]]), columns[3]] = correction_df[columns[1]]\n","\n","\n"," # rh75\n"," correction_df.loc[(correction_df[columns[10]] == correction_df[columns[8]]) & (correction_df[columns[8]] == correction_df[columns[7]]) &\n"," (correction_df[columns[9]] != correction_df[columns[10]]), columns[9]] = correction_df[columns[10]]\n"," correction_df.loc[(correction_df[columns[6]] == correction_df[columns[7]]) & (correction_df[columns[7]] == correction_df[columns[9]]) &\n"," (correction_df[columns[9]] == correction_df[columns[10]]) & (correction_df[columns[8]] != correction_df[columns[6]]), columns[8]] = correction_df[columns[6]]\n","\n","\n"," # rh50\n"," correction_df.loc[(correction_df[columns[15]] == correction_df[columns[13]]) & (correction_df[columns[13]] == correction_df[columns[12]]) &\n"," (correction_df[columns[14]] != correction_df[columns[15]]), columns[14]] = correction_df[columns[15]]\n"," correction_df.loc[(correction_df[columns[11]] == correction_df[columns[12]]) & (correction_df[columns[12]] == correction_df[columns[14]]) &\n"," (correction_df[columns[14]] == correction_df[columns[15]]) & (correction_df[columns[13]] != correction_df[columns[11]]), columns[13]] = correction_df[columns[11]]\n","\n"," return correction_df\n","\n","def ch_corrections_2017(df):\n"," columns = list(df.columns)\n"," n = len(columns)\n","\n"," # Correcting the second last year - rh98\n"," correction_df = df[(df[columns[5]] == df[columns[4]]) & (df[columns[4]] == df[columns[2]]) & (df[columns[3]] != df[columns[5]])]\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the second last year - rh75\n"," new_df = df[(df[columns[10]] == df[columns[9]]) & (df[columns[9]] == df[columns[7]]) & (df[columns[8]] != df[columns[10]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Correcting the second last year - rh50\n"," new_df = df[(df[columns[15]] == df[columns[14]]) & (df[columns[14]] == df[columns[12]]) & (df[columns[13]] != df[columns[15]])]\n"," correction_df = pd.concat([correction_df, new_df], ignore_index=True)\n"," del(new_df)\n"," correction_df.drop_duplicates(inplace=True)\n","\n"," # Actually Performing the corrections once all rows where corrections need to be performed are found\n","\n"," # rh98\n"," correction_df.loc[(correction_df[columns[5]] == correction_df[columns[4]]) & (correction_df[columns[4]] == correction_df[columns[2]]) &\n"," (correction_df[columns[3]] != correction_df[columns[5]]), columns[3]] = correction_df[columns[5]]\n","\n"," # rh75\n"," correction_df.loc[(correction_df[columns[10]] == correction_df[columns[9]]) & (correction_df[columns[9]] == correction_df[columns[7]]) &\n"," (correction_df[columns[8]] != correction_df[columns[10]]), columns[8]] = correction_df[columns[10]]\n","\n"," # rh50\n"," correction_df.loc[(correction_df[columns[15]] == correction_df[columns[14]]) & (correction_df[columns[14]] == correction_df[columns[12]]) &\n"," (correction_df[columns[13]] != correction_df[columns[15]]), columns[13]] = correction_df[columns[15]]\n","\n"," return correction_df"]},{"cell_type":"markdown","metadata":{"id":"2aOG9olEVlV-"},"source":["CH corrections without Chunking"]},{"cell_type":"code","execution_count":19,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"exmkYL5zPGs-","outputId":"3c535796-d3f1-468c-de0e-a1ba907ce127","executionInfo":{"status":"ok","timestamp":1774808836553,"user_tz":-330,"elapsed":1310782,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["Western Himalayan Region\n","len(dist_list): 4\n","dist_list=: ['Dehradun', 'Garhwal', 'Nainital', 'Champawat']\n","0 Dehradun\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","... ... ... \n","5126057 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126058 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126059 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126060 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","5126061 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 1.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","5126057 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126058 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126059 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126060 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126061 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 1.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","5126057 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126058 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126059 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126060 0.0 0.0 0.0 0.0 0.0 0.0 \n","5126061 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2024 rh50_2024 \n","0 0.0 0.0 \n","1 0.0 0.0 \n","2 0.0 0.0 \n","3 0.0 0.0 \n","4 0.0 0.0 \n","... ... ... \n","5126057 0.0 0.0 \n","5126058 1.0 0.0 \n","5126059 0.0 1.0 \n","5126060 0.0 1.0 \n","5126061 0.0 0.0 \n","\n","[5126062 rows x 16 columns]\n","Length of merged_df: 5126062\n","Total Length: 5126062\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 1278553\n","Total Corrections: 1278553\n","1 Garhwal\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","... ... ... \n","9596162 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596163 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596164 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596165 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","9596166 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","9596162 0.0 0.0 0.0 0.0 0.0 NaN \n","9596163 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596164 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596165 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596166 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","9596162 NaN NaN NaN NaN NaN 0.0 \n","9596163 0.0 0.0 NaN NaN NaN 0.0 \n","9596164 0.0 0.0 0.0 0.0 0.0 0.0 \n","9596165 0.0 0.0 NaN NaN NaN 0.0 \n","9596166 0.0 0.0 0.0 0.0 0.0 0.0 \n","\n"," rh75_2024 rh50_2024 \n","0 0.0 0.0 \n","1 0.0 0.0 \n","2 0.0 0.0 \n","3 0.0 0.0 \n","4 0.0 0.0 \n","... ... ... \n","9596162 0.0 0.0 \n","9596163 0.0 0.0 \n","9596164 0.0 0.0 \n","9596165 0.0 0.0 \n","9596166 0.0 0.0 \n","\n","[9596167 rows x 16 columns]\n","Length of merged_df: 9596167\n","Total Length: 14722229\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 1725432\n","Total Corrections: 3003985\n","2 Nainital\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","... ... ... \n","6912865 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912866 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912867 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912868 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","6912869 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 1.0 1.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","6912865 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912866 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912867 1.0 1.0 1.0 1.0 1.0 1.0 \n","6912868 1.0 1.0 1.0 1.0 1.0 1.0 \n","6912869 1.0 1.0 0.0 1.0 1.0 0.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 0.0 0.0 0.0 0.0 0.0 0.0 \n","1 0.0 0.0 0.0 0.0 0.0 0.0 \n","2 0.0 0.0 0.0 0.0 0.0 0.0 \n","3 0.0 0.0 0.0 0.0 0.0 0.0 \n","4 0.0 0.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","6912865 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912866 1.0 0.0 1.0 1.0 1.0 0.0 \n","6912867 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912868 1.0 1.0 1.0 1.0 1.0 0.0 \n","6912869 1.0 1.0 1.0 1.0 1.0 0.0 \n","\n"," rh75_2024 rh50_2024 \n","0 0.0 0.0 \n","1 0.0 0.0 \n","2 0.0 0.0 \n","3 0.0 0.0 \n","4 0.0 0.0 \n","... ... ... \n","6912865 0.0 1.0 \n","6912866 0.0 1.0 \n","6912867 0.0 1.0 \n","6912868 0.0 1.0 \n","6912869 0.0 1.0 \n","\n","[6912870 rows x 16 columns]\n","Length of merged_df: 6912870\n","Total Length: 21635099\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 1325096\n","Total Corrections: 4329081\n","3 Champawat\n","merged_df>>> .geo rh98_2020 \\\n","0 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","1 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","2 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","3 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","4 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","... ... ... \n","2951210 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951211 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951212 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951213 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 0.0 \n","2951214 {\"geodesic\":false,\"type\":\"Point\",\"coordinates\"... 1.0 \n","\n"," rh75_2020 rh50_2020 rh98_2021 rh75_2021 rh50_2021 rh98_2022 \\\n","0 1.0 1.0 0.0 0.0 1.0 0.0 \n","1 1.0 1.0 0.0 0.0 0.0 0.0 \n","2 1.0 1.0 1.0 0.0 0.0 0.0 \n","3 0.0 1.0 0.0 1.0 1.0 0.0 \n","4 1.0 1.0 0.0 0.0 0.0 0.0 \n","... ... ... ... ... ... ... \n","2951210 1.0 0.0 1.0 1.0 1.0 1.0 \n","2951211 0.0 0.0 1.0 1.0 1.0 1.0 \n","2951212 0.0 0.0 1.0 1.0 1.0 1.0 \n","2951213 0.0 0.0 1.0 1.0 1.0 1.0 \n","2951214 0.0 0.0 1.0 1.0 1.0 1.0 \n","\n"," rh75_2022 rh50_2022 rh98_2023 rh75_2023 rh50_2023 rh98_2024 \\\n","0 1.0 0.0 1.0 1.0 1.0 0.0 \n","1 1.0 1.0 1.0 1.0 1.0 1.0 \n","2 1.0 1.0 1.0 1.0 1.0 1.0 \n","3 1.0 1.0 1.0 1.0 1.0 0.0 \n","4 0.0 1.0 1.0 1.0 1.0 0.0 \n","... ... ... ... ... ... ... \n","2951210 1.0 1.0 1.0 1.0 1.0 0.0 \n","2951211 1.0 1.0 1.0 1.0 1.0 1.0 \n","2951212 1.0 1.0 1.0 1.0 1.0 1.0 \n","2951213 1.0 1.0 1.0 1.0 1.0 1.0 \n","2951214 1.0 1.0 1.0 1.0 1.0 1.0 \n","\n"," rh75_2024 rh50_2024 \n","0 1.0 1.0 \n","1 1.0 1.0 \n","2 0.0 0.0 \n","3 1.0 1.0 \n","4 0.0 1.0 \n","... ... ... \n","2951210 0.0 0.0 \n","2951211 0.0 0.0 \n","2951212 1.0 0.0 \n","2951213 1.0 1.0 \n","2951214 1.0 1.0 \n","\n","[2951215 rows x 16 columns]\n","Length of merged_df: 2951215\n","Total Length: 24586314\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_17578/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length: 762567\n","Total Corrections: 5091648\n"]}],"source":["# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n","\n"," df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n"," df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)\n"," district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n"," dist_list = []\n"," for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," dist_list.append(district)\n","\n"," dist_list = ['Dehradun', 'Garhwal', 'Nainital', 'Champawat']\n"," print(f'len(dist_list): {len(dist_list)}')\n"," print(f'dist_list=: {dist_list}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/'\n","\n"," total_corrections = 0\n"," total_length = 0\n","\n"," for i in range(len(dist_list)):\n","\n"," if i == 4 or i==6:\n"," continue\n","\n"," print(i, dist_list[i])\n"," file_4 = path + dist_list[i] + f\"/{year_4}/result_chm.csv\"\n"," file_3 = path + dist_list[i] + f\"/{year_3}/result_chm.csv\"\n"," file_2 = path + dist_list[i] + f\"/{year_2}/result_chm.csv\"\n"," file_1 = path + dist_list[i] + f\"/{year_1}/result_chm.csv\"\n"," file_0 = path + dist_list[i] + f\"/{year}/result_chm.csv\"\n","\n"," try:\n"," df_4 = pd.read_csv(file_4)\n"," df_4.rename(columns={'rh98_class': f'rh98_{year_4}', 'rh75_class': f'rh75_{year_4}', 'rh50_class': f'rh50_{year_4}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_4 = pd.DataFrame(columns=['.geo', f'rh98_{year_4}', f'rh75_{year_4}', f'rh50_{year_4}', f'ch_{year_4}'])\n","\n"," try:\n"," df_3 = pd.read_csv(file_3)\n"," df_3.rename(columns={'rh98_class': f'rh98_{year_3}', 'rh75_class': f'rh75_{year_3}', 'rh50_class': f'rh50_{year_3}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_3 = pd.DataFrame(columns=['.geo', f'rh98_{year_3}', f'rh75_{year_3}', f'rh50_{year_3}', f'ch_{year_3}'])\n","\n"," merged_df = pd.merge(df_4, df_3, on='.geo', how='outer')\n"," del(df_4)\n"," del(df_3)\n","\n"," try:\n"," df_2 = pd.read_csv(file_2)\n"," df_2.rename(columns={'rh98_class': f'rh98_{year_2}', 'rh75_class': f'rh75_{year_2}', 'rh50_class': f'rh50_{year_2}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_2 = pd.DataFrame(columns=['.geo', f'rh98_{year_2}', f'rh75_{year_2}', f'rh50_{year_2}', f'ch_{year_2}'])\n","\n"," merged_df = pd.merge(merged_df, df_2, on='.geo', how='outer')\n"," del(df_2)\n","\n"," try:\n"," df_1 = pd.read_csv(file_1)\n"," df_1.rename(columns={'rh98_class': f'rh98_{year_1}', 'rh75_class': f'rh75_{year_1}', 'rh50_class': f'rh50_{year_1}'}, inplace=True)\n"," except Exception as e:\n"," print(e)\n"," df_1 = pd.DataFrame(columns=['.geo', f'rh98_{year_1}', f'rh75_{year_1}', f'rh50_{year_1}', f'ch_{year_1}'])\n","\n"," merged_df = pd.merge(merged_df, df_1, on='.geo', how='outer')\n"," del(df_1)\n","\n"," try:\n"," df_0 = pd.read_csv(file_0)\n"," df_0.rename(columns={'rh98_class': f'rh98_{year}', 'rh75_class': f'rh75_{year}', 'rh50_class': f'rh50_{year}'}, inplace=True)\n","\n"," except Exception as e:\n"," print(e)\n"," df_0 = pd.DataFrame(columns=['.geo', f'rh98_{year}', f'rh75_{year}', f'rh50_{year}', f'ch_{year}'])\n","\n"," merged_df = pd.merge(merged_df, df_0, on='.geo', how='outer')\n"," del(df_0)\n"," print(\"merged_df>>>\",merged_df)\n","\n"," try:\n"," merged_df = merged_df[['.geo', f'rh98_{year_4}', f'rh98_{year_3}', f'rh98_{year_2}', f'rh98_{year_1}', f'rh98_{year}',\n"," f'rh75_{year_4}', f'rh75_{year_3}', f'rh75_{year_2}', f'rh75_{year_1}', f'rh75_{year}', f'rh50_{year_4}',\n"," f'rh50_{year_3}', f'rh50_{year_2}', f'rh50_{year_1}', f'rh50_{year}']]\n"," except Exception as e:\n"," print(e)\n","\n"," total_length += len(merged_df)\n"," print(\"Length of merged_df:\", len(merged_df))\n"," print(\"Total Length:\", total_length)\n"," if year == '2019':\n"," correction_df = ch_corrections_2017(merged_df) # Uncomment when correcting 2017\n"," else:\n"," correction_df = ch_corrections(merged_df) # Comment when correcting 2017\n","\n"," del(merged_df)\n","\n"," total_corrections += len(correction_df)\n"," print(f'Correction_df length: {len(correction_df)}')\n"," print(f'Total Corrections: {total_corrections}')\n"," if len(correction_df) > 0:\n","\n"," choices = [0, 0, 1, 2, 1, 2]\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_4}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_3}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_2}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_1}'] = np.select(conditions, choices, default=3)\n","\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1)\n"," ]\n"," correction_df[f'ch_{year}'] = np.select(conditions, choices, default=3)\n","\n"," cols = correction_df.columns\n"," for j in range(1, len(cols)):\n"," correction_df[cols[j]] = correction_df[cols[j]].astype('Int64')\n"," # if year != last_year: # TODO remove this\n"," correction_year_2 = correction_df[['.geo', f'rh50_{year_2}', f'rh75_{year_2}', f'rh98_{year_2}', f'ch_{year_2}']]\n"," correction_year_2.to_csv(f'{path}{dist_list[i]}/{year_2}/result_chm_corrections.csv', index=False)\n"," if year != '2019':\n"," correction_year_1 = correction_df[['.geo', f'rh50_{year_1}', f'rh75_{year_1}', f'rh98_{year_1}', f'ch_{year_1}']] # Comment when correcting 2017\n"," correction_year_1.to_csv(f'{path}{dist_list[i]}/{year_1}/result_chm_corrections.csv', index=False) # Comment when correcting 2017\n","\n"," del(correction_df)\n"]},{"cell_type":"markdown","metadata":{"id":"ZwMptI_mVQ4a"},"source":["CH - correction Chunking (Run this only for those locations which are causing RAM issue)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"eNlS8jKW3tom","executionInfo":{"status":"ok","timestamp":1774375877955,"user_tz":-330,"elapsed":6162811,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"ca3318b0-6fe1-42e9-c59f-03e707e8339c"},"outputs":[{"output_type":"stream","name":"stdout","text":["2024\n","Eastern Plateau & Hills Region\n","len(dist_list): 1\n","dist_list: ['Garhchiroli']\n","0 District Garhchiroli\n","File 4\n","File 3\n","File 2\n","File 1\n","File 0\n","Total unique .geo keys (union): 20257293\n","Processing 6 merge-chunks of size up to 4000000...\n","Processing SQL chunk 1/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 4000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 251390\n","Total Corrections so far: 251390\n","Processing SQL chunk 2/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 8000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 365821\n","Total Corrections so far: 617211\n","Processing SQL chunk 3/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 12000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 530155\n","Total Corrections so far: 1147366\n","Processing SQL chunk 4/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 16000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 626236\n","Total Corrections so far: 1773602\n","Processing SQL chunk 5/6 with 4000000 rows\n","Length of merged_df (this chunk): 4000000\n","Total Length so far: 20000000\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 824080\n","Total Corrections so far: 2597682\n","Processing SQL chunk 6/6 with 257293 rows\n","Length of merged_df (this chunk): 257293\n","Total Length so far: 20257293\n"]},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6773/833223507.py:14: SettingWithCopyWarning: \n","A value is trying to be set on a copy of a slice from a DataFrame\n","\n","See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n"," correction_df.drop_duplicates(inplace=True)\n"]},{"output_type":"stream","name":"stdout","text":["Correction_df length (chunk): 54985\n","Total Corrections so far: 2652667\n"]}],"source":["# Add imports at top of your file (if not already imported)\n","import sqlite3\n","import os\n","import math\n","from pathlib import Path\n","\n","CHUNKSIZE = 4000_000\n","print(year)\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n","\n"," df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n"," df['IntersectingZones'] = df['IntersectingZones'].apply(ast.literal_eval)\n"," district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n","\n"," dist_list = district_mapping_df['District'].tolist()\n"," dist_list = ['Garhchiroli']\n"," print(f'len(dist_list): {len(dist_list)}')\n"," print(f'dist_list: {dist_list}')\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/'\n"," total_corrections = 0\n"," total_length = 0\n","\n","\n"," for i in range(len(dist_list)):\n"," print(i, \"District\",dist_list[i])\n"," file_4 = f\"{path}{dist_list[i]}/{year_4}/result_chm.csv\"\n"," file_3 = f\"{path}{dist_list[i]}/{year_3}/result_chm.csv\"\n"," file_2 = f\"{path}{dist_list[i]}/{year_2}/result_chm.csv\"\n"," file_1 = f\"{path}{dist_list[i]}/{year_1}/result_chm.csv\"\n"," file_0 = f\"{path}{dist_list[i]}/{year}/result_chm.csv\"\n","\n"," # --- Begin drop-in chunked-on-disk merge logic ---\n"," # Create temp sqlite DB per district to avoid collisions and keep things local to each district\n"," db_path = f'{path}{dist_list[i]}/temp_merge.db'\n"," # ensure directory exists\n"," Path(os.path.dirname(db_path)).mkdir(parents=True, exist_ok=True)\n"," # remove previous DB if exists (so headers won't collide when appending)\n"," if os.path.exists(db_path):\n"," try:\n"," os.remove(db_path)\n"," except Exception:\n"," pass\n","\n"," conn = sqlite3.connect(db_path)\n"," # Use pragmas to speed inserts (safe for temp file)\n"," conn.execute('PRAGMA synchronous=OFF;')\n"," conn.execute('PRAGMA journal_mode=MEMORY;')\n"," conn.commit()\n","\n"," # helper to stream a CSV into an sqlite table; normalizes column names: .geo -> geo, rh*_class -> rh*_\n"," def stream_csv_to_table(csv_file, table_name, year_tag):\n"," # if file doesn't exist, create empty table with expected schema (so joins work)\n"," if not os.path.exists(csv_file) or os.path.getsize(csv_file) <= 1:\n"," # create empty table with schema\n"," conn.execute(f'''\n"," CREATE TABLE IF NOT EXISTS {table_name} (\n"," geo TEXT PRIMARY KEY,\n"," rh98_{year_tag} INTEGER,\n"," rh75_{year_tag} INTEGER,\n"," rh50_{year_tag} INTEGER\n"," )\n"," ''')\n"," conn.commit()\n"," return\n","\n"," cols = ['.geo', 'rh98_class', 'rh75_class', 'rh50_class']\n"," try:\n"," reader = pd.read_csv(csv_file, usecols=cols, chunksize=CHUNKSIZE, iterator=True)\n"," except Exception as e:\n"," # if reading with those columns fails, fall back to reading only .geo\n"," reader = pd.read_csv(csv_file, chunksize=CHUNKSIZE, iterator=True)\n","\n"," any_rows = False\n","\n"," for chunk in reader:\n"," any_rows = True\n"," # normalize column names and keep only what we need\n"," if '.geo' in chunk.columns:\n"," chunk = chunk.rename(columns={\n"," '.geo': 'geo',\n"," 'rh98_class': f'rh98_{year_tag}',\n"," 'rh75_class': f'rh75_{year_tag}',\n"," 'rh50_class': f'rh50_{year_tag}'\n"," })\n"," # ensure columns exist even if file lacked them\n"," for c in [f'rh98_{year_tag}', f'rh75_{year_tag}', f'rh50_{year_tag}']:\n"," if c not in chunk.columns:\n"," chunk[c] = pd.NA\n","\n"," chunk[['geo', f'rh98_{year_tag}', f'rh75_{year_tag}', f'rh50_{year_tag}']].to_sql(\n"," table_name, conn, if_exists='append', index=False\n"," )\n","\n"," # If the file had headers but no data rows,\n"," # make sure the table still exists with the right schema\n"," if not any_rows:\n"," conn.execute(f'''\n"," CREATE TABLE IF NOT EXISTS {table_name} (\n"," geo TEXT PRIMARY KEY,\n"," rh98_{year_tag} INTEGER,\n"," rh75_{year_tag} INTEGER,\n"," rh50_{year_tag} INTEGER\n"," )\n"," ''')\n"," conn.commit()\n","\n"," # Stream each file into its own sqlite table\n"," print(\"File 4\")\n"," stream_csv_to_table(file_4, 't4', year_4)\n"," print(\"File 3\")\n"," stream_csv_to_table(file_3, 't3', year_3)\n"," print(\"File 2\")\n"," stream_csv_to_table(file_2, 't2', year_2)\n"," print(\"File 1\")\n"," stream_csv_to_table(file_1, 't1', year_1)\n"," print(\"File 0\")\n"," stream_csv_to_table(file_0, 't0', year)\n","\n"," # Build union of all geo keys (on-disk), then count total rows\n"," union_sql = '''\n"," SELECT geo FROM t4\n"," UNION\n"," SELECT geo FROM t3\n"," UNION\n"," SELECT geo FROM t2\n"," UNION\n"," SELECT geo FROM t1\n"," UNION\n"," SELECT geo FROM t0\n"," '''\n"," count_sql = f\"SELECT COUNT(*) FROM ({union_sql}) AS u\"\n"," cursor = conn.execute(count_sql)\n"," total_rows = cursor.fetchone()[0]\n"," print(\"Total unique .geo keys (union):\", total_rows)\n","\n"," # process in CHUNKSIZE slices from the union\n"," num_chunks = math.ceil(total_rows / CHUNKSIZE)\n"," print(f\"Processing {num_chunks} merge-chunks of size up to {CHUNKSIZE}...\")\n","\n"," # build a parameterized select that left-joins each year's table to the union subquery\n"," chunk_select_template = f'''\n"," SELECT u.geo AS \".geo\",\n"," t4.rh98_{year_4} as rh98_{year_4}, t4.rh75_{year_4} as rh75_{year_4}, t4.rh50_{year_4} as rh50_{year_4},\n"," t3.rh98_{year_3} as rh98_{year_3}, t3.rh75_{year_3} as rh75_{year_3}, t3.rh50_{year_3} as rh50_{year_3},\n"," t2.rh98_{year_2} as rh98_{year_2}, t2.rh75_{year_2} as rh75_{year_2}, t2.rh50_{year_2} as rh50_{year_2},\n"," t1.rh98_{year_1} as rh98_{year_1}, t1.rh75_{year_1} as rh75_{year_1}, t1.rh50_{year_1} as rh50_{year_1},\n"," t0.rh98_{year} as rh98_{year}, t0.rh75_{year} as rh75_{year}, t0.rh50_{year} as rh50_{year}\n"," FROM (\n"," {union_sql}\n"," ORDER BY geo\n"," LIMIT ? OFFSET ?\n"," ) u\n"," LEFT JOIN t4 ON u.geo = t4.geo\n"," LEFT JOIN t3 ON u.geo = t3.geo\n"," LEFT JOIN t2 ON u.geo = t2.geo\n"," LEFT JOIN t1 ON u.geo = t1.geo\n"," LEFT JOIN t0 ON u.geo = t0.geo\n"," '''\n","\n"," # remove any existing output correction files for this district/year so we can append anew\n"," out_file_y2 = f'{path}{dist_list[i]}/{year_2}/result_chm_corrections.csv'\n"," out_file_y1 = f'{path}{dist_list[i]}/{year_1}/result_chm_corrections.csv'\n"," # We'll append chunk outputs; delete existing so headers are added correctly (mimic original behaviour)\n"," if os.path.exists(out_file_y2):\n"," os.remove(out_file_y2)\n"," if os.path.exists(out_file_y1) and year != '2019':\n"," os.remove(out_file_y1)\n","\n"," for chunk_idx in range(num_chunks):\n"," offset = chunk_idx * CHUNKSIZE\n"," params = (CHUNKSIZE, offset)\n"," # read chunk into pandas\n"," df_chunk = pd.read_sql_query(chunk_select_template, conn, params=params)\n","\n"," # rename the 'year' columns to include the actual year variable names used in the template above\n"," # (they already are named properly except the generic 'rh98_{year}' placeholders which the SQL aliased as rh98_{year})\n"," # ensure types and presence of columns, then proceed exactly as your old code expects (select columns etc.)\n"," print(f\"Processing SQL chunk {chunk_idx+1}/{num_chunks} with {len(df_chunk)} rows\")\n"," total_length += len(df_chunk)\n"," print(\"Length of merged_df (this chunk):\", len(df_chunk))\n"," print(\"Total Length so far:\", total_length)\n","\n"," # Recreate expected column selection (same as original)\n"," try:\n"," merged_df = df_chunk[['.geo',\n"," f'rh98_{year_4}', f'rh98_{year_3}', f'rh98_{year_2}', f'rh98_{year_1}', f'rh98_{year}',\n"," f'rh75_{year_4}', f'rh75_{year_3}', f'rh75_{year_2}', f'rh75_{year_1}', f'rh75_{year}',\n"," f'rh50_{year_4}', f'rh50_{year_3}', f'rh50_{year_2}', f'rh50_{year_1}', f'rh50_{year}']]\n"," except Exception as e:\n"," print(\"Column selection error on chunk:\", e)\n"," # Continue with whatever columns are present\n"," merged_df = df_chunk.copy()\n","\n"," # run correction on this chunk exactly like before\n"," if year == '2019':\n"," correction_df = ch_corrections_2017(merged_df)\n"," else:\n"," correction_df = ch_corrections(merged_df)\n","\n"," del(merged_df)\n","\n"," total_corrections += len(correction_df)\n"," print(f'Correction_df length (chunk): {len(correction_df)}')\n"," print(f'Total Corrections so far: {total_corrections}')\n","\n"," if len(correction_df) > 0:\n"," # same CH assignment logic as before - keep exactly the same code\n"," choices = [0, 0, 1, 2, 1, 2]\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 0) & (correction_df[f'rh75_{year_4}'] == 1) & (correction_df[f'rh98_{year_4}'] == 1),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 0),\n"," (correction_df[f'rh50_{year_4}'] == 1) & (correction_df[f'rh75_{year_4}'] == 0) & (correction_df[f'rh98_{year_4}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_4}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 0) & (correction_df[f'rh75_{year_3}'] == 1) & (correction_df[f'rh98_{year_3}'] == 1),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 0),\n"," (correction_df[f'rh50_{year_3}'] == 1) & (correction_df[f'rh75_{year_3}'] == 0) & (correction_df[f'rh98_{year_3}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_3}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 0) & (correction_df[f'rh75_{year_2}'] == 1) & (correction_df[f'rh98_{year_2}'] == 1),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 0),\n"," (correction_df[f'rh50_{year_2}'] == 1) & (correction_df[f'rh75_{year_2}'] == 0) & (correction_df[f'rh98_{year_2}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_2}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 0) & (correction_df[f'rh75_{year_1}'] == 1) & (correction_df[f'rh98_{year_1}'] == 1),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 0),\n"," (correction_df[f'rh50_{year_1}'] == 1) & (correction_df[f'rh75_{year_1}'] == 0) & (correction_df[f'rh98_{year_1}'] == 1)\n"," ]\n"," correction_df[f'ch_{year_1}'] = np.select(conditions, choices, default=3)\n","\n"," conditions = [\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 0) & (correction_df[f'rh75_{year}'] == 1) & (correction_df[f'rh98_{year}'] == 1),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 0),\n"," (correction_df[f'rh50_{year}'] == 1) & (correction_df[f'rh75_{year}'] == 0) & (correction_df[f'rh98_{year}'] == 1)\n"," ]\n"," correction_df[f'ch_{year}'] = np.select(conditions, choices, default=3)\n","\n"," cols = correction_df.columns\n"," for j in range(1, len(cols)):\n"," correction_df[cols[j]] = correction_df[cols[j]].astype('Int64')\n","\n"," # Write outputs appending chunk results (same filenames as before)\n"," # if year != last_year:\n"," # correction_year_2 = correction_df[['.geo', f'rh50_{year_2}', f'rh75_{year_2}', f'rh98_{year_2}', f'ch_{year_2}']]\n"," correction_year_2 = correction_df[correction_df[f'ch_{year_2}'].notna()][['.geo', f'rh50_{year_2}', f'rh75_{year_2}', f'rh98_{year_2}', f'ch_{year_2}']]\n"," correction_year_2.to_csv(out_file_y2, mode='a', index=False, header=not os.path.exists(out_file_y2))\n","\n"," if year != '2019':\n"," # correction_year_1 = correction_df[['.geo', f'rh50_{year_1}', f'rh75_{year_1}', f'rh98_{year_1}', f'ch_{year_1}']]\n"," correction_year_1 = correction_df[correction_df[f'ch_{year_1}'].notna()][['.geo', f'rh50_{year_1}', f'rh75_{year_1}', f'rh98_{year_1}', f'ch_{year_1}']]\n"," correction_year_1.to_csv(out_file_y1, mode='a', index=False, header=not os.path.exists(out_file_y1))\n","\n"," del(correction_df)\n","\n"," # cleanup sqlite DB\n"," conn.close()\n"," try:\n"," os.remove(db_path)\n"," except Exception:\n"," pass\n","\n"," # --- End drop-in chunked-on-disk merge logic ---"]}],"metadata":{"colab":{"collapsed_sections":["OpBWXM1TMVAs"],"provenance":[{"file_id":"1mrIZUaD4wZbGsDhalhghUuHK-uYOTTew","timestamp":1758914446736}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb b/utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb new file mode 100644 index 00000000..a7b0281d --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/uploadAssets.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":13357,"status":"ok","timestamp":1775284491086,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"yewisZzO_XBA"},"outputs":[],"source":["import ee\n","import pandas as pd\n","import json\n","from glob import glob\n","import geemap\n","import pyproj\n","from geopandas import geopandas as gpd\n","from shapely.geometry import Point\n","from copy import deepcopy\n","import numpy as np\n","import os\n","import ast\n","from google.cloud import storage"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":22,"status":"ok","timestamp":1774410186498,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"iIy2d0N33YVM","outputId":"d30444eb-aa3c-47f4-a361-2793d2fe07a2"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# from google.colab import drive\n","# drive.flush_and_unmount()"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":50444,"status":"ok","timestamp":1775284560071,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"XOwz3mnBYzzf","outputId":"da646a7e-387e-4fd0-ea21-11a6859e69e4"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":3,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":44694,"status":"ok","timestamp":1775284604766,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"EmZdWlXYBv-I","outputId":"b6bfe92a-5c89-470a-ee5b-add2fbf5b974"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["ee.Authenticate()\n","ee_project = \"corestack1-dev-alpha\"\n","ee.Initialize(project=\"core-stack-dev-2\")#\"corestack1-dev-alpha\")"]},{"cell_type":"code","execution_count":4,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":1407,"status":"ok","timestamp":1775284606174,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"SnpGWiMmDvM3","outputId":"36fbf7ff-49be-4ed0-bd4c-ff348b3a593c"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["client = storage.Client()\n","bucket = client.get_bucket('core_stack')"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":4,"status":"ok","timestamp":1775284606179,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"RT2Qqbs-Y4Qv","outputId":"b9727627-6f15-48e8-d405-a42d433c899e"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775284609820,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qoDgejJ4Y7uk","outputId":"54668df5-bb0a-4fc2-9e77-a9de785ea269"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# agroclimatic_zone = 'Eastern Plateau & Hills Region'\n","\n","# agroclimatic_zone = 'Lower Gangetic Plain Region'\n","\n","agroclimatic_zone = 'Middle Gangetic Plain Region'\n","\n","# agroclimatic_zone = 'Western Himalayan Region'\n","\n","# agroclimatic_zone = 'Eastern Himalayan Region'\n","\n","# agroclimatic_zone = 'Upper Gangetic Plain Region'\n","\n","# agroclimatic_zone = 'Trans Gangetic Plain Region'\n","\n","# agroclimatic_zone = 'Central Plateau & Hills Region'\n","\n","# agroclimatic_zone = 'Western Plateau and Hills Region'\n","\n","# agroclimatic_zone = 'Southern Plateau and Hills Region'\n","\n","# agroclimatic_zone = 'East Coast Plains & Hills Region'"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":8,"status":"ok","timestamp":1775284614550,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"EpHcGapNX0cq","outputId":"cd3efa6e-0415-40bb-be10-31803077cad4"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":4,"status":"ok","timestamp":1774369649861,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qlIS9q3xZAzR","outputId":"e02201ba-b02f-4f7b-83df-53508912e2a1"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# acz_list = ['Western Himalayan Region', 'Eastern Himalayan Region', 'Lower Gangetic Plain Region',\n","# 'Middle Gangetic Plain Region', 'Upper Gangetic Plain Region', 'Trans Gangetic Plain Region',\n","# 'Eastern Plateau & Hills Region', 'Central Plateau & Hills Region', 'Western Plateau and Hills Region',\n","# 'Southern Plateau and Hills Region', 'East Coast Plains & Hills Region']\n","\n","# acz_list = ['Eastern Plateau & Hills Region']"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":6,"status":"ok","timestamp":1775284625250,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"j6ZwUG7Q-7WS","outputId":"989135a3-bb1b-477a-99c7-3a5916d759bb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def upload_file_to_gcs(local_file_path, file_name):\n"," blob = bucket.blob(f'nrm_tree_health/compiled_results/{file_name}.csv') # GCS path\n"," blob.upload_from_filename(local_file_path)\n","\n"," print(\"Upload complete.\")\n","\n","def export_to_gee(file_name):\n"," # CSV GCS path\n"," gcs_path = f'gs://core_stack/nrm_tree_health/compiled_results/{file_name}.csv'\n","\n"," # Create task ID and run table ingestion\n"," task_id = ee.data.newTaskId()[0]\n"," asset_id = f'projects/{ee_project}/assets/tree_characteristics/{file_name}'\n","\n"," manifest = {\n"," 'id': asset_id,\n"," 'sources': [\n"," {\n"," 'primaryPath': gcs_path,\n"," 'additionalPaths': []\n"," }\n"," ]\n"," }\n","\n"," ee.data.startTableIngestion(task_id, manifest)\n"," print(\"Ingestion task started:\", task_id)"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"collapsed":true,"executionInfo":{"elapsed":40,"status":"ok","timestamp":1775284627970,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"T7qHDbSCcy2H","outputId":"a8394995-d17d-44e1-c095-3add8ad846c5"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Set the years for which results need to be combined\n","years = ['2016','2017', '2018','2019','2020','2021','2022','2023','2024']"]},{"cell_type":"markdown","metadata":{"id":"_e3eXwraczpB"},"source":["# Combine CSVs CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":73},"executionInfo":{"elapsed":3235,"status":"ok","timestamp":1774369664051,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"cH7MCucjc4P8","outputId":"9ae54cac-8bc9-4727-edc0-224ef31a9a88"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["119\n","['East Godavari', 'Srikakulam', 'Visakhapatnam', 'Vizianagaram', 'AurangabadB', 'Banka', 'Gaya', 'Jamui', 'Nawada', 'Rohtas', 'Baloda Bazar', 'Balod', 'BalrampurC', 'Bastar', 'Bemetara', 'BijapurC', 'BilaspurC', 'Dantewada', 'Dhamtari', 'Durg', 'Gariaband', 'Janjgir-Champa', 'Jashpur', 'Kabeerdham', 'Kondagaon', 'Korba', 'Koriya', 'Mahasamund', 'Mungeli', 'Narayanpur', 'Raigarh', 'Raipur', 'Rajnandgaon', 'Sukma', 'Surajpur', 'Surguja', 'Uttar Bastar Kanker', 'Bokaro', 'Chatra', 'Deoghar', 'Dhanbad', 'Dumka', 'Garhwa', 'Giridih', 'Godda', 'Gumla', 'Hazaribagh', 'Jamtara', 'Khunti', 'Kodarma', 'Latehar', 'Lohardaga', 'Pakur', 'Palamu', 'Pashchimi Singhbhum', 'Purbi Singhbhum', 'Ramgarh', 'Ranchi', 'Sahibganj', 'Saraikela-kharsawan', 'Simdega', 'Anuppur', 'Balaghat', 'Dindori', 'Jabalpur', 'Katni', 'Mandla', 'Rewa', 'Satna', 'Seoni', 'Shahdol', 'Sidhi', 'Singrauli', 'Umaria', 'Bhandara', 'Chandrapur', 'Garhchiroli', 'Gondiya', 'Nagpur', 'Wardha', 'Yavatmal', 'Anugul', 'Balangir', 'Baleshwar', 'Bargarh', 'Bauda', 'Bhadrak', 'Cuttack', 'Debagarh', 'Dhenkanal', 'Gajapati', 'Ganjam', 'Jajapur', 'Jharsuguda', 'Kalahandi', 'Kandhamal', 'Kendujhar', 'Koraput', 'Malkangiri', 'Mayurbhanj', 'Nabarangapur', 'Nayagarh', 'Nuapada', 'Rayagada', 'Sambalpur', 'Subarnapur', 'Sundargarh', 'Adilabad', 'Karimnagar', 'Khammam', 'Warangal', 'Mirzapur', 'Sonbhadra', 'Bankura', 'Barddhaman', 'Birbhum', 'Murshidabad', 'Pashchim Medinipur', 'Puruliya']\n"]}],"source":["df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n","dist_list = list(df['Name'])\n","print(len(dist_list))\n","print(dist_list)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":24132732,"status":"error","timestamp":1774393796785,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"9hFjUp4QmLL3","outputId":"401dc704-cf94-41f9-a38c-41c4c2b11d2a"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["2023\n","0 East Godavari\n","1 Srikakulam\n","2 Visakhapatnam\n","3 Vizianagaram\n","4 AurangabadB\n","5 Banka\n","6 Gaya\n","7 Jamui\n","8 Nawada\n","9 Rohtas\n","10 Baloda Bazar\n","11 Balod\n","12 BalrampurC\n","13 Bastar\n","14 Bemetara\n","15 BijapurC\n","16 BilaspurC\n","17 Dantewada\n","18 Dhamtari\n","19 Durg\n","20 Gariaband\n","21 Janjgir-Champa\n","22 Jashpur\n","23 Kabeerdham\n","24 Kondagaon\n","25 Korba\n","26 Koriya\n","Saving result_0.csv with 80,497,091 rows\n","Upload complete.\n","Ingestion task started: c73487a3-3340-4ce0-afe7-141323f3c52a\n","27 Mahasamund\n","28 Mungeli\n","29 Narayanpur\n","30 Raigarh\n","31 Raipur\n","32 Rajnandgaon\n","33 Sukma\n","34 Surajpur\n","35 Surguja\n","36 Uttar Bastar Kanker\n","37 Bokaro\n","38 Chatra\n","39 Deoghar\n","40 Dhanbad\n","41 Dumka\n","42 Garhwa\n","43 Giridih\n","Saving result_1.csv with 80,298,124 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: 1469ba67-3aff-4e72-997c-39bbba391b60\n","44 Godda\n","45 Gumla\n","46 Hazaribagh\n","47 Jamtara\n","48 Khunti\n","49 Kodarma\n","50 Latehar\n","51 Lohardaga\n","52 Pakur\n","53 Palamu\n","54 Pashchimi Singhbhum\n","55 Purbi Singhbhum\n","56 Ramgarh\n","57 Ranchi\n","58 Sahibganj\n","59 Saraikela-kharsawan\n","60 Simdega\n","61 Anuppur\n","62 Balaghat\n","63 Dindori\n","64 Jabalpur\n","65 Katni\n","66 Mandla\n","67 Rewa\n","68 Satna\n","69 Seoni\n","70 Shahdol\n","71 Sidhi\n","Saving result_2.csv with 80,399,602 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: 17b547b5-7742-4840-806d-2fb7800bc9ce\n","72 Singrauli\n","73 Umaria\n","74 Bhandara\n","75 Chandrapur\n","76 Garhchiroli\n","77 Gondiya\n","78 Nagpur\n","79 Wardha\n","80 Yavatmal\n","81 Anugul\n","82 Balangir\n","83 Baleshwar\n","84 Bargarh\n","85 Bauda\n","86 Bhadrak\n","87 Cuttack\n","88 Debagarh\n","89 Dhenkanal\n","Saving result_3.csv with 80,196,991 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: ed0f5f3d-2f04-43b1-9f20-0b012f85218a\n","90 Gajapati\n","91 Ganjam\n","92 Jajapur\n","93 Jharsuguda\n","94 Kalahandi\n","95 Kandhamal\n","96 Kendujhar\n","97 Koraput\n","98 Malkangiri\n","99 Mayurbhanj\n","100 Nabarangapur\n","101 Nayagarh\n","102 Nuapada\n","103 Rayagada\n","Saving result_4.csv with 80,452,883 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: ae7633ed-a43e-4b3c-9074-3b3f223d2342\n","104 Sambalpur\n","105 Subarnapur\n","106 Sundargarh\n","107 Adilabad\n","108 Karimnagar\n","109 Khammam\n","110 Warangal\n","111 Mirzapur\n","112 Sonbhadra\n","113 Bankura\n","114 Barddhaman\n","115 Birbhum\n","116 Murshidabad\n","117 Pashchim Medinipur\n","118 Puruliya\n","Saving result_5.csv with 30,673,393 rows\n","Upload complete.\n","Ingestion task started: 1869d43e-71d7-49cc-9c1d-b1bb4a7f8467\n","2024\n","0 East Godavari\n","1 Srikakulam\n","2 Visakhapatnam\n","3 Vizianagaram\n","4 AurangabadB\n","5 Banka\n","6 Gaya\n","7 Jamui\n","8 Nawada\n","9 Rohtas\n","10 Baloda Bazar\n","11 Balod\n","12 BalrampurC\n","13 Bastar\n","14 Bemetara\n","15 BijapurC\n","16 BilaspurC\n","17 Dantewada\n","18 Dhamtari\n","19 Durg\n","20 Gariaband\n","21 Janjgir-Champa\n","22 Jashpur\n","23 Kabeerdham\n","24 Kondagaon\n","25 Korba\n","26 Koriya\n","Saving result_0.csv with 80,265,488 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: dafeda5b-16c0-423d-a117-240a643c4431\n","27 Mahasamund\n","28 Mungeli\n","29 Narayanpur\n","30 Raigarh\n","31 Raipur\n","32 Rajnandgaon\n","33 Sukma\n","34 Surajpur\n","35 Surguja\n","36 Uttar Bastar Kanker\n","37 Bokaro\n","38 Chatra\n","39 Deoghar\n","40 Dhanbad\n","41 Dumka\n","42 Garhwa\n","43 Giridih\n","Saving result_1.csv with 80,493,525 rows\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Ingestion task started: 248f4208-704a-4ba9-85d1-9bead9490c53\n","44 Godda\n","45 Gumla\n","46 Hazaribagh\n","47 Jamtara\n","48 Khunti\n","49 Kodarma\n","50 Latehar\n","51 Lohardaga\n","52 Pakur\n","53 Palamu\n","54 Pashchimi Singhbhum\n","55 Purbi Singhbhum\n","56 Ramgarh\n","57 Ranchi\n","58 Sahibganj\n","59 Saraikela-kharsawan\n","60 Simdega\n","61 Anuppur\n","62 Balaghat\n","63 Dindori\n","64 Jabalpur\n","65 Katni\n","66 Mandla\n","67 Rewa\n","68 Satna\n","69 Seoni\n","70 Shahdol\n","71 Sidhi\n","Saving result_2.csv with 80,264,095 rows\n","Upload complete.\n","Ingestion task started: 3cde4a32-1589-462e-b073-f84f97e6cd27\n"]},{"output_type":"error","ename":"OSError","evalue":"[Errno 5] Input/output error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36msave\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 269\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 270\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 271\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36m_save\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 274\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save_header\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 275\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save_body\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 276\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36m_save_body\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 313\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_save_chunk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstart_i\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mend_i\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 314\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36m_save_chunk\u001b[0;34m(self, start_i, end_i)\u001b[0m\n\u001b[1;32m 323\u001b[0m \u001b[0mix\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata_index\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mslicer\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_values_for_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_number_format\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 324\u001b[0;31m libwriters.write_csv_rows(\n\u001b[0m\u001b[1;32m 325\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32mwriters.pyx\u001b[0m in \u001b[0;36mpandas._libs.writers.write_csv_rows\u001b[0;34m()\u001b[0m\n","\u001b[0;31mOSError\u001b[0m: [Errno 5] Input/output error","\nDuring handling of the above exception, another exception occurred:\n","\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)","\u001b[0;31mOSError\u001b[0m: [Errno 5] Input/output error","\nDuring handling of the above exception, another exception occurred:\n","\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_2765/2038472130.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[0;31m# Append to current output file\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 34\u001b[0;31m df_chunk.to_csv(drive_path, mode='a',\n\u001b[0m\u001b[1;32m 35\u001b[0m header=first_write, index=False)\n\u001b[1;32m 36\u001b[0m \u001b[0mfirst_write\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/util/_decorators.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 331\u001b[0m \u001b[0mstacklevel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfind_stack_level\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 332\u001b[0m )\n\u001b[0;32m--> 333\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 334\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 335\u001b[0m \u001b[0;31m# error: \"Callable[[VarArg(Any), KwArg(Any)], Any]\" has no\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/generic.py\u001b[0m in \u001b[0;36mto_csv\u001b[0;34m(self, path_or_buf, sep, na_rep, float_format, columns, header, index, index_label, mode, encoding, compression, quoting, quotechar, lineterminator, chunksize, date_format, doublequote, escapechar, decimal, errors, storage_options)\u001b[0m\n\u001b[1;32m 3965\u001b[0m )\n\u001b[1;32m 3966\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3967\u001b[0;31m return DataFrameRenderer(formatter).to_csv(\n\u001b[0m\u001b[1;32m 3968\u001b[0m \u001b[0mpath_or_buf\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3969\u001b[0m \u001b[0mlineterminator\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mlineterminator\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/format.py\u001b[0m in \u001b[0;36mto_csv\u001b[0;34m(self, path_or_buf, encoding, sep, columns, index_label, mode, compression, quoting, quotechar, lineterminator, chunksize, date_format, doublequote, escapechar, errors, storage_options)\u001b[0m\n\u001b[1;32m 1012\u001b[0m \u001b[0mformatter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfmt\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1013\u001b[0m )\n\u001b[0;32m-> 1014\u001b[0;31m \u001b[0mcsv_formatter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msave\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1015\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1016\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcreated_buffer\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/formats/csvs.py\u001b[0m in \u001b[0;36msave\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 249\u001b[0m \"\"\"\n\u001b[1;32m 250\u001b[0m \u001b[0;31m# apply compression and byte/text conversion\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 251\u001b[0;31m with get_handle(\n\u001b[0m\u001b[1;32m 252\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 253\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmode\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/common.py\u001b[0m in \u001b[0;36m__exit__\u001b[0;34m(self, exc_type, exc_value, traceback)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mTracebackType\u001b[0m \u001b[0;34m|\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m ) -> None:\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/io/common.py\u001b[0m in \u001b[0;36mclose\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 142\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreated_handles\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhandle\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 143\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mhandle\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreated_handles\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 144\u001b[0;31m \u001b[0mhandle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 145\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreated_handles\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_wrapped\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mOSError\u001b[0m: [Errno 5] Input/output error"]}],"source":["best_month = best_month_dict[agroclimatic_zone]\n","chunk_size=500_000\n","max_rows=80_000_000\n","\n","for year in years:\n"," print(year)\n"," result_num = 0\n"," dst_num = 0\n"," row_counter = 0\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n"," os.makedirs(path, exist_ok=True)\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # controls header\n","\n"," for district in dist_list:\n"," print(dst_num, district)\n"," dst_num += 1\n"," try:\n"," reader = pd.read_csv(\n"," f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc.csv',\n"," chunksize=chunk_size\n"," )\n"," except:\n"," continue\n","\n"," for df_chunk in reader:\n"," if df_chunk.shape[0] > 0:\n"," # print(df_chunk)\n"," # Add \"cc\" column from best month\n"," df_chunk['cc'] = df_chunk[best_month]\n","\n"," # Append to current output file\n"," df_chunk.to_csv(drive_path, mode='a',\n"," header=first_write, index=False)\n"," first_write = False\n","\n"," row_counter += len(df_chunk)\n","\n"," # Split file if max_rows exceeded\n"," if row_counter >= max_rows:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n","\n"," file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n","\n"," # reset counters\n"," result_num += 1\n"," row_counter = 0\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # new file needs header\n","\n"," # flush leftovers\n"," if row_counter > 0:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n"," file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"RYYNSioqd0T9"},"outputs":[],"source":["# best_month = best_month_dict[agroclimatic_zone]\n","# for year in years:\n","# print(year)\n","# df_con = pd.DataFrame()\n","# df_len = 0\n","# result_num = 0\n","# dst_num = 0\n","# for district in dist_list:\n","# print(dst_num, district)\n","# dst_num += 1\n","# try:\n","# df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc.csv')\n","# except:\n","# continue\n","# df_len += len(df)\n","# df_con = pd.concat([df_con, df])\n","# del(df)\n","# print(df_len)\n","\n","\n","# if df_len > 80000000:\n","# print(f'Saving result_{result_num}.csv')\n","# df_con['cc'] = df_con[best_month]\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/result_{result_num}.csv', index=False)\n","# file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# upload_file_to_gcs(drive_path, file_name)\n","# export_to_gee(file_name)\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()\n","\n","# if (len(df_con) > 0):\n","# print(f'Saving result_{result_num}.csv')\n","# df_con['cc'] = df_con[best_month]\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(drive_path, index=False)\n","\n","# file_name = f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# upload_file_to_gcs(drive_path, file_name)\n","# export_to_gee(file_name)\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":327},"executionInfo":{"elapsed":168191,"status":"ok","timestamp":1762767949388,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"7nY3jBPsNG6_","outputId":"23307a31-72c0-48fe-8962-7cce2c8cf161"},"outputs":[{"data":{"text/html":["\n"," \n"," "],"text/plain":[""]},"metadata":{},"output_type":"display_data"},{"name":"stdout","output_type":"stream","text":[".geo object\n","cc_1 int64\n","cc_2 int64\n","cc_3 int64\n","cc_4 int64\n","cc_5 int64\n","cc_6 int64\n","cc_7 int64\n","cc_8 int64\n","cc_9 int64\n","cc_10 int64\n","cc_11 int64\n","cc_12 int64\n","cc int64\n","dtype: object\n","Upload complete.\n","Ingestion task started: 2a4f86c7-03b8-4605-916d-fb63feae926e\n"]}],"source":["# year = '2016'\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results/{year}/'\n","# for result_num in range(1):\n","# drive_path = f\"{path}/result_{result_num}.csv\"\n","# reader = pd.read_csv(drive_path, chunksize=100)\n","# for df_chunk in reader:\n","# print(df_chunk.dtypes)\n","# break\n","# # upload + export\n","# upload_file_to_gcs(\n","# drive_path,\n","# f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )\n","# export_to_gee(\n","# f\"ccd_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )"]},{"cell_type":"markdown","metadata":{"id":"sfozGeDdiQcC"},"source":["# Combine CSVs CH"]},{"cell_type":"code","execution_count":10,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":2344,"status":"ok","timestamp":1775284632488,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"bk7terDQiQ_y","outputId":"8ab1da5e-de1e-4284-e3a6-a81a26266423"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":3,"status":"ok","timestamp":1775284632493,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"eQgVTZCiiYa0","outputId":"83e3c7a2-8256-4c9b-b076-9f3ce2cd84f6"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)"]},{"cell_type":"code","execution_count":12,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":23,"status":"ok","timestamp":1775284633301,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"QxHpT4hhibUG","outputId":"03c65e28-8dfa-4dfc-eec2-b1eb3268f2b2"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]"]},{"cell_type":"code","execution_count":13,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":55},"executionInfo":{"elapsed":15,"status":"ok","timestamp":1775284634739,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"PpR3DJEbifWe","outputId":"9a95daed-af5c-4426-c099-363db40b89a3"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["['Araria', 'Arwal', 'AurangabadB', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar', 'Darbhanga', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur', 'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger', 'Muzaffarpur', 'Nalanda', 'Nawada', 'Pashchim Champaran', 'Patna', 'Purba Champaran', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur', 'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali', 'Godda', 'Sahibganj', 'Ambedkar Nagar', 'Azamgarh', 'Bahraich', 'Ballia', 'Balrampur', 'Basti', 'Chandauli', 'Deoria', 'Faizabad', 'Ghazipur', 'Gonda', 'Gorakhpur', 'Jaunpur', 'Kushinagar', 'Maharajganj', 'Mau', 'Mirzapur', 'Sant Kabir Nagar', 'Sant Ravi Das Nagar', 'Shravasti', 'Siddharth Nagar', 'Sonbhadra', 'Varanasi']\n"]}],"source":["dist_list = []\n","i = 0\n","for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," # print(i, district, zones)\n"," dist_list.append(district)\n"," i += 1\n","# dist_list = ['Darjiling']\n","print(dist_list)"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":7,"status":"ok","timestamp":1775284642451,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"orS9dnFnmVEe","outputId":"9237665f-2f26-4e37-e4af-185bbc58fbbb"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def classify_chunk(df_chunk):\n"," \"\"\"Add ch_class column based on rh50/rh75/rh98 logic.\"\"\"\n"," conditions = [\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 0),\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 1),\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 1) & (df_chunk['rh98_class'] == 0),\n"," (df_chunk['rh50_class'] == 0) & (df_chunk['rh75_class'] == 1) & (df_chunk['rh98_class'] == 1),\n"," (df_chunk['rh50_class'] == 1) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 0),\n"," (df_chunk['rh50_class'] == 1) & (df_chunk['rh75_class'] == 0) & (df_chunk['rh98_class'] == 1)\n"," ]\n"," choices = [0, 0, 1, 2, 1, 2]\n","\n"," df_chunk['ch_class'] = np.select(conditions, choices, default=3)\n"," return df_chunk"]},{"cell_type":"code","execution_count":15,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"PbwYc1iLmXA5","executionInfo":{"status":"error","timestamp":1775286506270,"user_tz":-330,"elapsed":1861463,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"70821778-37d8-4b6a-c59a-304c8f89d767"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["2016\n","0 Araria\n","1 Arwal\n","2 AurangabadB\n","3 Banka\n","4 Begusarai\n","5 Bhagalpur\n","6 Bhojpur\n","7 Buxar\n","8 Darbhanga\n","9 Gaya\n","10 Gopalganj\n","11 Jamui\n","12 Jehanabad\n","13 Kaimur\n","14 Katihar\n","15 Khagaria\n","16 Kishanganj\n","17 Lakhisarai\n","18 Madhepura\n","19 Madhubani\n","20 Munger\n","21 Muzaffarpur\n","22 Nalanda\n","23 Nawada\n","24 Pashchim Champaran\n","25 Patna\n","26 Purba Champaran\n","27 Purnia\n","28 Rohtas\n","29 Saharsa\n","30 Samastipur\n","31 Saran\n","32 Sheikhpura\n","33 Sheohar\n","34 Sitamarhi\n","35 Siwan\n","36 Supaul\n","37 Vaishali\n","38 Godda\n","39 Sahibganj\n","40 Ambedkar Nagar\n","41 Azamgarh\n","42 Bahraich\n","43 Ballia\n","44 Balrampur\n","45 Basti\n","46 Chandauli\n","47 Deoria\n","48 Faizabad\n","49 Ghazipur\n","50 Gonda\n","51 Gorakhpur\n","52 Jaunpur\n","53 Kushinagar\n","54 Maharajganj\n","55 Mau\n","56 Mirzapur\n","57 Sant Kabir Nagar\n","58 Sant Ravi Das Nagar\n","59 Shravasti\n","60 Siddharth Nagar\n","61 Sonbhadra\n","62 Varanasi\n","Saving result_0.csv with 68,660,369 rows\n"]},{"output_type":"error","ename":"KeyboardInterrupt","evalue":"","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_4886/2152513440.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 56\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mrow_counter\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 57\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'Saving result_{result_num}.csv with {row_counter:,} rows'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 58\u001b[0;31m upload_file_to_gcs(\n\u001b[0m\u001b[1;32m 59\u001b[0m \u001b[0mdrive_path\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 60\u001b[0m \u001b[0;34mf\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipykernel_4886/1786300172.py\u001b[0m in \u001b[0;36mupload_file_to_gcs\u001b[0;34m(local_file_path, file_name)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mupload_file_to_gcs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlocal_file_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfile_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mblob\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mbucket\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mblob\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'nrm_tree_health/compiled_results/{file_name}.csv'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# GCS path\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mblob\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupload_from_filename\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlocal_file_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Upload complete.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36mupload_from_filename\u001b[0;34m(self, filename, content_type, client, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 3196\u001b[0m \"\"\"\n\u001b[1;32m 3197\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mcreate_trace_span\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Storage.Blob.uploadFromFilename\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3198\u001b[0;31m self._handle_filename_and_upload(\n\u001b[0m\u001b[1;32m 3199\u001b[0m \u001b[0mfilename\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3200\u001b[0m \u001b[0mcontent_type\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcontent_type\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_handle_filename_and_upload\u001b[0;34m(self, filename, content_type, *args, **kwargs)\u001b[0m\n\u001b[1;32m 3046\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"rb\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mfile_obj\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3047\u001b[0m \u001b[0mtotal_bytes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfstat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_obj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfileno\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mst_size\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3048\u001b[0;31m self._prep_and_do_upload(\n\u001b[0m\u001b[1;32m 3049\u001b[0m \u001b[0mfile_obj\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3050\u001b[0m \u001b[0mcontent_type\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcontent_type\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_prep_and_do_upload\u001b[0;34m(self, file_obj, rewind, size, content_type, client, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, command, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 2833\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2834\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2835\u001b[0;31m created_json = self._do_upload(\n\u001b[0m\u001b[1;32m 2836\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2837\u001b[0m \u001b[0mfile_obj\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_do_upload\u001b[0;34m(self, client, stream, content_type, size, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, command, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 2644\u001b[0m )\n\u001b[1;32m 2645\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2646\u001b[0;31m response = self._do_resumable_upload(\n\u001b[0m\u001b[1;32m 2647\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2648\u001b[0m \u001b[0mstream\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/blob.py\u001b[0m in \u001b[0;36m_do_resumable_upload\u001b[0;34m(self, client, stream, content_type, size, predefined_acl, if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match, timeout, checksum, retry, command, crc32c_checksum_value)\u001b[0m\n\u001b[1;32m 2461\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mupload\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfinished\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2462\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2463\u001b[0;31m \u001b[0mresponse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mupload\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransmit_next_chunk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtransport\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2464\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mDataCorruption\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2465\u001b[0m \u001b[0;31m# Attempt to delete the corrupted object.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/_media/requests/upload.py\u001b[0m in \u001b[0;36mtransmit_next_chunk\u001b[0;34m(self, transport, timeout)\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 528\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 529\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_request_helpers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_and_retry\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mretriable_request\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_retry_strategy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 530\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 531\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrecover\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtransport\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/_media/requests/_request_helpers.py\u001b[0m in \u001b[0;36mwait_and_retry\u001b[0;34m(func, retry_strategy)\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mretry_strategy\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0mfunc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mretry_strategy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 107\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/api_core/retry/retry_unary.py\u001b[0m in \u001b[0;36mretry_wrapped_func\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 292\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_initial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_maximum\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmultiplier\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_multiplier\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 293\u001b[0m )\n\u001b[0;32m--> 294\u001b[0;31m return retry_target(\n\u001b[0m\u001b[1;32m 295\u001b[0m \u001b[0mtarget\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 296\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_predicate\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/api_core/retry/retry_unary.py\u001b[0m in \u001b[0;36mretry_target\u001b[0;34m(target, predicate, sleep_generator, timeout, on_error, exception_factory, **kwargs)\u001b[0m\n\u001b[1;32m 145\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 147\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtarget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 148\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0minspect\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misawaitable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 149\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwarn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_ASYNC_RETRY_WARNING\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/cloud/storage/_media/requests/upload.py\u001b[0m in \u001b[0;36mretriable_request\u001b[0;34m()\u001b[0m\n\u001b[1;32m 519\u001b[0m \u001b[0;31m# Wrap the request business logic in a function to be retried.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 520\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mretriable_request\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 521\u001b[0;31m result = transport.request(\n\u001b[0m\u001b[1;32m 522\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mpayload\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 523\u001b[0m )\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/auth/transport/requests.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, data, headers, max_allowed_time, timeout, **kwargs)\u001b[0m\n\u001b[1;32m 541\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mTimeoutGuard\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mremaining_time\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mguard\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 542\u001b[0m \u001b[0m_helpers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrequest_log\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_LOGGER\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 543\u001b[0;31m response = super(AuthorizedSession, self).request(\n\u001b[0m\u001b[1;32m 544\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 545\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/requests/sessions.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[1;32m 587\u001b[0m }\n\u001b[1;32m 588\u001b[0m \u001b[0msend_kwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msettings\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 589\u001b[0;31m \u001b[0mresp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprep\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0msend_kwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 590\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 591\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/requests/sessions.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, **kwargs)\u001b[0m\n\u001b[1;32m 701\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 702\u001b[0m \u001b[0;31m# Send the request\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 703\u001b[0;31m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0madapter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 704\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 705\u001b[0m \u001b[0;31m# Total elapsed time of the request (approximately)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/requests/adapters.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 665\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 666\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 667\u001b[0;31m resp = conn.urlopen(\n\u001b[0m\u001b[1;32m 668\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 669\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36murlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[0m\n\u001b[1;32m 785\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 786\u001b[0m \u001b[0;31m# Make the request on the HTTPConnection object\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 787\u001b[0;31m response = self._make_request(\n\u001b[0m\u001b[1;32m 788\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 789\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36m_make_request\u001b[0;34m(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)\u001b[0m\n\u001b[1;32m 491\u001b[0m \u001b[0;31m# urllib3.request. It also calls makefile (recv) on the socket.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 492\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 493\u001b[0;31m conn.request(\n\u001b[0m\u001b[1;32m 494\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/urllib3/connection.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)\u001b[0m\n\u001b[1;32m 506\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mb\"%x\\r\\n%b\\r\\n\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchunk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mchunk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 507\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 508\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchunk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 509\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[0;31m# Regardless of whether we have a body or not, if we're in\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/http/client.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 1075\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maudit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"http.client.send\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1076\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1077\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msendall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1078\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1079\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcollections\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mabc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mIterable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/ssl.py\u001b[0m in \u001b[0;36msendall\u001b[0;34m(self, data, flags)\u001b[0m\n\u001b[1;32m 1208\u001b[0m \u001b[0mamount\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbyte_view\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1209\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0mamount\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1210\u001b[0;31m \u001b[0mv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbyte_view\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mcount\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1211\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1212\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/ssl.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, data, flags)\u001b[0m\n\u001b[1;32m 1177\u001b[0m \u001b[0;34m\"non-zero flags not allowed in calls to send() on %s\"\u001b[0m \u001b[0;34m%\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1178\u001b[0m self.__class__)\n\u001b[0;32m-> 1179\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sslobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1180\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1181\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "]}],"source":["chunk_size=500_000\n","max_rows=80_000_000\n","for year in years:\n"," print(year)\n"," result_num = 0\n"," dst_num = 0\n"," row_counter = 0\n","\n"," path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n"," os.makedirs(path, exist_ok=True)\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # controls header writing\n","\n"," for district in dist_list:\n"," print(dst_num, district)\n"," dst_num += 1\n"," try:\n"," reader = pd.read_csv(\n"," f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm.csv',\n"," chunksize=chunk_size\n"," )\n"," except:\n"," print(\"Errorrrrrrrr\")\n"," continue\n","\n"," for df_chunk in reader:\n"," df_chunk = classify_chunk(df_chunk)\n","\n"," # Append to current result file\n"," df_chunk.to_csv(drive_path, mode='a',\n"," header=first_write, index=False)\n"," first_write = False\n","\n"," row_counter += len(df_chunk)\n","\n"," # If exceeds limit → finalize file and start new one\n"," if row_counter >= max_rows:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n","\n"," # upload + export\n"," upload_file_to_gcs(\n"," drive_path,\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n"," export_to_gee(\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n","\n"," # reset counters\n"," result_num += 1\n"," row_counter = 0\n"," drive_path = f\"{path}/result_{result_num}.csv\"\n"," first_write = True # new file needs header\n","\n"," # Flush leftover rows at end of loop\n"," if row_counter > 0:\n"," print(f'Saving result_{result_num}.csv with {row_counter:,} rows')\n"," upload_file_to_gcs(\n"," drive_path,\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n"," export_to_gee(\n"," f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," )\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"SKueMXZDj1NP"},"outputs":[],"source":["# for year in years:\n","# print(year)\n","# df_con = pd.DataFrame()\n","# df_len = 0\n","# result_num = 0\n","# dst_num = 0\n","# for district in dist_list:\n","# print(dst_num, district)\n","# dst_num += 1\n","# try:\n","# df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm.csv')\n","# except:\n","# continue\n","# df_len += len(df)\n","# df_con = pd.concat([df_con, df])\n","# del(df)\n","# print(df_len)\n","\n","# if df_len > 80000000:\n","# print(f'Saving result_{result_num}.csv')\n","\n","# conditions = [\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1)\n","# ]\n","\n","# choices = [0, 0, 1, 2, 1, 2]\n","\n","# df_con['ch_class'] = np.select(conditions, choices, default=3)\n","\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(drive_path, index=False)\n","\n","# upload_file_to_gcs(drive_path, f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","# export_to_gee(f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()\n","\n","# if (len(df_con) > 0):\n","# print(f'Saving result_{result_num}.csv')\n","\n","# conditions = [\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 0) & (df_con['rh75_class'] == 1) & (df_con['rh98_class'] == 1),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 0),\n","# (df_con['rh50_class'] == 1) & (df_con['rh75_class'] == 0) & (df_con['rh98_class'] == 1)\n","# ]\n","\n","# choices = [0, 0, 1, 2, 1, 2]\n","\n","# df_con['ch_class'] = np.select(conditions, choices, default=3)\n","\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n","# if not os.path.exists(path):\n","# os.makedirs(path)\n","\n","# drive_path = f'{path}/result_{result_num}.csv'\n","# df_con.to_csv(drive_path, index=False)\n","\n","# upload_file_to_gcs(drive_path, f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","# export_to_gee(f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\")\n","\n","# result_num += 1\n","# del(df_con)\n","# df_len = 0\n","# df_con = pd.DataFrame()"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"xl-Lt3vZRfsq"},"outputs":[],"source":["\n","# year = '2023'\n","# path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/Compiled Results CH/{year}/'\n","# for result_num in range(6):\n","# drive_path = f\"{path}/result_{result_num}.csv\"\n","# # upload + export\n","# upload_file_to_gcs(\n","# drive_path,\n","# f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )\n","# export_to_gee(\n","# f\"ch_{year}_result_{result_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n","# )"]}],"metadata":{"colab":{"collapsed_sections":["_e3eXwraczpB"],"provenance":[{"file_id":"11PyrzNVnNfHa7YM1M37Eg-Ov3L91sSrM","timestamp":1758134547309}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb b/utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb new file mode 100644 index 00000000..b7a02243 --- /dev/null +++ b/utilities/scripts/tree_health/colab_notebooks/uploadAssets_correction.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":1,"metadata":{"executionInfo":{"elapsed":10466,"status":"ok","timestamp":1775209278781,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"yewisZzO_XBA"},"outputs":[],"source":["import ee\n","import pandas as pd\n","import json\n","from glob import glob\n","import geemap\n","import pyproj\n","from geopandas import geopandas as gpd\n","from shapely.geometry import Point\n","from copy import deepcopy\n","import numpy as np\n","import os\n","import ast\n","from google.cloud import storage"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"executionInfo":{"elapsed":21238,"status":"ok","timestamp":1775209302013,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"XOwz3mnBYzzf","outputId":"1557f773-62e2-4e8e-a6be-a6c619c3f2b6"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Mounted at /content/drive\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":583,"status":"ok","timestamp":1775209354321,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"BYyrrpjo0be9","outputId":"161c88fb-cbea-40cd-d20b-09930cf31c6d"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["ee.Authenticate()\n","ee_project = \"corestack1-dev-alpha\"\n","ee.Initialize(project=\"core-stack-dev-2\")#\"corestack1-dev-alpha\")"]},{"cell_type":"code","execution_count":4,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":197,"status":"ok","timestamp":1775209320541,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"xSSojnkB0Twj","outputId":"d81f8d77-f546-4e6e-c895-c55513dc4822"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["client = storage.Client()\n","bucket = client.get_bucket('core_stack')"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775209320551,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"RT2Qqbs-Y4Qv","outputId":"cc559b70-3004-4507-a360-7dba8797c768"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["best_month_dict = {'Eastern Plateau & Hills Region': 'cc_12',\n"," 'Middle Gangetic Plain Region': 'cc_10',\n"," 'Lower Gangetic Plain Region': 'cc_9',\n"," 'Western Himalayan Region': 'cc_8',\n"," 'Eastern Himalayan Region': 'cc_10',\n"," 'Upper Gangetic Plain Region': 'cc_9',\n"," 'Trans Gangetic Plain Region': 'cc_9',\n"," 'Central Plateau & Hills Region': 'cc_7',\n"," 'Western Plateau and Hills Region': 'cc_11',\n"," 'Southern Plateau and Hills Region': 'cc_8',\n"," 'East Coast Plains & Hills Region': 'cc_12'}"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":9,"status":"ok","timestamp":1775209321213,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"OySilp5oft-j","outputId":"ed9bffbe-3b93-4cdd-fb33-62526c03a36c"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["agroclimaticZone_acronym_dict = {'Eastern Plateau & Hills Region': 'EPAHR',\n"," 'Southern Plateau and Hills Region': 'SPAHR',\n"," 'East Coast Plains & Hills Region': 'ECPHR',\n"," 'Western Plateau and Hills Region': 'WPAHR',\n"," 'Central Plateau & Hills Region': 'CPAHR',\n"," 'Lower Gangetic Plain Region': 'LGPR',\n"," 'Middle Gangetic Plain Region': 'MGPR',\n"," 'Eastern Himalayan Region': 'EHR',\n"," 'Western Himalayan Region': 'WHR',\n"," 'Upper Gangetic Plain Region': 'UGPR',\n"," 'Trans Gangetic Plain Region': 'TGPR',\n"," 'West Coast Plains & Ghat Region': 'WCPGR',\n"," 'Gujarat Plains & Hills Region': 'GPHR',\n"," 'Western Dry Region': 'WDR'}"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":11,"status":"ok","timestamp":1775209323514,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"giCoCm_0fylu","outputId":"782b295f-85ce-497f-8cf7-5fa4bd02ae7d"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["def upload_file_to_gcs(local_file_path, file_name):\n"," blob = bucket.blob(f'nrm_tree_health/correction_compiled_results/{file_name}.csv') # GCS path\n"," blob.upload_from_filename(local_file_path)\n","\n"," print(\"Upload complete.\")\n","\n","def export_to_gee(file_name):\n"," # CSV GCS path\n"," gcs_path = f'gs://core_stack/nrm_tree_health/correction_compiled_results/{file_name}.csv'\n","\n"," # Create task ID and run table ingestion\n"," task_id = ee.data.newTaskId()[0]\n"," asset_id = f'projects/{ee_project}/assets/tree_characteristics/{file_name}'\n","\n"," manifest = {\n"," 'id': asset_id,\n"," 'sources': [\n"," {\n"," 'primaryPath': gcs_path,\n"," 'additionalPaths': []\n"," }\n"," ]\n"," }\n","\n"," ee.data.startTableIngestion(task_id, manifest)\n"," print(\"Ingestion task started:\", task_id)"]},{"cell_type":"markdown","metadata":{"id":"YGk1NbQIY_9w"},"source":["Merge Correction CSVs"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":43,"status":"ok","timestamp":1775209358495,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"qlIS9q3xZAzR","outputId":"724f3d95-d00a-4f55-c744-97b764af7bd2"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["acz_list = [\n"," # 'Western Himalayan Region',\n"," 'Eastern Himalayan Region',\n"," # 'Lower Gangetic Plain Region',\n"," # 'Middle Gangetic Plain Region',\n"," # 'Upper Gangetic Plain Region',\n"," # 'Trans Gangetic Plain Region',\n"," # 'Eastern Plateau & Hills Region',\n"," # 'Central Plateau & Hills Region',\n"," # 'Western Plateau and Hills Region',\n"," # 'Southern Plateau and Hills Region',\n"," # 'East Coast Plains & Hills Region'\n"," ]\n","\n"]},{"cell_type":"code","execution_count":10,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":17},"executionInfo":{"elapsed":10,"status":"ok","timestamp":1775209362961,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"},"user_tz":-330},"id":"ae3Qg-PHZFWP","outputId":"2a0f4a86-0f2d-4fe3-f889-ed971bfdf4d4"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}}],"source":["years = ['2017', '2018', '2019', '2020', '2021', '2022', '2023']"]},{"cell_type":"markdown","metadata":{"id":"Ah69USIF_hSV"},"source":["# CCD"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":363},"collapsed":true,"id":"Gi4Bw7-LZJHw","outputId":"3af2dd77-cd05-4aaa-a89c-0bd7ffb5568a"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Eastern Plateau & Hills Region\n","2020\n","length(dist_list): 119\n","0 East Godavari\n","1 Srikakulam\n","2 Visakhapatnam\n","3 Vizianagaram\n","4 AurangabadB\n","5 Banka\n","6 Gaya\n","7 Jamui\n","8 Nawada\n","9 Rohtas\n","10 Baloda Bazar\n","11 Balod\n","12 BalrampurC\n","13 Bastar\n","14 Bemetara\n","15 BijapurC\n"]}],"source":["# CCD\n","\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n"," for year in years:\n"," print(year)\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/Agroclimatic_regions/{agroclimatic_zone}.csv')\n"," dist_list = list(df['Name'])\n"," print(f'length(dist_list): {len(dist_list)}')\n","\n"," i = 0\n"," merged_df = pd.DataFrame()\n"," for district in dist_list:\n"," print(i, district)\n"," i += 1\n","\n"," try:\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_monthly_cc_corrections.csv')\n"," except:\n"," continue\n","\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n","\n"," print(f'length(merged_df): {len(merged_df)}')\n","\n"," cols = merged_df.columns\n"," for i in range(1, len(cols)):\n"," merged_df[cols[i]] = merged_df[cols[i]].astype('Int64')\n"," drive_path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/corrections_{year}.csv'\n"," merged_df.to_csv(drive_path, index=False)\n","\n"," file_name = f\"corrections_ccd_{year}_result_0_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)"]},{"cell_type":"markdown","metadata":{"id":"J7kg35BL_rWU"},"source":["# CH"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"umuwxquJLI1q","executionInfo":{"status":"ok","timestamp":1775217779330,"user_tz":-330,"elapsed":8414523,"user":{"displayName":"CoRE Stack","userId":"15795073488475210764"}},"outputId":"0605ef8a-3f71-4edc-a097-442968229a59"},"outputs":[{"output_type":"display_data","data":{"text/plain":[""],"text/html":["\n"," \n"," "]},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Eastern Himalayan Region\n","2017\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 2979532\n","1 Changlang\n","length(merged_df): 3307447\n","2 Dibang Valley\n","length(merged_df): 4194324\n","3 East Kameng\n","length(merged_df): 4352964\n","4 East Siang\n","length(merged_df): 4458119\n","5 Kurung Kumey\n","6 Lohit\n","length(merged_df): 5274731\n","7 Longding\n","length(merged_df): 5300151\n","8 Lower Dibang Valley\n","length(merged_df): 6630346\n","9 Lower Subansiri\n","10 Namsai\n","length(merged_df): 6654526\n","11 Papum Pare\n","12 Tawang\n","13 Tirap\n","length(merged_df): 6808687\n","14 Upper Siang\n","15 Upper Subansiri\n","16 West Kameng\n","length(merged_df): 7216607\n","17 West Siang\n","18 Baksa\n","length(merged_df): 7300480\n","19 Barpeta\n","length(merged_df): 7319087\n","20 Bongaigaon\n","length(merged_df): 7335411\n","21 Cachar\n","length(merged_df): 7372150\n","22 Chirang\n","length(merged_df): 7420786\n","23 Darrang\n","length(merged_df): 7454804\n","24 Dhemaji\n","length(merged_df): 7457716\n","25 Dhubri\n","length(merged_df): 7488291\n","26 Dibrugarh\n","length(merged_df): 7520020\n","27 Dima Hasao\n","length(merged_df): 7968962\n","28 Goalpara\n","length(merged_df): 7993104\n","29 Golaghat\n","length(merged_df): 8007504\n","30 Hailakandi\n","length(merged_df): 8013767\n","31 Jorhat\n","length(merged_df): 8023171\n","32 Kamrup Metropolitan\n","length(merged_df): 8124708\n","33 Kamrup\n","length(merged_df): 8236975\n","34 Karbi Anglong\n","length(merged_df): 10643741\n","35 Karimganj\n","length(merged_df): 10760499\n","36 Kokrajhar\n","length(merged_df): 10925086\n","37 Lakhimpur\n","length(merged_df): 10933650\n","38 Morigaon\n","length(merged_df): 11075910\n","39 Nagaon\n","length(merged_df): 11178452\n","40 Nalbari\n","length(merged_df): 11196382\n","41 Sivasagar\n","length(merged_df): 11234982\n","42 Sonitpur\n","length(merged_df): 11367250\n","43 Tinsukia\n","length(merged_df): 11619699\n","44 Udalguri\n","length(merged_df): 11682623\n","45 Bishnupur\n","46 Chandel\n","47 Churachandpur\n","48 Imphal East\n","49 Imphal West\n","50 Senapati\n","length(merged_df): 12040830\n","51 Tamenglong\n","length(merged_df): 12293929\n","52 Thoubal\n","53 Ukhrul\n","length(merged_df): 12351297\n","54 East Garo Hills\n","length(merged_df): 12481308\n","55 East Khasi Hills\n","length(merged_df): 13191302\n","56 Jaintia Hills\n","length(merged_df): 14527081\n","57 North Garo Hills\n","length(merged_df): 14543576\n","58 Ri Bhoi\n","length(merged_df): 15479001\n","59 South Garo Hills\n","length(merged_df): 15711222\n","60 South West Garo Hills\n","length(merged_df): 15721187\n","61 South West Khasi Hills\n","length(merged_df): 15973372\n","62 West Garo Hills\n","length(merged_df): 16109333\n","63 West Khasi Hills\n","length(merged_df): 16882649\n","64 Aizawl\n","length(merged_df): 16883641\n","65 Champhai\n","length(merged_df): 16962300\n","66 Kolasib\n","67 Lawangtlai\n","length(merged_df): 17607846\n","68 Lunglei\n","length(merged_df): 17777978\n","69 Mamit\n","length(merged_df): 17816098\n","70 Saiha\n","length(merged_df): 18337927\n","71 Serchhip\n","length(merged_df): 18456898\n","72 Dimapur\n","length(merged_df): 18653296\n","73 Kiphire\n","length(merged_df): 18828710\n","74 Kohima\n","length(merged_df): 19026827\n","75 Longleng\n","76 Mokokchung\n","77 Mon\n","length(merged_df): 19026889\n","78 Peren\n","length(merged_df): 19635289\n","79 Phek\n","length(merged_df): 19663020\n","80 Tuensang\n","length(merged_df): 19889386\n","81 Wokha\n","length(merged_df): 20040494\n","82 Zunheboto\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 20042784\n","86 West Sikkim\n","length(merged_df): 20042906\n","87 Dhalai\n","length(merged_df): 20349925\n","88 Gomati\n","length(merged_df): 20559679\n","89 Khowai\n","length(merged_df): 20770547\n","90 North Tripura\n","length(merged_df): 20915052\n","91 Sipahijala\n","length(merged_df): 21005581\n","92 South Tripura\n","length(merged_df): 21023307\n","93 Unokoti\n","length(merged_df): 21069492\n","94 West Tripura\n","length(merged_df): 21162616\n","95 Alipurduar\n","length(merged_df): 21333417\n","96 Darjiling\n","length(merged_df): 21731155\n","97 Jalpaiguri\n","length(merged_df): 21848690\n","98 Koch Bihar\n","length(merged_df): 21905819\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: a50d3798-10da-4004-a2fb-6e0e6aa129c2\n","2018\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 478344\n","1 Changlang\n","length(merged_df): 2444511\n","2 Dibang Valley\n","length(merged_df): 3836445\n","3 East Kameng\n","length(merged_df): 5122433\n","4 East Siang\n","length(merged_df): 5540789\n","5 Kurung Kumey\n","length(merged_df): 6210350\n","6 Lohit\n","length(merged_df): 7154987\n","7 Longding\n","length(merged_df): 7518784\n","8 Lower Dibang Valley\n","length(merged_df): 9181092\n","9 Lower Subansiri\n","length(merged_df): 9544762\n","10 Namsai\n","length(merged_df): 9599147\n","11 Papum Pare\n","length(merged_df): 10071058\n","12 Tawang\n","13 Tirap\n","length(merged_df): 10651507\n","14 Upper Siang\n","15 Upper Subansiri\n","length(merged_df): 10996049\n","16 West Kameng\n","length(merged_df): 11911902\n","17 West Siang\n","length(merged_df): 12567368\n","18 Baksa\n","length(merged_df): 12741540\n","19 Barpeta\n","length(merged_df): 12814699\n","20 Bongaigaon\n","length(merged_df): 12865104\n","21 Cachar\n","length(merged_df): 13634666\n","22 Chirang\n","length(merged_df): 13737843\n","23 Darrang\n","length(merged_df): 13803079\n","24 Dhemaji\n","length(merged_df): 14005416\n","25 Dhubri\n","length(merged_df): 14086546\n","26 Dibrugarh\n","length(merged_df): 14327414\n","27 Dima Hasao\n","length(merged_df): 15403060\n","28 Goalpara\n","length(merged_df): 15456008\n","29 Golaghat\n","length(merged_df): 15577333\n","30 Hailakandi\n","length(merged_df): 15668824\n","31 Jorhat\n","length(merged_df): 15803737\n","32 Kamrup Metropolitan\n","length(merged_df): 15886696\n","33 Kamrup\n","length(merged_df): 16170410\n","34 Karbi Anglong\n","length(merged_df): 17867132\n","35 Karimganj\n","length(merged_df): 18041501\n","36 Kokrajhar\n","length(merged_df): 18451796\n","37 Lakhimpur\n","length(merged_df): 18617844\n","38 Morigaon\n","length(merged_df): 18681752\n","39 Nagaon\n","length(merged_df): 18889057\n","40 Nalbari\n","length(merged_df): 18936490\n","41 Sivasagar\n","length(merged_df): 19070646\n","42 Sonitpur\n","length(merged_df): 19487051\n","43 Tinsukia\n","length(merged_df): 20132264\n","44 Udalguri\n","length(merged_df): 20234159\n","45 Bishnupur\n","length(merged_df): 20256633\n","46 Chandel\n","length(merged_df): 21456762\n","47 Churachandpur\n","length(merged_df): 22912733\n","48 Imphal East\n","length(merged_df): 22977450\n","49 Imphal West\n","length(merged_df): 23010433\n","50 Senapati\n","length(merged_df): 24073080\n","51 Tamenglong\n","length(merged_df): 25665819\n","52 Thoubal\n","length(merged_df): 25718095\n","53 Ukhrul\n","length(merged_df): 27128826\n","54 East Garo Hills\n","length(merged_df): 27513780\n","55 East Khasi Hills\n","length(merged_df): 28529934\n","56 Jaintia Hills\n","length(merged_df): 29485284\n","57 North Garo Hills\n","length(merged_df): 29544664\n","58 Ri Bhoi\n","length(merged_df): 30143241\n","59 South Garo Hills\n","length(merged_df): 30579916\n","60 South West Garo Hills\n","length(merged_df): 30602928\n","61 South West Khasi Hills\n","length(merged_df): 31055221\n","62 West Garo Hills\n","length(merged_df): 31366797\n","63 West Khasi Hills\n","length(merged_df): 32639956\n","64 Aizawl\n","length(merged_df): 33683017\n","65 Champhai\n","length(merged_df): 34876756\n","66 Kolasib\n","length(merged_df): 35235014\n","67 Lawangtlai\n","length(merged_df): 36017069\n","68 Lunglei\n","length(merged_df): 37422177\n","69 Mamit\n","length(merged_df): 38303422\n","70 Saiha\n","length(merged_df): 39353050\n","71 Serchhip\n","length(merged_df): 39818787\n","72 Dimapur\n","length(merged_df): 39983662\n","73 Kiphire\n","length(merged_df): 40462734\n","74 Kohima\n","length(merged_df): 40960637\n","75 Longleng\n","length(merged_df): 41132686\n","76 Mokokchung\n","length(merged_df): 41676302\n","77 Mon\n","length(merged_df): 42316638\n","78 Peren\n","length(merged_df): 43022268\n","79 Phek\n","length(merged_df): 43711636\n","80 Tuensang\n","length(merged_df): 44510144\n","81 Wokha\n","length(merged_df): 45009024\n","82 Zunheboto\n","length(merged_df): 45589344\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 45594992\n","86 West Sikkim\n","length(merged_df): 45595116\n","87 Dhalai\n","length(merged_df): 46143129\n","88 Gomati\n","length(merged_df): 46453951\n","89 Khowai\n","length(merged_df): 46721002\n","90 North Tripura\n","length(merged_df): 47019475\n","91 Sipahijala\n","length(merged_df): 47148693\n","92 South Tripura\n","length(merged_df): 47417704\n","93 Unokoti\n","length(merged_df): 47531409\n","94 West Tripura\n","length(merged_df): 47655435\n","95 Alipurduar\n","length(merged_df): 48182964\n","96 Darjiling\n","length(merged_df): 48864414\n","97 Jalpaiguri\n","length(merged_df): 49084292\n","98 Koch Bihar\n","length(merged_df): 49297786\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: ba30440b-954b-43bb-814b-f9240309b01a\n","2019\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 478344\n","1 Changlang\n","length(merged_df): 2444511\n","2 Dibang Valley\n","length(merged_df): 3836445\n","3 East Kameng\n","length(merged_df): 5122433\n","4 East Siang\n","length(merged_df): 5540789\n","5 Kurung Kumey\n","length(merged_df): 6210350\n","6 Lohit\n","length(merged_df): 7154987\n","7 Longding\n","length(merged_df): 7518784\n","8 Lower Dibang Valley\n","length(merged_df): 9181092\n","9 Lower Subansiri\n","length(merged_df): 9544762\n","10 Namsai\n","length(merged_df): 9599147\n","11 Papum Pare\n","length(merged_df): 10071058\n","12 Tawang\n","13 Tirap\n","length(merged_df): 10651507\n","14 Upper Siang\n","15 Upper Subansiri\n","length(merged_df): 10996049\n","16 West Kameng\n","length(merged_df): 11911902\n","17 West Siang\n","length(merged_df): 12567368\n","18 Baksa\n","length(merged_df): 12741540\n","19 Barpeta\n","length(merged_df): 12814699\n","20 Bongaigaon\n","length(merged_df): 12865104\n","21 Cachar\n","length(merged_df): 13634666\n","22 Chirang\n","length(merged_df): 13737843\n","23 Darrang\n","length(merged_df): 13803079\n","24 Dhemaji\n","length(merged_df): 14005416\n","25 Dhubri\n","length(merged_df): 14086546\n","26 Dibrugarh\n","length(merged_df): 14327414\n","27 Dima Hasao\n","length(merged_df): 15403060\n","28 Goalpara\n","length(merged_df): 15456008\n","29 Golaghat\n","length(merged_df): 15577333\n","30 Hailakandi\n","length(merged_df): 15668824\n","31 Jorhat\n","length(merged_df): 15803737\n","32 Kamrup Metropolitan\n","length(merged_df): 15886696\n","33 Kamrup\n","length(merged_df): 16170410\n","34 Karbi Anglong\n","length(merged_df): 17867132\n","35 Karimganj\n","length(merged_df): 18041501\n","36 Kokrajhar\n","length(merged_df): 18451796\n","37 Lakhimpur\n","length(merged_df): 18617844\n","38 Morigaon\n","length(merged_df): 18681752\n","39 Nagaon\n","length(merged_df): 18889057\n","40 Nalbari\n","length(merged_df): 18936490\n","41 Sivasagar\n","length(merged_df): 19070646\n","42 Sonitpur\n","length(merged_df): 19487051\n","43 Tinsukia\n","length(merged_df): 20132264\n","44 Udalguri\n","length(merged_df): 20234159\n","45 Bishnupur\n","length(merged_df): 20256633\n","46 Chandel\n","length(merged_df): 21456762\n","47 Churachandpur\n","length(merged_df): 22912733\n","48 Imphal East\n","length(merged_df): 22977450\n","49 Imphal West\n","length(merged_df): 23010433\n","50 Senapati\n","length(merged_df): 24073080\n","51 Tamenglong\n","length(merged_df): 25665819\n","52 Thoubal\n","length(merged_df): 25718095\n","53 Ukhrul\n","length(merged_df): 27128826\n","54 East Garo Hills\n","length(merged_df): 27513780\n","55 East Khasi Hills\n","length(merged_df): 28529934\n","56 Jaintia Hills\n","length(merged_df): 29485284\n","57 North Garo Hills\n","length(merged_df): 29544664\n","58 Ri Bhoi\n","length(merged_df): 30143241\n","59 South Garo Hills\n","length(merged_df): 30579916\n","60 South West Garo Hills\n","length(merged_df): 30602928\n","61 South West Khasi Hills\n","length(merged_df): 31055221\n","62 West Garo Hills\n","length(merged_df): 31366797\n","63 West Khasi Hills\n","length(merged_df): 32639956\n","64 Aizawl\n","length(merged_df): 33683017\n","65 Champhai\n","length(merged_df): 34876756\n","66 Kolasib\n","length(merged_df): 35235014\n","67 Lawangtlai\n","length(merged_df): 36017069\n","68 Lunglei\n","length(merged_df): 37422177\n","69 Mamit\n","length(merged_df): 38303422\n","70 Saiha\n","length(merged_df): 39353050\n","71 Serchhip\n","length(merged_df): 39818787\n","72 Dimapur\n","length(merged_df): 39983662\n","73 Kiphire\n","length(merged_df): 40462734\n","74 Kohima\n","length(merged_df): 40960637\n","75 Longleng\n","length(merged_df): 41132686\n","76 Mokokchung\n","length(merged_df): 41676302\n","77 Mon\n","length(merged_df): 42316638\n","78 Peren\n","length(merged_df): 43022268\n","79 Phek\n","length(merged_df): 43711636\n","80 Tuensang\n","length(merged_df): 44510144\n","81 Wokha\n","length(merged_df): 45009024\n","82 Zunheboto\n","length(merged_df): 45589344\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 45594992\n","86 West Sikkim\n","length(merged_df): 45595116\n","87 Dhalai\n","length(merged_df): 46143129\n","88 Gomati\n","length(merged_df): 46453951\n","89 Khowai\n","length(merged_df): 46721002\n","90 North Tripura\n","length(merged_df): 47019475\n","91 Sipahijala\n","length(merged_df): 47148693\n","92 South Tripura\n","length(merged_df): 47417704\n","93 Unokoti\n","length(merged_df): 47531409\n","94 West Tripura\n","length(merged_df): 47655435\n","95 Alipurduar\n","length(merged_df): 48182964\n","96 Darjiling\n","length(merged_df): 49123864\n","97 Jalpaiguri\n","length(merged_df): 49343742\n","98 Koch Bihar\n","length(merged_df): 49557236\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: 5533b929-ee31-49af-88d3-d3604cacf2fc\n","2020\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1330356\n","1 Changlang\n","length(merged_df): 3381834\n","2 Dibang Valley\n","length(merged_df): 4485987\n","3 East Kameng\n","length(merged_df): 5946992\n","4 East Siang\n","length(merged_df): 6841619\n","5 Kurung Kumey\n","length(merged_df): 7374955\n","6 Lohit\n","length(merged_df): 8623493\n","7 Longding\n","length(merged_df): 9122254\n","8 Lower Dibang Valley\n","length(merged_df): 10832333\n","9 Lower Subansiri\n","length(merged_df): 11508961\n","10 Namsai\n","length(merged_df): 11572983\n","11 Papum Pare\n","length(merged_df): 11964446\n","12 Tawang\n","13 Tirap\n","length(merged_df): 12482533\n","14 Upper Siang\n","length(merged_df): 12986325\n","15 Upper Subansiri\n","length(merged_df): 13569984\n","16 West Kameng\n","length(merged_df): 14629622\n","17 West Siang\n","length(merged_df): 15473209\n","18 Baksa\n","length(merged_df): 15724686\n","19 Barpeta\n","length(merged_df): 15796660\n","20 Bongaigaon\n","length(merged_df): 15864834\n","21 Cachar\n","length(merged_df): 16954573\n","22 Chirang\n","length(merged_df): 17208861\n","23 Darrang\n","length(merged_df): 17261514\n","24 Dhemaji\n","length(merged_df): 17476816\n","25 Dhubri\n","length(merged_df): 17557170\n","26 Dibrugarh\n","length(merged_df): 17762589\n","27 Dima Hasao\n","length(merged_df): 19560412\n","28 Goalpara\n","length(merged_df): 19608366\n","29 Golaghat\n","length(merged_df): 19763513\n","30 Hailakandi\n","length(merged_df): 19944247\n","31 Jorhat\n","length(merged_df): 20226737\n","32 Kamrup Metropolitan\n","length(merged_df): 20346460\n","33 Kamrup\n","length(merged_df): 20894960\n","34 Karbi Anglong\n","length(merged_df): 24409800\n","35 Karimganj\n","length(merged_df): 24720227\n","36 Kokrajhar\n","length(merged_df): 25249349\n","37 Lakhimpur\n","length(merged_df): 25394037\n","38 Morigaon\n","length(merged_df): 25464656\n","39 Nagaon\n","length(merged_df): 25730146\n","40 Nalbari\n","length(merged_df): 25783561\n","41 Sivasagar\n","length(merged_df): 25989411\n","42 Sonitpur\n","length(merged_df): 26322156\n","43 Tinsukia\n","length(merged_df): 26849930\n","44 Udalguri\n","length(merged_df): 26974580\n","45 Bishnupur\n","length(merged_df): 27012133\n","46 Chandel\n","length(merged_df): 29576779\n","47 Churachandpur\n","length(merged_df): 34538630\n","48 Imphal East\n","length(merged_df): 34666561\n","49 Imphal West\n","length(merged_df): 34724393\n","50 Senapati\n","length(merged_df): 36660245\n","51 Tamenglong\n","length(merged_df): 39328598\n","52 Thoubal\n","length(merged_df): 39442977\n","53 Ukhrul\n","length(merged_df): 42235241\n","54 East Garo Hills\n","length(merged_df): 42628773\n","55 East Khasi Hills\n","length(merged_df): 43646393\n","56 Jaintia Hills\n","length(merged_df): 45028991\n","57 North Garo Hills\n","length(merged_df): 45074050\n","58 Ri Bhoi\n","length(merged_df): 45875336\n","59 South Garo Hills\n","length(merged_df): 46412583\n","60 South West Garo Hills\n","length(merged_df): 46429328\n","61 South West Khasi Hills\n","length(merged_df): 46933540\n","62 West Garo Hills\n","length(merged_df): 47220989\n","63 West Khasi Hills\n","length(merged_df): 48539439\n","64 Aizawl\n","length(merged_df): 50741733\n","Saving merged_corrections_0.csv\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n","WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n","Ingestion task started: 719ee0b0-b6f9-4f82-a567-e1de6a176ab3\n","65 Champhai\n","length(merged_df): 3634458\n","66 Kolasib\n","length(merged_df): 4210001\n","67 Lawangtlai\n","length(merged_df): 5163272\n","68 Lunglei\n","length(merged_df): 7603186\n","69 Mamit\n","length(merged_df): 8935328\n","70 Saiha\n","length(merged_df): 10161350\n","71 Serchhip\n","length(merged_df): 10989166\n","72 Dimapur\n","length(merged_df): 11231066\n","73 Kiphire\n","length(merged_df): 11796555\n","74 Kohima\n","length(merged_df): 12588621\n","75 Longleng\n","length(merged_df): 12837079\n","76 Mokokchung\n","length(merged_df): 13936156\n","77 Mon\n","length(merged_df): 14914721\n","78 Peren\n","length(merged_df): 15919623\n","79 Phek\n","length(merged_df): 17010418\n","80 Tuensang\n","length(merged_df): 18037322\n","81 Wokha\n","length(merged_df): 19164284\n","82 Zunheboto\n","length(merged_df): 20101345\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 20114100\n","86 West Sikkim\n","length(merged_df): 20114504\n","87 Dhalai\n","length(merged_df): 20852781\n","88 Gomati\n","length(merged_df): 21287454\n","89 Khowai\n","length(merged_df): 21705560\n","90 North Tripura\n","length(merged_df): 22057720\n","91 Sipahijala\n","length(merged_df): 22183921\n","92 South Tripura\n","length(merged_df): 22465453\n","93 Unokoti\n","length(merged_df): 22613908\n","94 West Tripura\n","length(merged_df): 22735736\n","95 Alipurduar\n","length(merged_df): 23223854\n","96 Darjiling\n","length(merged_df): 24393328\n","97 Jalpaiguri\n","length(merged_df): 24698003\n","98 Koch Bihar\n","length(merged_df): 24915683\n","Saving merged_corrections_1.csv\n","Upload complete.\n","Ingestion task started: b4ac2c6e-32aa-44a8-a4a6-dbd936a44ca4\n","2021\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1330356\n","1 Changlang\n","length(merged_df): 3381834\n","2 Dibang Valley\n","length(merged_df): 4485987\n","3 East Kameng\n","length(merged_df): 5946992\n","4 East Siang\n","length(merged_df): 6841619\n","5 Kurung Kumey\n","length(merged_df): 7374955\n","6 Lohit\n","length(merged_df): 8623493\n","7 Longding\n","length(merged_df): 9122254\n","8 Lower Dibang Valley\n","length(merged_df): 10832333\n","9 Lower Subansiri\n","length(merged_df): 11508961\n","10 Namsai\n","length(merged_df): 11572983\n","11 Papum Pare\n","length(merged_df): 11964446\n","12 Tawang\n","13 Tirap\n","length(merged_df): 12482533\n","14 Upper Siang\n","length(merged_df): 12986325\n","15 Upper Subansiri\n","length(merged_df): 13569984\n","16 West Kameng\n","length(merged_df): 14629622\n","17 West Siang\n","length(merged_df): 15473209\n","18 Baksa\n","length(merged_df): 15724686\n","19 Barpeta\n","length(merged_df): 15796660\n","20 Bongaigaon\n","length(merged_df): 15864834\n","21 Cachar\n","length(merged_df): 16954573\n","22 Chirang\n","length(merged_df): 17208861\n","23 Darrang\n","length(merged_df): 17261514\n","24 Dhemaji\n","length(merged_df): 17476816\n","25 Dhubri\n","length(merged_df): 17557170\n","26 Dibrugarh\n","length(merged_df): 17762589\n","27 Dima Hasao\n","length(merged_df): 19560412\n","28 Goalpara\n","length(merged_df): 19608366\n","29 Golaghat\n","length(merged_df): 19763513\n","30 Hailakandi\n","length(merged_df): 19944247\n","31 Jorhat\n","length(merged_df): 20226737\n","32 Kamrup Metropolitan\n","length(merged_df): 20346460\n","33 Kamrup\n","length(merged_df): 20894960\n","34 Karbi Anglong\n","35 Karimganj\n","length(merged_df): 21205387\n","36 Kokrajhar\n","length(merged_df): 21734509\n","37 Lakhimpur\n","length(merged_df): 21879197\n","38 Morigaon\n","length(merged_df): 21949816\n","39 Nagaon\n","length(merged_df): 22215306\n","40 Nalbari\n","length(merged_df): 22268721\n","41 Sivasagar\n","length(merged_df): 22474571\n","42 Sonitpur\n","length(merged_df): 22807316\n","43 Tinsukia\n","length(merged_df): 23335090\n","44 Udalguri\n","length(merged_df): 23459740\n","45 Bishnupur\n","length(merged_df): 23497293\n","46 Chandel\n","length(merged_df): 26061939\n","47 Churachandpur\n","length(merged_df): 31023790\n","48 Imphal East\n","length(merged_df): 31151721\n","49 Imphal West\n","length(merged_df): 31209553\n","50 Senapati\n","length(merged_df): 33145405\n","51 Tamenglong\n","length(merged_df): 35813758\n","52 Thoubal\n","length(merged_df): 35928137\n","53 Ukhrul\n","length(merged_df): 38720401\n","54 East Garo Hills\n","length(merged_df): 39113933\n","55 East Khasi Hills\n","length(merged_df): 40131553\n","56 Jaintia Hills\n","length(merged_df): 41514151\n","57 North Garo Hills\n","length(merged_df): 41559210\n","58 Ri Bhoi\n","length(merged_df): 42360496\n","59 South Garo Hills\n","length(merged_df): 42897743\n","60 South West Garo Hills\n","length(merged_df): 42914488\n","61 South West Khasi Hills\n","length(merged_df): 43418700\n","62 West Garo Hills\n","length(merged_df): 43706149\n","63 West Khasi Hills\n","length(merged_df): 45024599\n","64 Aizawl\n","length(merged_df): 47226893\n","65 Champhai\n","length(merged_df): 50861351\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: 6d28ee07-5504-47db-bef6-41b776609a75\n","66 Kolasib\n","length(merged_df): 575543\n","67 Lawangtlai\n","length(merged_df): 1528814\n","68 Lunglei\n","length(merged_df): 3968728\n","69 Mamit\n","length(merged_df): 5300870\n","70 Saiha\n","length(merged_df): 6526892\n","71 Serchhip\n","length(merged_df): 7354708\n","72 Dimapur\n","length(merged_df): 7596608\n","73 Kiphire\n","length(merged_df): 8162097\n","74 Kohima\n","length(merged_df): 8954163\n","75 Longleng\n","length(merged_df): 9202621\n","76 Mokokchung\n","length(merged_df): 10301698\n","77 Mon\n","length(merged_df): 11280263\n","78 Peren\n","length(merged_df): 12285165\n","79 Phek\n","length(merged_df): 13375960\n","80 Tuensang\n","length(merged_df): 14402864\n","81 Wokha\n","length(merged_df): 15529826\n","82 Zunheboto\n","length(merged_df): 16466887\n","83 East Sikkim\n","84 North Sikkim\n","85 South Sikkim\n","length(merged_df): 16479642\n","86 West Sikkim\n","length(merged_df): 16480046\n","87 Dhalai\n","length(merged_df): 17218323\n","88 Gomati\n","length(merged_df): 17652996\n","89 Khowai\n","length(merged_df): 18071102\n","90 North Tripura\n","length(merged_df): 18423262\n","91 Sipahijala\n","length(merged_df): 18549463\n","92 South Tripura\n","length(merged_df): 18830995\n","93 Unokoti\n","length(merged_df): 18979450\n","94 West Tripura\n","length(merged_df): 19101278\n","95 Alipurduar\n","length(merged_df): 19589396\n","96 Darjiling\n","length(merged_df): 20711886\n","97 Jalpaiguri\n","length(merged_df): 21016561\n","98 Koch Bihar\n","length(merged_df): 21234241\n","Saving merged_corrections_1.csv\n","Upload complete.\n","Ingestion task started: 07473749-9783-4784-9b75-52ef5c1bd445\n","2022\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1848049\n","1 Changlang\n","length(merged_df): 4620455\n","2 Dibang Valley\n","length(merged_df): 7067896\n","3 East Kameng\n","length(merged_df): 9238528\n","4 East Siang\n","length(merged_df): 10273774\n","5 Kurung Kumey\n","length(merged_df): 12139221\n","6 Lohit\n","length(merged_df): 13411891\n","7 Longding\n","length(merged_df): 13957060\n","8 Lower Dibang Valley\n","length(merged_df): 15800235\n","9 Lower Subansiri\n","length(merged_df): 17537049\n","10 Namsai\n","length(merged_df): 17592906\n","11 Papum Pare\n","length(merged_df): 19454295\n","12 Tawang\n","length(merged_df): 20259605\n","13 Tirap\n","length(merged_df): 20849587\n","14 Upper Siang\n","length(merged_df): 25013318\n","15 Upper Subansiri\n","length(merged_df): 26200628\n","16 West Kameng\n","length(merged_df): 29142728\n","17 West Siang\n","length(merged_df): 31692865\n","18 Baksa\n","length(merged_df): 31844938\n","19 Barpeta\n","length(merged_df): 31948301\n","20 Bongaigaon\n","length(merged_df): 32020012\n","21 Cachar\n","length(merged_df): 33015996\n","22 Chirang\n","length(merged_df): 33110098\n","23 Darrang\n","length(merged_df): 33184191\n","24 Dhemaji\n","length(merged_df): 33459882\n","25 Dhubri\n","length(merged_df): 33584733\n","26 Dibrugarh\n","length(merged_df): 33854284\n","27 Dima Hasao\n","length(merged_df): 35107231\n","28 Goalpara\n","length(merged_df): 35181002\n","29 Golaghat\n","length(merged_df): 35345503\n","30 Hailakandi\n","length(merged_df): 35574708\n","31 Jorhat\n","length(merged_df): 35772826\n","32 Kamrup Metropolitan\n","length(merged_df): 35930324\n","33 Kamrup\n","length(merged_df): 36346509\n","34 Karbi Anglong\n","length(merged_df): 38769946\n","35 Karimganj\n","length(merged_df): 39093925\n","36 Kokrajhar\n","length(merged_df): 39620058\n","37 Lakhimpur\n","length(merged_df): 39918127\n","38 Morigaon\n","length(merged_df): 40003240\n","39 Nagaon\n","length(merged_df): 40244487\n","40 Nalbari\n","length(merged_df): 40300500\n","41 Sivasagar\n","length(merged_df): 40499034\n","42 Sonitpur\n","length(merged_df): 40866091\n","43 Tinsukia\n","length(merged_df): 41475492\n","44 Udalguri\n","length(merged_df): 41567105\n","45 Bishnupur\n","length(merged_df): 41604433\n","46 Chandel\n","length(merged_df): 42744913\n","47 Churachandpur\n","length(merged_df): 43872322\n","48 Imphal East\n","length(merged_df): 43964012\n","49 Imphal West\n","length(merged_df): 44013228\n","50 Senapati\n","length(merged_df): 45464578\n","51 Tamenglong\n","length(merged_df): 47644037\n","52 Thoubal\n","length(merged_df): 47707540\n","53 Ukhrul\n","length(merged_df): 50022798\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: b7a98fca-05eb-42b0-85c3-de59164d0658\n","54 East Garo Hills\n","length(merged_df): 357834\n","55 East Khasi Hills\n","length(merged_df): 1398969\n","56 Jaintia Hills\n","length(merged_df): 2608139\n","57 North Garo Hills\n","length(merged_df): 2680692\n","58 Ri Bhoi\n","length(merged_df): 3610989\n","59 South Garo Hills\n","length(merged_df): 4039361\n","60 South West Garo Hills\n","length(merged_df): 4071718\n","61 South West Khasi Hills\n","length(merged_df): 4490566\n","62 West Garo Hills\n","length(merged_df): 4804823\n","63 West Khasi Hills\n","length(merged_df): 6123187\n","64 Aizawl\n","length(merged_df): 7805567\n","65 Champhai\n","length(merged_df): 8604961\n","66 Kolasib\n","length(merged_df): 9126897\n","67 Lawangtlai\n","length(merged_df): 9927838\n","68 Lunglei\n","length(merged_df): 11885061\n","69 Mamit\n","length(merged_df): 13041068\n","70 Saiha\n","length(merged_df): 13994266\n","71 Serchhip\n","length(merged_df): 14664286\n","72 Dimapur\n","length(merged_df): 14839149\n","73 Kiphire\n","length(merged_df): 15397224\n","74 Kohima\n","length(merged_df): 16046210\n","75 Longleng\n","length(merged_df): 16286450\n","76 Mokokchung\n","length(merged_df): 17059850\n","77 Mon\n","length(merged_df): 18007009\n","78 Peren\n","length(merged_df): 18737431\n","79 Phek\n","length(merged_df): 19621866\n","80 Tuensang\n","length(merged_df): 20699273\n","81 Wokha\n","length(merged_df): 21418859\n","82 Zunheboto\n","length(merged_df): 22249456\n","83 East Sikkim\n","84 North Sikkim\n","length(merged_df): 22255108\n","85 South Sikkim\n","length(merged_df): 22263543\n","86 West Sikkim\n","length(merged_df): 22433494\n","87 Dhalai\n","length(merged_df): 23093845\n","88 Gomati\n","length(merged_df): 23380456\n","89 Khowai\n","length(merged_df): 23629337\n","90 North Tripura\n","length(merged_df): 24031752\n","91 Sipahijala\n","length(merged_df): 24177507\n","92 South Tripura\n","length(merged_df): 24426953\n","93 Unokoti\n","length(merged_df): 24600290\n","94 West Tripura\n","length(merged_df): 24730615\n","95 Alipurduar\n","length(merged_df): 25183066\n","96 Darjiling\n","length(merged_df): 26125332\n","97 Jalpaiguri\n","length(merged_df): 26444567\n","98 Koch Bihar\n","length(merged_df): 26737087\n","Saving merged_corrections_1.csv\n"]},{"output_type":"stream","name":"stderr","text":["WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n","WARNING:google_auth_httplib2:httplib2 transport does not support per-request timeout. Set the timeout when constructing the httplib2.Http instance.\n"]},{"output_type":"stream","name":"stdout","text":["Upload complete.\n","Ingestion task started: 5a592b45-4bd1-4fef-9018-cd77a083c6de\n","2023\n","length(dist_list): 99\n","0 Anjaw\n","length(merged_df): 1848049\n","1 Changlang\n","length(merged_df): 4620455\n","2 Dibang Valley\n","length(merged_df): 7067896\n","3 East Kameng\n","length(merged_df): 9238528\n","4 East Siang\n","length(merged_df): 10273774\n","5 Kurung Kumey\n","length(merged_df): 12139221\n","6 Lohit\n","length(merged_df): 13411891\n","7 Longding\n","length(merged_df): 13957060\n","8 Lower Dibang Valley\n","length(merged_df): 15800235\n","9 Lower Subansiri\n","length(merged_df): 17537049\n","10 Namsai\n","length(merged_df): 17592906\n","11 Papum Pare\n","length(merged_df): 19454295\n","12 Tawang\n","length(merged_df): 20259605\n","13 Tirap\n","length(merged_df): 20849587\n","14 Upper Siang\n","length(merged_df): 25013318\n","15 Upper Subansiri\n","length(merged_df): 26200628\n","16 West Kameng\n","length(merged_df): 29142728\n","17 West Siang\n","length(merged_df): 31692865\n","18 Baksa\n","length(merged_df): 31844938\n","19 Barpeta\n","length(merged_df): 31948301\n","20 Bongaigaon\n","length(merged_df): 32020012\n","21 Cachar\n","length(merged_df): 33015996\n","22 Chirang\n","length(merged_df): 33110098\n","23 Darrang\n","length(merged_df): 33184191\n","24 Dhemaji\n","length(merged_df): 33459882\n","25 Dhubri\n","length(merged_df): 33584733\n","26 Dibrugarh\n","length(merged_df): 33854284\n","27 Dima Hasao\n","length(merged_df): 35107231\n","28 Goalpara\n","length(merged_df): 35181002\n","29 Golaghat\n","length(merged_df): 35345503\n","30 Hailakandi\n","length(merged_df): 35574708\n","31 Jorhat\n","length(merged_df): 35772826\n","32 Kamrup Metropolitan\n","length(merged_df): 35930324\n","33 Kamrup\n","length(merged_df): 36346509\n","34 Karbi Anglong\n","length(merged_df): 38769946\n","35 Karimganj\n","length(merged_df): 39093925\n","36 Kokrajhar\n","length(merged_df): 39620058\n","37 Lakhimpur\n","length(merged_df): 39918127\n","38 Morigaon\n","length(merged_df): 40003240\n","39 Nagaon\n","length(merged_df): 40244487\n","40 Nalbari\n","length(merged_df): 40300500\n","41 Sivasagar\n","length(merged_df): 40499034\n","42 Sonitpur\n","length(merged_df): 40866091\n","43 Tinsukia\n","length(merged_df): 41475492\n","44 Udalguri\n","length(merged_df): 41567105\n","45 Bishnupur\n","length(merged_df): 41604433\n","46 Chandel\n","length(merged_df): 42744913\n","47 Churachandpur\n","length(merged_df): 43872322\n","48 Imphal East\n","length(merged_df): 43964012\n","49 Imphal West\n","length(merged_df): 44013228\n","50 Senapati\n","length(merged_df): 45464578\n","51 Tamenglong\n","length(merged_df): 47644037\n","52 Thoubal\n","length(merged_df): 47707540\n","53 Ukhrul\n","length(merged_df): 50022798\n","Saving merged_corrections_0.csv\n","Upload complete.\n","Ingestion task started: bf24e7ae-59e0-47e6-9e17-ceda97654ae2\n","54 East Garo Hills\n","length(merged_df): 357834\n","55 East Khasi Hills\n","length(merged_df): 1398969\n","56 Jaintia Hills\n","length(merged_df): 2608139\n","57 North Garo Hills\n","length(merged_df): 2680692\n","58 Ri Bhoi\n","length(merged_df): 3610989\n","59 South Garo Hills\n","length(merged_df): 4039361\n","60 South West Garo Hills\n","length(merged_df): 4071718\n","61 South West Khasi Hills\n","length(merged_df): 4490566\n","62 West Garo Hills\n","length(merged_df): 4804823\n","63 West Khasi Hills\n","length(merged_df): 6123187\n","64 Aizawl\n","length(merged_df): 7805567\n","65 Champhai\n","length(merged_df): 8604961\n","66 Kolasib\n","length(merged_df): 9126897\n","67 Lawangtlai\n","length(merged_df): 9927838\n","68 Lunglei\n","length(merged_df): 11885061\n","69 Mamit\n","length(merged_df): 13041068\n","70 Saiha\n","length(merged_df): 13994266\n","71 Serchhip\n","length(merged_df): 14664286\n","72 Dimapur\n","length(merged_df): 14839149\n","73 Kiphire\n","length(merged_df): 15397224\n","74 Kohima\n","length(merged_df): 16046210\n","75 Longleng\n","length(merged_df): 16286450\n","76 Mokokchung\n","length(merged_df): 17059850\n","77 Mon\n","length(merged_df): 18007009\n","78 Peren\n","length(merged_df): 18737431\n","79 Phek\n","length(merged_df): 19621866\n","80 Tuensang\n","length(merged_df): 20699273\n","81 Wokha\n","length(merged_df): 21418859\n","82 Zunheboto\n","length(merged_df): 22249456\n","83 East Sikkim\n","84 North Sikkim\n","length(merged_df): 22255108\n","85 South Sikkim\n","length(merged_df): 22263543\n","86 West Sikkim\n","length(merged_df): 22433494\n","87 Dhalai\n","length(merged_df): 23093845\n","88 Gomati\n","length(merged_df): 23380456\n","89 Khowai\n","length(merged_df): 23629337\n","90 North Tripura\n","length(merged_df): 24031752\n","91 Sipahijala\n","length(merged_df): 24177507\n","92 South Tripura\n","length(merged_df): 24426953\n","93 Unokoti\n","length(merged_df): 24600290\n","94 West Tripura\n","length(merged_df): 24730615\n","95 Alipurduar\n","length(merged_df): 25183066\n","96 Darjiling\n","length(merged_df): 26125332\n","97 Jalpaiguri\n","length(merged_df): 26444567\n","98 Koch Bihar\n","length(merged_df): 26737087\n","Saving merged_corrections_1.csv\n","Upload complete.\n","Ingestion task started: 7643f5cd-7037-483d-97cd-4712f77c7924\n"]}],"source":["# CH\n","\n","# Function to convert string representation of list to an actual list\n","def convert_to_list(string):\n"," return ast.literal_eval(string)\n","\n","for agroclimatic_zone in acz_list:\n"," print(agroclimatic_zone)\n"," for year in years:\n"," print(year)\n"," df = pd.read_csv('drive/MyDrive/TreeHealth/district_to_agroclimaticZone_mapping.csv')\n"," df['IntersectingZones'] = df['IntersectingZones'].apply(convert_to_list)\n"," district_mapping_df = df[df['AgroclimaticZone'] == agroclimatic_zone][['District', 'IntersectingZones']]\n"," dist_list = []\n"," for ind in district_mapping_df.index:\n"," district = district_mapping_df.loc[ind, 'District']\n"," zones = district_mapping_df['IntersectingZones'][ind]\n"," dist_list.append(district)\n","\n"," print(f'length(dist_list): {len(dist_list)}')\n","\n"," correction_num = 0\n"," j = 0\n"," merged_df = pd.DataFrame()\n","\n","\n"," for district in dist_list:\n"," print(j, district)\n"," j += 1\n","\n"," try:\n"," df = pd.read_csv(f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/{district}/{year}/result_chm_corrections.csv')\n"," except:\n"," continue\n","\n"," merged_df = pd.concat([merged_df, df], ignore_index=True)\n"," print(f'length(merged_df): {len(merged_df)}')\n"," drive_path = f'drive/MyDrive/TreeHealth/{agroclimatic_zone}/corrections_chm_{year}_{correction_num}.csv'\n","\n"," if len(merged_df) > 50000000: # Adjust based on need, if script crashes because of RAM exhaustion, default 70000000\n"," cols = merged_df.columns\n"," for i in range(1, len(cols)):\n"," merged_df[cols[i]] = merged_df[cols[i]].astype('Int64')\n"," print(f'Saving merged_corrections_{correction_num}.csv')\n"," merged_df.to_csv(drive_path, index=False)\n","\n"," file_name = f\"corrections_ch_{year}_result_{correction_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n","\n"," del(merged_df)\n"," correction_num += 1\n"," merged_df = pd.DataFrame()\n","\n"," if len(merged_df) > 0:\n"," cols = merged_df.columns\n"," for i in range(1, len(cols)):\n"," merged_df[cols[i]] = merged_df[cols[i]].astype('Int64')\n"," print(f'Saving merged_corrections_{correction_num}.csv')\n"," merged_df.to_csv(drive_path, index=False)\n","\n"," file_name = f\"corrections_ch_{year}_result_{correction_num}_{agroclimaticZone_acronym_dict[agroclimatic_zone]}\"\n"," upload_file_to_gcs(drive_path, file_name)\n"," export_to_gee(file_name)\n","\n"," del(merged_df)"]}],"metadata":{"colab":{"collapsed_sections":["Ah69USIF_hSV"],"provenance":[{"file_id":"1m0Q6Ic94NeVThpU_ZU0OydAeqlp6zWoe","timestamp":1759050819148}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/utilities/scripts/tree_health/gee_scripts/ccd_change.js b/utilities/scripts/tree_health/gee_scripts/ccd_change.js new file mode 100644 index 00000000..33e60087 --- /dev/null +++ b/utilities/scripts/tree_health/gee_scripts/ccd_change.js @@ -0,0 +1,76 @@ +// This script is for doing a modal analysis while analysing forest cover change over the years between any 2 given years. +// It also gives the change quantification + +// Set year_1 and year_2. These are the 2 years between which comparison has to be made +// Note that year_2 must be > year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2023; + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +var project_path = 'projects/corestack-trees/assets/tree_characteristics'; + +var initial_image = ee.Image(project_path+'/modal_ccd_' + year_1 + '/' + agroclimaticZoneAcronymDict[acz]); +var final_image = ee.Image(project_path+'/modal_ccd_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz]); + +var change = initial_image.addBands(final_image); +change = change.unmask(-9999); + +print(change); + +// 3 denotes missing data +change = change.expression( + "((b('cc')==0) and (b('cc_1')==0)) ? (0)"+ + ":((b('cc')==1) and (b('cc_1')==1)) ? (0)"+ + ":((b('cc')==0) and (b('cc_1')==1)) ? (1)"+ + ":((b('cc')==1) and (b('cc_1')==0)) ? (-1)"+ + ":((b('cc')==-9999) and (b('cc_1')!=-9999)) ? (2)"+ + ":((b('cc')!=-9999) and (b('cc_1')==-9999)) ? (-2)"+ + ":((b('cc')==2) or (b('cc_1')==2)) ? (3)"+ + ":-9999" + ) + .clip(aoi); +change = change.updateMask(change.neq(-9999)); +// var palette = ['red', 'orange', 'white', 'lightgreen', 'darkgreen', 'black']; +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer'); + +// Export CCD change image +Export.image.toAsset({ + image: change, + description: 'ccd_change_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/ccd_change_' + year_1 + '_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); \ No newline at end of file diff --git a/utilities/scripts/tree_health/gee_scripts/ch_change.js b/utilities/scripts/tree_health/gee_scripts/ch_change.js new file mode 100644 index 00000000..ec746fd3 --- /dev/null +++ b/utilities/scripts/tree_health/gee_scripts/ch_change.js @@ -0,0 +1,74 @@ +// This script is for doing a modal analysis while analysing forest cover change over the years between any 2 given years. +// It also gives the change quantification + +// Set year_1 and year_2. These are the 2 years between which comparison has to be made +// Note that year_2 must be > year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2023; + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +var project_path = 'projects/corestack1-dev-alpha/assets/tree_characteristics'; + +var initial_image = ee.Image(project_path+'/modal_ch_' + year_1 + '/' + agroclimaticZoneAcronymDict[acz]); +var final_image = ee.Image(project_path+'/modal_ch_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz]); + +var change = initial_image.addBands(final_image); +change = change.unmask(-9999); +// 3 denotes missing data +change = change.expression( + "((b('ch_class') == -9999) and (b('ch_class_1') != -9999)) ? (2)" + + ": ((b('ch_class') != -9999) and (b('ch_class_1') == -9999)) ? (-2)"+ + ": ((b('ch_class') == 8) or (b('ch_class_1') == 8)) ? (3)" + + ": ((b('ch_class') == b('ch_class_1')) and (b('ch_class') != -9999)) ? (0)" + + ": ((b('ch_class') < b('ch_class_1')) and (b('ch_class') != -9999) and ((b('ch_class') <= 1) or (b('ch_class') >= 4))) ? (1)" + + ": ((b('ch_class') > b('ch_class_1')) and (b('ch_class_1') != -9999)) ? (-1)" + + ": ((b('ch_class') == 2) and ((b('ch_class_1') == 3) or (b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": ((b('ch_class') == 3) and ((b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": (((b('ch_class') == 2) or (b('ch_class') == 3)) and ((b('ch_class_1') == 4) or (b('ch_class_1') == 5))) ? (-1)"+ + ":-9999" +).clip(aoi); +change = change.updateMask(change.neq(-9999)); +// var palette = ['red', 'orange', 'white', 'lightgreen', 'green', 'black']; +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer ch'); + +// Export CH change image +Export.image.toAsset({ + image: change, + description: 'ch_change_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/ch_change_' + year_1 + '_' + year_2 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); diff --git a/utilities/scripts/tree_health/gee_scripts/fc_to_image.js b/utilities/scripts/tree_health/gee_scripts/fc_to_image.js new file mode 100644 index 00000000..629ce8ec --- /dev/null +++ b/utilities/scripts/tree_health/gee_scripts/fc_to_image.js @@ -0,0 +1,157 @@ +// Using Compiled Result from upload asset script +// Both CCD and CH images can be generated using this script. Comment out CCD if you +// are generating CH image and vice versa + +// TODO: Set total_files according to the number of result files for a particular ACZ +// in a particular year +var total_files = 5; + +// TODO: Mention the year for which you want to run this script for. +var year = '2018'; + +// TODO: Uncomment the acz for which you want to run this script for. +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +var res_list = []; +for (var i=0; i modify these + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); +} + + + +// ch + +var dist_list_dict = {'EPAHR': ['Baloda Bazar', 'Balod', 'BalrampurC', 'Bastar', 'Bemetara', 'BijapurC', 'BilaspurC', 'Dantewada', 'Dhamtari', 'Durg', 'Gariaband', 'Janjgir-Champa', 'Jashpur', 'Kabeerdham', 'Kondagaon', 'Korba', 'Koriya', 'Mahasamund', 'Mungeli', 'Narayanpur', 'Raigarh', 'Raipur', 'Rajnandgaon', 'Sukma', 'Surajpur', 'Surguja', 'Uttar Bastar Kanker', 'Bokaro', 'Chatra', 'Deoghar', 'Dhanbad', 'Dumka', 'Garhwa', 'Giridih', 'Gumla', 'Hazaribagh', 'Jamtara', 'Khunti', 'Kodarma', 'Latehar', 'Lohardaga', 'Pakur', 'Palamu', 'Pashchimi Singhbhum', 'Purbi Singhbhum', 'Ramgarh', 'Ranchi', 'Saraikela-kharsawan', 'Simdega', 'Anuppur', 'Balaghat', 'Shahdol', 'Sidhi', 'Singrauli', 'Umaria', 'Bhandara', 'Chandrapur', 'Garhchiroli', 'Gondiya', 'Anugul', 'Balangir', 'Bargarh', 'Bauda', 'Debagarh', 'Dhenkanal', 'Jharsuguda', 'Kalahandi', 'Kandhamal', 'Kendujhar', 'Koraput', 'Malkangiri', 'Mayurbhanj', 'Nabarangapur', 'Nuapada', 'Rayagada', 'Sambalpur', 'Subarnapur', 'Sundargarh', 'Puruliya'], + 'CPAHR': ['Ashoknagar', 'Betul', 'Bhind', 'Bhopal', 'Chhatarpur', 'Chhindwara', 'Damoh', 'Datia', 'Dindori', 'Guna', 'Gwalior', 'Harda', 'Hoshangabad', 'Jabalpur', 'Katni', 'Mandla', 'Morena', 'Narsimhapur', 'Panna', 'Raisen', 'Rajgarh', 'Rewa', 'Sagar', 'Satna', 'Sehore', 'Seoni', 'Sheopur', 'Shivpuri', 'Tikamgarh', 'Vidisha', 'Ajmer', 'Alwar', 'Banswara', 'Baran', 'Bharatpur', 'Bhilwara', 'Bundi', 'Chittaurgarh', 'Dausa', 'Dhaulpur', 'Dungarpur', 'Jaipur', 'Karauli', 'Kota', 'Pali', 'Pratapgarhraj', 'Rajsamand', 'Sawai Madhopur', 'Sirohi', 'Tonk', 'Udaipur', 'Banda', 'Chitrakoot', 'Hamirpurup', 'Jalaun', 'Jhansi', 'Lalitpur', 'Mahoba'], + 'SPAHR': ['Anantapur', 'Chittoor', 'Kurnool', 'Y.S.R.', 'Bagalkot', 'Bangalore Rural', 'Bangalore', 'Belgaum', 'Bellary', 'Bidar', 'Bijapur', 'Chamrajnagar', 'Chikballapura', 'Chitradurga', 'Davanagere', 'Dharwad', 'Gadag', 'Gulbarga', 'Hassan', 'Haveri', 'Kolar', 'Koppal', 'Mandya', 'Mysore', 'Raichur', 'Ramanagara', 'Tumkur', 'Yadgir', 'Ariyalur', 'Coimbatore', 'Dharmapuri', 'Dindigul', 'Erode', 'Karur', 'Krishnagiri', 'Madurai', 'Namakkal', 'Perambalur', 'Salem', 'The Nilgiris', 'Theni', 'Tiruchirappalli', 'Tiruppur', 'Adilabad', 'Hyderabad', 'Karimnagar', 'Khammam', 'Mahbubnagar', 'Medak', 'Nalgonda', 'Nizamabad', 'Ranga Reddy', 'Warangal'], + 'WPAHR': ['Agar Malwa', 'Alirajpur', 'Barwani', 'Burhanpur', 'Dewas', 'Dhar', 'East Nimar', 'Indore', 'Jhabua', 'Mandsaur', 'Neemuch', 'Ratlam', 'Shajapur', 'Ujjain', 'West Nimar', 'Ahmadnagar', 'Akola', 'Amravati', 'Aurangabad', 'Bid', 'Buldana', 'Dhule', 'Hingoli', 'Jalgaon', 'Jalna', 'Kolhapur', 'Latur', 'Nagpur', 'Nanded', 'Nandurbar', 'Nashik', 'Osmanabad', 'Parbhani', 'Pune', 'Sangli', 'Satara', 'Solapur', 'Wardha', 'Washim', 'Yavatmal', 'Jhalawar'], + 'ECPHR': ['East Godavari', 'Guntur', 'Krishna', 'Nellore', 'Prakasam', 'Srikakulam', 'Visakhapatnam', 'Vizianagaram', 'West Godavari', 'Baleshwar', 'Bhadrak', 'Cuttack', 'Gajapati', 'Ganjam', 'Jagatsinghapur', 'Jajapur', 'Kendrapara', 'Khordha', 'Nayagarh', 'Puri', 'Karaikal', 'Puducherry', 'Yanam', 'Chennai', 'Cuddalore', 'Kancheepuram', 'Nagappattinam', 'Pudukkottai', 'Ramanathapuram', 'Sivaganga', 'Thanjavur', 'Thiruvallur', 'Thiruvarur', 'Thoothukkudi', 'Tirunelveli', 'Tiruvannamalai', 'Vellore', 'Viluppuram', 'Virudunagar'], + 'UGPR': ['Agra', 'Aligarh', 'Allahabad', 'Amethi', 'Amroha', 'Auraiya', 'Baghpat', 'Barabanki', 'Bareilly', 'Bijnor', 'Budaun', 'Bulandshahr', 'Etah', 'Etawah', 'Farrukhabad', 'Fatehpur', 'Firozabad', 'Gautam Buddha Nagar', 'Ghaziabad', 'Hapur', 'Hardoi', 'Hathras', 'Kannauj', 'Kanpur Dehat', 'Kanpur Nagar', 'Kasganj', 'Kaushambi', 'Lakhimpur Kheri', 'Lucknow', 'Mainpuri', 'Mathura', 'Meerut', 'Moradabad', 'Muzaffarnagar', 'Pilibhit', 'Pratapgarhup', 'Rae Bareli', 'Rampur', 'Saharanpur', 'Sambhal', 'Shahjahanpur', 'Shamli', 'Sitapur', 'Sultanpur', 'Unnao', 'Hardwar', 'Udham Singh Nagar'], + 'LGPR': ['Bankura', 'Barddhaman', 'Birbhum', 'Dakshin Dinajpur', 'Haora', 'Hugli', 'Kolkata', 'Maldah', 'Murshidabad', 'Nadia', 'North 24 Parganas', 'Pashchim Medinipur', 'Purba Medinipur', 'South 24 Parganas', 'Uttar Dinajpur'], + 'MGPR': ['Araria', 'Arwal', 'AurangabadB', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar', 'Darbhanga', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur', 'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger', 'Muzaffarpur', 'Nalanda', 'Nawada', 'Pashchim Champaran', 'Patna', 'Purba Champaran', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur', 'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali', 'Godda', 'Sahibganj', 'Ambedkar Nagar', 'Azamgarh', 'Bahraich', 'Ballia', 'Balrampur', 'Basti', 'Chandauli', 'Deoria', 'Faizabad', 'Ghazipur', 'Gonda', 'Gorakhpur', 'Jaunpur', 'Kushinagar', 'Maharajganj', 'Mau', 'Mirzapur', 'Sant Kabir Nagar', 'Sant Ravi Das Nagar', 'Shravasti', 'Siddharth Nagar', 'Sonbhadra', 'Varanasi'], + 'TGPR': ['Chandigarh', 'Ambala', 'Bhiwani', 'Faridabad', 'Fatehabad', 'Gurgaon', 'Hisar', 'Jhajjar', 'Jind', 'Kaithal', 'Karnal', 'Kurukshetra', 'Mahendragarh', 'Mewat', 'Palwal', 'Panchkula', 'Panipat', 'Rewari', 'Rohtak', 'Sirsa', 'Sonipat', 'Yamunanagar', 'Delhi', 'Amritsar', 'Barnala', 'Bathinda', 'Faridkot', 'Fatehgarh Sahib', 'Fazilka', 'Firozpur', 'Gurdaspur', 'Hoshiarpur', 'Jalandhar', 'Kapurthala', 'Ludhiana', 'Mansa', 'Moga', 'Muktsar', 'Patiala', 'Sahibzada Ajit Singh Nagar', 'Sangrur', 'Shahid Bhagat Singh Nagar', 'Tarn Taran', 'Ganganagar', 'Hanumangarh'], + 'WHR': ['Bilaspur', 'Chamba', 'Hamirpur', 'Kangra', 'Kinnaur', 'Kullu', 'Lahul & Spiti', 'Mandi', 'Shimla', 'Sirmaur', 'Solan', 'Una', 'Anantnag', 'Badgam', 'Bandipore', 'Baramulla', 'Doda', 'Ganderbal', 'Jammu', 'Kargil', 'Kathua', 'Kishtwar', 'Kulgam', 'Kupwara', 'Leh (Ladakh)', 'Poonch', 'Pulwama', 'Rajouri', 'Ramban', 'Reasi', 'Samba', 'Shupiyan', 'Srinagar', 'Udhampur', 'Pathankot', 'Rupnagar', 'Almora', 'Bageshwar', 'Chamoli', 'Champawat', 'Dehradun', 'Garhwal', 'Nainital', 'Pithoragarh', 'Rudraprayag', 'Tehri Garhwal', 'Uttarkashi'], + 'EHR': ['Anjaw', 'Changlang', 'Dibang Valley', 'East Kameng', 'East Siang', 'Kurung Kumey', 'Lohit', 'Longding', 'Lower Dibang Valley', 'Lower Subansiri', 'Namsai', 'Papum Pare', 'Tawang', 'Tirap', 'Upper Siang', 'Upper Subansiri', 'West Kameng', 'West Siang', 'Baksa', 'Barpeta', 'Bongaigaon', 'Cachar', 'Chirang', 'Darrang', 'Dhemaji', 'Dhubri', 'Dibrugarh', 'Dima Hasao', 'Goalpara', 'Golaghat', 'Hailakandi', 'Jorhat', 'Kamrup Metropolitan', 'Kamrup', 'Karbi Anglong', 'Karimganj', 'Kokrajhar', 'Lakhimpur', 'Morigaon', 'Nagaon', 'Nalbari', 'Sivasagar', 'Sonitpur', 'Tinsukia', 'Udalguri', 'Bishnupur', 'Chandel', 'Churachandpur', 'Imphal East', 'Imphal West', 'Senapati', 'Tamenglong', 'Thoubal', 'Ukhrul', 'East Garo Hills', 'East Khasi Hills', 'Jaintia Hills', 'North Garo Hills', 'Ri Bhoi', 'South Garo Hills', 'South West Garo Hills', 'South West Khasi Hills', 'West Garo Hills', 'West Khasi Hills', 'Aizawl', 'Champhai', 'Kolasib', 'Lawangtlai', 'Lunglei', 'Mamit', 'Saiha', 'Serchhip', 'Dimapur', 'Kiphire', 'Kohima', 'Longleng', 'Mokokchung', 'Mon', 'Peren', 'Phek', 'Tuensang', 'Wokha', 'Zunheboto', 'East Sikkim', 'North Sikkim', 'South Sikkim', 'West Sikkim', 'Dhalai', 'Gomati', 'Khowai', 'North Tripura', 'Sipahijala', 'South Tripura', 'Unokoti', 'West Tripura', 'Alipurduar', 'Darjiling', 'Jalpaiguri', 'Koch Bihar'] +}; + +acz = acronym; + +var dist_list = dist_list_dict[acz]; +var india_district = ee.FeatureCollection('projects/ee-indiasat/assets/india_district_boundaries'); +var aoi = india_district.filter(ee.Filter.eq('Name', dist_list[0])).geometry(); + +for (var i=1; i year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2018; + +// var india_district_boundary = ee.FeatureCollection("projects/ee-indiasat/assets/india_district_boundaries"); +// var aoi = india_district_boundary.filter(ee.Filter.eq('Name', "Baloda Bazar")).geometry(); + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +// var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +// Set the change list according to the following: +// -2 -> Deforestation +// -1 -> Degradation +// 0 -> No Change +// 1 -> Improvement +// 2 -> Afforestation +// 3 -> Missing Data +var change_list = [-1, 1]; + +Map.addLayer(aoi, {'color': 'black'}, 'AOI'); +Map.centerObject(aoi, 7); + +var year_in_0 = String(year_1-1); +var year_in_1 = String(year_1); +var year_in_2 = String(year_1+1); + +var year_fi_0 = String(year_2-1); +var year_fi_1 = String(year_2); +var year_fi_2 = String(year_2+1); + +var project_path = 'projects/corestack-trees/assets/tree_characteristics'; + +var ccd_in_0 = ee.ImageCollection(project_path+'/ccd_' + year_in_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + + +ccd_in_0 = ee.Algorithms.If( + ccd_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_in_0.mean().clip(aoi).select('cc') +); + +ccd_in_0 = ee.Image(ccd_in_0); + + +// var tree_cover_in_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_0 = tree_cover.get_tree_cover(aoi, year_in_0); +tree_cover_in_0 = tree_cover_in_0.rename(['label_' + year_in_0.slice(-2)]); + + +var ccd_in_1 = ee.ImageCollection(project_path+'/ccd_' + year_in_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_in_1 = ee.Algorithms.If( + ccd_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_in_1.mean().clip(aoi).select('cc') +); +ccd_in_1 = ee.Image(ccd_in_1); + +// var tree_cover_in_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_1 = tree_cover.get_tree_cover(aoi, year_in_1); +tree_cover_in_1 = tree_cover_in_1.rename(['label_' + year_in_1.slice(-2)]); + +var ccd_in_2 = ee.ImageCollection(project_path+'/ccd_' + year_in_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_in_2 = ee.Algorithms.If( + ccd_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_in_2.mean().clip(aoi).select('cc') +); +ccd_in_2 = ee.Image(ccd_in_2); + +// var tree_cover_in_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_2 = tree_cover.get_tree_cover(aoi, year_in_2); +tree_cover_in_2 = tree_cover_in_2.rename(['label_' + year_in_2.slice(-2)]); + +var ccd_fi_0 = ee.ImageCollection(project_path+'/ccd_' + year_fi_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_fi_0 = ee.Algorithms.If( + ccd_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_fi_0.mean().clip(aoi).select('cc') +); +ccd_fi_0 = ee.Image(ccd_fi_0); + +// var tree_cover_fi_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_0 = tree_cover.get_tree_cover(aoi, year_fi_0); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_' + year_fi_0.slice(-2)]); + +var ccd_fi_1 = ee.ImageCollection(project_path+'/ccd_' + year_fi_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_fi_1 = ee.Algorithms.If( + ccd_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_fi_1.mean().clip(aoi).select('cc') +); +ccd_fi_1 = ee.Image(ccd_fi_1); + +// var tree_cover_fi_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_1 = tree_cover.get_tree_cover(aoi, year_fi_1); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_' + year_fi_1.slice(-2)]); + +var ccd_fi_2 = ee.ImageCollection(project_path+'/ccd_' + year_fi_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc').toInt32(); + }); + +ccd_fi_2 = ee.Algorithms.If( + ccd_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc') + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ccd_fi_2.mean().clip(aoi).select('cc') +); +ccd_fi_2 = ee.Image(ccd_fi_2); + +// var tree_cover_fi_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_2 = tree_cover.get_tree_cover(aoi, year_fi_2); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_' + year_fi_2.slice(-2)]); + +var correction_in_0 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_in_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_in_0).toInt32(); + }); + +correction_in_0 = ee.Algorithms.If( + correction_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_in_0) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_0.mode().clip(aoi).select('cc_' + year_in_0) +); +correction_in_0 = ee.Image(correction_in_0); + +var correction_in_1 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_in_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_in_1).toInt32(); + }); + +correction_in_1 = ee.Algorithms.If( + correction_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_in_1) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_1.mode().clip(aoi).select('cc_' + year_in_1) +); +correction_in_1 = ee.Image(correction_in_1); + +var correction_in_2 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_in_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_in_2).toInt32(); + }); + +correction_in_2 = ee.Algorithms.If( + correction_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_in_2) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_2.mode().clip(aoi).select('cc_' + year_in_2) +); +correction_in_2 = ee.Image(correction_in_2); + +var correction_fi_0 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_fi_0) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_fi_0).toInt32(); + }); + +correction_fi_0 = ee.Algorithms.If( + correction_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_fi_0) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_0.mode().clip(aoi).select('cc_' + year_fi_0) +); +correction_fi_0 = ee.Image(correction_fi_0); + +var correction_fi_1 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_fi_1) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_fi_1).toInt32(); + }); + +correction_fi_1 = ee.Algorithms.If( + correction_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_fi_1) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_1.mode().clip(aoi).select('cc_' + year_fi_1) +); +correction_fi_1 = ee.Image(correction_fi_1); + +var correction_fi_2 = ee.ImageCollection(project_path+'/corrections_ccd_' + year_fi_2) + .filterBounds(aoi) + .map(function(img) { + return img.select('cc_' + year_fi_2).toInt32(); + }); + +correction_fi_2 = ee.Algorithms.If( + correction_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant(-9999) + .rename('cc_' + year_fi_2) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_2.mode().clip(aoi).select('cc_' + year_fi_2) +); +correction_fi_2 = ee.Image(correction_fi_2); + + +var palette2 =['FFA500', '007500', '000000']; +Map.addLayer(correction_fi_2, {bands:['cc_'+year_fi_2], min: 0, max: 2, palette: palette2}, 'Empty'); + +correction_in_0 = correction_in_0.rename(['cc_in_0']); +correction_in_1 = correction_in_1.rename(['cc_in_1']); +correction_in_2 = correction_in_2.rename(['cc_in_2']); +correction_fi_0 = correction_fi_0.rename(['cc_fi_0']); +correction_fi_1 = correction_fi_1.rename(['cc_fi_1']); +correction_fi_2 = correction_fi_2.rename(['cc_fi_2']); + + + + +ccd_in_0 = ccd_in_0.addBands(correction_in_0); +ccd_in_0 = ccd_in_0.unmask(-9999); +ccd_in_0 = ccd_in_0.expression( + "((b('cc_in_0')!=b('cc')) and (b('cc_in_0')!=-9999)) ? (b('cc_in_0'))"+ + ":b('cc')" + ).clip(aoi); +ccd_in_0 = ccd_in_0.updateMask(ccd_in_0.neq(-9999)); +ccd_in_0 = ccd_in_0.rename(['cc_in_0']); + + +ccd_in_1 = ccd_in_1.addBands(correction_in_1); +ccd_in_1 = ccd_in_1.unmask(-9999); +ccd_in_1 = ccd_in_1.expression( + "((b('cc_in_1')!=b('cc')) and (b('cc_in_1')!=-9999)) ? (b('cc_in_1'))"+ + ":b('cc')" + ).clip(aoi); +ccd_in_1 = ccd_in_1.updateMask(ccd_in_1.neq(-9999)); +ccd_in_1 = ccd_in_1.rename(['cc_in_1']); + + +ccd_in_2 = ccd_in_2.addBands(correction_in_2); +ccd_in_2 = ccd_in_2.unmask(-9999); +ccd_in_2 = ccd_in_2.expression( + "((b('cc_in_2')!=b('cc')) and (b('cc_in_2')!=-9999)) ? (b('cc_in_2'))"+ + ":b('cc')" + ).clip(aoi); +ccd_in_2 = ccd_in_2.updateMask(ccd_in_2.neq(-9999)); +ccd_in_2 = ccd_in_2.rename(['cc_in_2']); + + +ccd_fi_0 = ccd_fi_0.addBands(correction_fi_0); +ccd_fi_0 = ccd_fi_0.unmask(-9999); +ccd_fi_0 = ccd_fi_0.expression( + "((b('cc_fi_0')!=b('cc')) and (b('cc_fi_0')!=-9999)) ? (b('cc_fi_0'))"+ + ":b('cc')" + ).clip(aoi); +ccd_fi_0 = ccd_fi_0.updateMask(ccd_fi_0.neq(-9999)); +ccd_fi_0 = ccd_fi_0.rename(['cc_fi_0']); + + +ccd_fi_1 = ccd_fi_1.addBands(correction_fi_1); +ccd_fi_1 = ccd_fi_1.unmask(-9999); +ccd_fi_1 = ccd_fi_1.expression( + "((b('cc_fi_1')!=b('cc')) and (b('cc_fi_1')!=-9999)) ? (b('cc_fi_1'))"+ + ":b('cc')" + ).clip(aoi); +ccd_fi_1 = ccd_fi_1.updateMask(ccd_fi_1.neq(-9999)); +ccd_fi_1 = ccd_fi_1.rename(['cc_fi_1']); + + +ccd_fi_2 = ccd_fi_2.addBands(correction_fi_2); +ccd_fi_2 = ccd_fi_2.unmask(-9999); +ccd_fi_2 = ccd_fi_2.expression( + "((b('cc_fi_2')!=b('cc')) and (b('cc_fi_2')!=-9999)) ? (b('cc_fi_2'))"+ + ":b('cc')" + ).clip(aoi); +ccd_fi_2 = ccd_fi_2.updateMask(ccd_fi_2.neq(-9999)); +ccd_fi_2 = ccd_fi_2.rename(['cc_fi_2']); + + +tree_cover_in_0 = tree_cover_in_0.rename(['label_in_0']); +tree_cover_in_1 = tree_cover_in_1.rename(['label_in_1']); +tree_cover_in_2 = tree_cover_in_2.rename(['label_in_2']); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_fi_0']); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_fi_1']); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_fi_2']); + +var tree_cover_initial = tree_cover_in_0.addBands(tree_cover_in_1).addBands(tree_cover_in_2); +tree_cover_initial = tree_cover_initial.unmask(-9999); +tree_cover_initial = tree_cover_initial.expression( + "((b('label_in_1')!=-9999)) ? (b('label_in_1'))"+ + ":((b('label_in_0')!=-9999) and (b('label_in_1')==-9999) and (b('label_in_2')!=-9999)) ? (b('label_in_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_initial = tree_cover_initial.rename(['label']); +tree_cover_initial = tree_cover_initial.updateMask(tree_cover_initial.neq(-9999)); +// print(tree_cover_initial); + + +var tree_cover_final = tree_cover_fi_0.addBands(tree_cover_fi_1).addBands(tree_cover_fi_2); +tree_cover_final = tree_cover_final.unmask(-9999); +tree_cover_final = tree_cover_final.expression( + "((b('label_fi_1')!=-9999)) ? (b('label_fi_1'))"+ + ":((b('label_fi_0')!=-9999) and (b('label_fi_1')==-9999) and (b('label_fi_2')!=-9999)) ? (b('label_fi_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_final = tree_cover_final.rename(['label']); +tree_cover_final = tree_cover_final.updateMask(tree_cover_final.neq(-9999)); + +Map.addLayer(tree_cover_initial, {palette: ['white']}, 'tree cover initial'); +Map.addLayer(tree_cover_final, {palette: ['white']}, 'tree cover final'); + +// 2 denotes missing data in modal images + +var initial_image = ccd_in_0.addBands(ccd_in_1).addBands(ccd_in_2); +initial_image = initial_image.unmask(-9999); +// 0 denotes Low; 1 denotes High; 2 denotes missing data +initial_image = initial_image.expression( + "((b('cc_in_0')==0) and (b('cc_in_1')==0) and (b('cc_in_2')==0)) ? (0)"+ + ":((b('cc_in_0')==0) and (b('cc_in_1')==0) and (b('cc_in_2')==1)) ? (0)"+ + ":((b('cc_in_0')==0) and (b('cc_in_1')==1) and (b('cc_in_2')==0)) ? (0)"+ + ":((b('cc_in_0')==0) and (b('cc_in_1')==1) and (b('cc_in_2')==1)) ? (1)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==0) and (b('cc_in_2')==0)) ? (0)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==0) and (b('cc_in_2')==1)) ? (1)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==1) and (b('cc_in_2')==0)) ? (1)"+ + ":((b('cc_in_0')==1) and (b('cc_in_1')==1) and (b('cc_in_2')==1)) ? (1)"+ + ":((b('cc_in_0')==-9999 or b('cc_in_2')==-9999) and (b('cc_in_1')!=-9999)) ? (b('cc_in_1'))"+ + ":((b('cc_in_0')!=-9999 and b('cc_in_1')==-9999 and b('cc_in_2')!=-9999) and (b('cc_in_0')==b('cc_in_2'))) ? (b('cc_in_0'))"+ + ":((b('cc_in_0')!=-9999 and b('cc_in_1')==-9999 and b('cc_in_2')==-9999)) ? (b('cc_in_0'))"+ + ":((b('cc_in_1')==-9999 and b('cc_in_2')!=-9999) and (b('cc_in_0')!=b('cc_in_2'))) ? (b('cc_in_2'))"+ + ":(-9999)" + ).clip(aoi); +initial_image = initial_image.rename(['cc']); +initial_image = initial_image.updateMask(initial_image.neq(-9999)); + +initial_image = initial_image.addBands(tree_cover_initial); +initial_image = initial_image.unmask(-9999); +// 2 denotes missing data +initial_image = initial_image.expression( + "((b('cc') == -9999) and (b('label') != -9999)) ? (2)"+ + ":b('cc')" + ).clip(aoi); +initial_image = initial_image.rename(['cc']); +initial_image = initial_image.updateMask(initial_image.neq(-9999)); +print("initial_image", initial_image) + +var final_image = ccd_fi_0.addBands(ccd_fi_1).addBands(ccd_fi_2); +final_image = final_image.unmask(-9999); +final_image = final_image.expression( + "((b('cc_fi_0')==0) and (b('cc_fi_1')==0) and (b('cc_fi_2')==0)) ? (0)"+ + ":((b('cc_fi_0')==0) and (b('cc_fi_1')==0) and (b('cc_fi_2')==1)) ? (0)"+ + ":((b('cc_fi_0')==0) and (b('cc_fi_1')==1) and (b('cc_fi_2')==0)) ? (0)"+ + ":((b('cc_fi_0')==0) and (b('cc_fi_1')==1) and (b('cc_fi_2')==1)) ? (1)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==0) and (b('cc_fi_2')==0)) ? (0)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==0) and (b('cc_fi_2')==1)) ? (1)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==1) and (b('cc_fi_2')==0)) ? (1)"+ + ":((b('cc_fi_0')==1) and (b('cc_fi_1')==1) and (b('cc_fi_2')==1)) ? (1)"+ + ":((b('cc_fi_0')==-9999 or b('cc_fi_2')==-9999) and (b('cc_fi_1')!=-9999)) ? (b('cc_fi_1'))"+ + ":((b('cc_fi_0')!=-9999 and b('cc_fi_1')==-9999 and b('cc_fi_2')!=-9999) and (b('cc_fi_0')==b('cc_fi_2'))) ? (b('cc_fi_0'))"+ + ":((b('cc_fi_0')!=-9999 and b('cc_fi_1')==-9999 and b('cc_fi_2')==-9999)) ? (b('cc_fi_0'))"+ + ":((b('cc_fi_1')==-9999 and b('cc_fi_2')!=-9999) and (b('cc_fi_0')!=b('cc_fi_2'))) ? (b('cc_fi_2'))"+ + ":(-9999)" + ).clip(aoi); +final_image = final_image.rename(['cc']); +final_image = final_image.updateMask(final_image.neq(-9999)); + +final_image = final_image.addBands(tree_cover_final); +final_image = final_image.unmask(-9999); +// 2 denotes missing data +final_image = final_image.expression( + "((b('cc') == -9999) and (b('label') != -9999)) ? (2)"+ + ":b('cc')" + ).clip(aoi); +final_image = final_image.rename(['cc']); +final_image = final_image.updateMask(final_image.neq(-9999)); + +// var palette = ['orange', 'green', 'black']; +var palette =['FFA500', '007500', '000000']; +Map.addLayer(initial_image, {bands:['cc'], min: 0, max: 2, palette: palette}, 'initial modal image'); +Map.addLayer(final_image, {bands:['cc'], min: 0, max: 2, palette: palette}, 'final modal image'); + +// var change = initial_image.addBands(final_image); +// change = change.unmask(-9999); + +// print(change); + +// // 3 denotes missing data +// change = change.expression( +// "((b('cc')==0) and (b('cc_1')==0)) ? (0)"+ +// ":((b('cc')==1) and (b('cc_1')==1)) ? (0)"+ +// ":((b('cc')==0) and (b('cc_1')==1)) ? (1)"+ +// ":((b('cc')==1) and (b('cc_1')==0)) ? (-1)"+ +// ":((b('cc')==-9999) and (b('cc_1')!=-9999)) ? (2)"+ +// ":((b('cc')!=-9999) and (b('cc_1')==-9999)) ? (-2)"+ +// ":((b('cc')==2) or (b('cc_1')==2)) ? (3)"+ +// ":-9999" +// ) +// .clip(aoi); +// change = change.updateMask(change.neq(-9999)); +// // var palette = ['red', 'orange', 'white', 'lightgreen', 'darkgreen', 'black']; +// var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +// Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer'); + +// Export modal initial image +Export.image.toAsset({ + image: initial_image, + description: 'ccd_' + year_in_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ccd_' + year_in_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// Export modal final image +Export.image.toAsset({ + image: final_image, + description: 'ccd_' + year_fi_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ccd_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// // Export CCD change image +// Export.image.toAsset({ +// image: change, +// description: 'ccd_change_' + agroclimaticZoneAcronymDict[acz], +// assetId: project_path+'/ccd_change_' + year_in_1 + '_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], +// region: aoi, +// scale: 25, +// crs: 'EPSG:4326', +// maxPixels: 10000000000 +// }); + +//=========================================== +// var change_ccd = change; + +// for (var c=0; c year_1 and both must be of Integer type +var year_1 = 2023; +var year_2 = 2022; + +// var india_district_boundary = ee.FeatureCollection("projects/ee-indiasat/assets/india_district_boundaries"); +// var aoi = india_district_boundary.filter(ee.Filter.eq('Name', "Baloda Bazar")).geometry(); + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +// var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// Set the AOI +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); + +// Set the change list according to the following: +// -2 -> Deforestation +// -1 -> Degradation +// 0 -> No Change +// 1 -> Improvement +// 2 -> Afforestation +// 3 -> Missing Data +var change_list = [-1, 1]; + +Map.addLayer(aoi, {'color': 'black'}, 'AOI'); +Map.centerObject(aoi, 7); + +var year_in_0 = String(year_1-1); +var year_in_1 = String(year_1); +var year_in_2 = String(year_1+1); + +var year_fi_0 = String(year_2-1); +var year_fi_1 = String(year_2); +var year_fi_2 = String(year_2+1); + +var project_path = 'projects/corestack1-dev-alpha/assets/tree_characteristics'; + +// var tree_cover_in_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_0 = tree_cover.get_tree_cover(aoi, year_in_0); +tree_cover_in_0 = tree_cover_in_0.rename(['label_' + year_in_0.slice(-2)]); + + +// var tree_cover_in_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_1 = tree_cover.get_tree_cover(aoi, year_in_1); +tree_cover_in_1 = tree_cover_in_1.rename(['label_' + year_in_1.slice(-2)]); + + +// var tree_cover_in_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_in_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_in_2 = tree_cover.get_tree_cover(aoi, year_in_2); +tree_cover_in_2 = tree_cover_in_2.rename(['label_' + year_in_2.slice(-2)]); + + +// var tree_cover_fi_0 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_0 = tree_cover.get_tree_cover(aoi, year_fi_0); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_' + year_fi_0.slice(-2)]); + + +// var tree_cover_fi_1 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_1 = tree_cover.get_tree_cover(aoi, year_fi_1); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_' + year_fi_1.slice(-2)]); + + +// var tree_cover_fi_2 = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/tree_cover_' + year_fi_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi); +var tree_cover_fi_2 = tree_cover.get_tree_cover(aoi, year_fi_2); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_' + year_fi_2.slice(-2)]); + +tree_cover_in_0 = tree_cover_in_0.rename(['label_in_0']); +tree_cover_in_1 = tree_cover_in_1.rename(['label_in_1']); +tree_cover_in_2 = tree_cover_in_2.rename(['label_in_2']); +tree_cover_fi_0 = tree_cover_fi_0.rename(['label_fi_0']); +tree_cover_fi_1 = tree_cover_fi_1.rename(['label_fi_1']); +tree_cover_fi_2 = tree_cover_fi_2.rename(['label_fi_2']); + +var tree_cover_initial = tree_cover_in_0.addBands(tree_cover_in_1).addBands(tree_cover_in_2); +tree_cover_initial = tree_cover_initial.unmask(-9999); +tree_cover_initial = tree_cover_initial.expression( + "((b('label_in_1')!=-9999)) ? (b('label_in_1'))"+ + ":((b('label_in_0')!=-9999) and (b('label_in_1')==-9999) and (b('label_in_2')!=-9999)) ? (b('label_in_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_initial = tree_cover_initial.rename(['label']); +tree_cover_initial = tree_cover_initial.updateMask(tree_cover_initial.neq(-9999)); +// print(tree_cover_initial); + + +var tree_cover_final = tree_cover_fi_0.addBands(tree_cover_fi_1).addBands(tree_cover_fi_2); +tree_cover_final = tree_cover_final.unmask(-9999); +tree_cover_final = tree_cover_final.expression( + "((b('label_fi_1')!=-9999)) ? (b('label_fi_1'))"+ + ":((b('label_fi_0')!=-9999) and (b('label_fi_1')==-9999) and (b('label_fi_2')!=-9999)) ? (b('label_fi_0'))"+ + ":(-9999)" + ).clip(aoi); +tree_cover_final = tree_cover_final.rename(['label']); +tree_cover_final = tree_cover_final.updateMask(tree_cover_final.neq(-9999)); + +Map.addLayer(tree_cover_initial, {palette: ['white']}, 'tree cover initial'); +Map.addLayer(tree_cover_final, {palette: ['white']}, 'tree cover final'); + + +// CH visualizations + +// var ch_in_0 = ee.ImageCollection(project_path+'/ch_' + year_in_0) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_in_0 = ee.ImageCollection(project_path+'/ch_' + year_in_0).filterBounds(aoi); + +ch_in_0 = ee.Algorithms.If( + ch_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_in_0.mean().clip(aoi) +); + +ch_in_0 = ee.Image(ch_in_0); + +// var ch_in_1 = ee.ImageCollection(project_path+'/ch_' + year_in_1) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_in_1 = ee.ImageCollection(project_path+'/ch_' + year_in_1).filterBounds(aoi); + +ch_in_1 = ee.Algorithms.If( + ch_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_in_1.mean().clip(aoi) +); + +ch_in_1 = ee.Image(ch_in_1); + +// var ch_in_2 = ee.ImageCollection(project_path+'/ch_' + year_in_2) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_in_2 = ee.ImageCollection(project_path+'/ch_' + year_in_2).filterBounds(aoi); + +ch_in_2 = ee.Algorithms.If( + ch_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_in_2.mean().clip(aoi) +); + +ch_in_2 = ee.Image(ch_in_2); + +// var ch_fi_0 = ee.ImageCollection(project_path+'/ch_' + year_fi_0) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_fi_0 = ee.ImageCollection(project_path+'/ch_' + year_fi_0).filterBounds(aoi); + +ch_fi_0 = ee.Algorithms.If( + ch_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_fi_0.mean().clip(aoi) +); + +ch_fi_0 = ee.Image(ch_fi_0); + +// var ch_fi_1 = ee.ImageCollection(project_path+'/ch_' + year_fi_1) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_fi_1 = ee.ImageCollection(project_path+'/ch_' + year_fi_1).filterBounds(aoi); + +ch_fi_1 = ee.Algorithms.If( + ch_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_fi_1.mean().clip(aoi) +); + +ch_fi_1 = ee.Image(ch_fi_1); + +// var ch_fi_2 = ee.ImageCollection(project_path+'/ch_' + year_fi_2) +// .filterBounds(aoi) +// .mean() +// .clip(aoi); +var ch_fi_2 = ee.ImageCollection(project_path+'/ch_' + year_fi_2).filterBounds(aoi); + +ch_fi_2 = ee.Algorithms.If( + ch_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_class','rh50_class','rh75_class','rh98_class']) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + ch_fi_2.mean().clip(aoi) +); + +ch_fi_2 = ee.Image(ch_fi_2); + +// var correction_img = ee.ImageCollection('projects/ee-mtpictd/assets/harsh/corrections_ch') +// .filterBounds(aoi) +// .mode() +// .clip(aoi); + +// var correction_in_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]); + +var correction_in_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_0).filterBounds(aoi); + +correction_in_0 = ee.Algorithms.If( + correction_in_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_0.mode().clip(aoi).select(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]) +); +correction_in_0 = ee.Image(correction_in_0); + +// var correction_in_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]); +var correction_in_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_1).filterBounds(aoi); + +correction_in_1 = ee.Algorithms.If( + correction_in_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_1.mode().clip(aoi).select(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]) +); +correction_in_1 = ee.Image(correction_in_1); + +// var correction_in_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]); + +var correction_in_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_in_2).filterBounds(aoi); + +correction_in_2 = ee.Algorithms.If( + correction_in_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_in_2.mode().clip(aoi).select(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]) +); +correction_in_2 = ee.Image(correction_in_2); + +// var correction_fi_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_0) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]); + +var correction_fi_0 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_0).filterBounds(aoi); + +correction_fi_0 = ee.Algorithms.If( + correction_fi_0.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_0.mode().clip(aoi).select(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]) +); +correction_fi_0 = ee.Image(correction_fi_0); + +// var correction_fi_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_1) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]); + +var correction_fi_1 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_1).filterBounds(aoi); + +correction_fi_1 = ee.Algorithms.If( + correction_fi_1.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_1.mode().clip(aoi).select(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]) +); +correction_fi_1 = ee.Image(correction_fi_1); + +// var correction_fi_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_2) +// .filterBounds(aoi) +// .mode() +// .clip(aoi) +// .select(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]); + +var correction_fi_2 = ee.ImageCollection(project_path+'/corrections_ch_' + year_fi_2).filterBounds(aoi); + +correction_fi_2 = ee.Algorithms.If( + correction_fi_2.size().eq(0), + // Dummy masked image + ee.Image.constant([-9999, -9999, -9999, -9999]) + .rename(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]) + .reproject('EPSG:4326', null, 25) + .clip(aoi) + .updateMask(ee.Image(1)), + // Real mean image + correction_fi_2.mode().clip(aoi).select(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]) +); +correction_fi_2 = ee.Image(correction_fi_2); + +// var correction_in_0 = correction_img.select(['ch_'+year_in_0, 'rh98_'+year_in_0, 'rh75_'+year_in_0, 'rh50_'+year_in_0]); +// var correction_in_1 = correction_img.select(['ch_'+year_in_1, 'rh98_'+year_in_1, 'rh75_'+year_in_1, 'rh50_'+year_in_1]); +// var correction_in_2 = correction_img.select(['ch_'+year_in_2, 'rh98_'+year_in_2, 'rh75_'+year_in_2, 'rh50_'+year_in_2]); +// var correction_fi_0 = correction_img.select(['ch_'+year_fi_0, 'rh98_'+year_fi_0, 'rh75_'+year_fi_0, 'rh50_'+year_fi_0]); +// var correction_fi_1 = correction_img.select(['ch_'+year_fi_1, 'rh98_'+year_fi_1, 'rh75_'+year_fi_1, 'rh50_'+year_fi_1]); +// var correction_fi_2 = correction_img.select(['ch_'+year_fi_2, 'rh98_'+year_fi_2, 'rh75_'+year_fi_2, 'rh50_'+year_fi_2]); + + +correction_in_0 = correction_in_0.rename(['ch_in_0', 'rh98_in_0', 'rh75_in_0', 'rh50_in_0']); +correction_in_1 = correction_in_1.rename(['ch_in_1', 'rh98_in_1', 'rh75_in_1', 'rh50_in_1']); +correction_in_2 = correction_in_2.rename(['ch_in_2', 'rh98_in_2', 'rh75_in_2', 'rh50_in_2']); +correction_fi_0 = correction_fi_0.rename(['ch_fi_0', 'rh98_fi_0', 'rh75_fi_0', 'rh50_fi_0']); +correction_fi_1 = correction_fi_1.rename(['ch_fi_1', 'rh98_fi_1', 'rh75_fi_1', 'rh50_fi_1']); +correction_fi_2 = correction_fi_2.rename(['ch_fi_2', 'rh98_fi_2', 'rh75_fi_2', 'rh50_fi_2']); + + +var ch_in_0_ch = ch_in_0.select('ch_class'); +ch_in_0_ch = ch_in_0_ch.addBands(correction_in_0); +ch_in_0_ch = ch_in_0_ch.unmask(-9999); +ch_in_0_ch = ch_in_0_ch.expression( + "((b('ch_in_0')!=b('ch_class')) and (b('ch_in_0')!=-9999)) ? (b('ch_in_0'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_in_0_ch = ch_in_0_ch.updateMask(ch_in_0_ch.neq(-9999)); +ch_in_0_ch = ch_in_0_ch.rename(['ch_in_0_ch']); + + +var ch_in_0_rh98 = ch_in_0.select('rh98_class'); +ch_in_0_rh98 = ch_in_0_rh98.addBands(correction_in_0); +ch_in_0_rh98 = ch_in_0_rh98.unmask(-9999); +ch_in_0_rh98 = ch_in_0_rh98.expression( + "((b('rh98_in_0')!=b('rh98_class')) and (b('rh98_in_0')!=-9999)) ? (b('rh98_in_0'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_in_0_rh98 = ch_in_0_rh98.updateMask(ch_in_0_rh98.neq(-9999)); +ch_in_0_rh98 = ch_in_0_rh98.rename(['ch_in_0_rh98']); + +var ch_in_0_rh75 = ch_in_0.select('rh75_class'); +ch_in_0_rh75 = ch_in_0_rh75.addBands(correction_in_0); +ch_in_0_rh75 = ch_in_0_rh75.unmask(-9999); +ch_in_0_rh75 = ch_in_0_rh75.expression( + "((b('rh75_in_0')!=b('rh75_class')) and (b('rh75_in_0')!=-9999)) ? (b('rh75_in_0'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_in_0_rh75 = ch_in_0_rh75.updateMask(ch_in_0_rh75.neq(-9999)); +ch_in_0_rh75 = ch_in_0_rh75.rename(['ch_in_0_rh75']);ch_in_0_rh75 + + +var ch_in_0_rh50 = ch_in_0.select('rh50_class'); +ch_in_0_rh50 = ch_in_0_rh50.addBands(correction_in_0); +ch_in_0_rh50 = ch_in_0_rh50.unmask(-9999); +ch_in_0_rh50 = ch_in_0_rh50.expression( + "((b('rh50_in_0')!=b('rh50_class')) and (b('rh50_in_0')!=-9999)) ? (b('rh50_in_0'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_in_0_rh50 = ch_in_0_rh50.updateMask(ch_in_0_rh50.neq(-9999)); +ch_in_0_rh50 = ch_in_0_rh50.rename(['ch_in_0_rh50']); + + +ch_in_0 = ch_in_0_ch.addBands(ch_in_0_rh98).addBands(ch_in_0_rh75).addBands(ch_in_0_rh50); + + +var ch_in_1_ch = ch_in_1.select('ch_class'); +ch_in_1_ch = ch_in_1_ch.addBands(correction_in_1); +ch_in_1_ch = ch_in_1_ch.unmask(-9999); +ch_in_1_ch = ch_in_1_ch.expression( + "((b('ch_in_1')!=b('ch_class')) and (b('ch_in_1')!=-9999)) ? (b('ch_in_1'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_in_1_ch = ch_in_1_ch.updateMask(ch_in_1_ch.neq(-9999)); +ch_in_1_ch = ch_in_1_ch.rename(['ch_in_1_ch']); + + +var ch_in_1_rh98 = ch_in_1.select('rh98_class'); +ch_in_1_rh98 = ch_in_1_rh98.addBands(correction_in_1); +ch_in_1_rh98 = ch_in_1_rh98.unmask(-9999); +ch_in_1_rh98 = ch_in_1_rh98.expression( + "((b('rh98_in_1')!=b('rh98_class')) and (b('rh98_in_1')!=-9999)) ? (b('rh98_in_1'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_in_1_rh98 = ch_in_1_rh98.updateMask(ch_in_1_rh98.neq(-9999)); +ch_in_1_rh98 = ch_in_1_rh98.rename(['ch_in_1_rh98']); + + +var ch_in_1_rh75 = ch_in_1.select('rh75_class'); +ch_in_1_rh75 = ch_in_1_rh75.addBands(correction_in_1); +ch_in_1_rh75 = ch_in_1_rh75.unmask(-9999); +ch_in_1_rh75 = ch_in_1_rh75.expression( + "((b('rh75_in_1')!=b('rh75_class')) and (b('rh75_in_1')!=-9999)) ? (b('rh75_in_1'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_in_1_rh75 = ch_in_1_rh75.updateMask(ch_in_1_rh75.neq(-9999)); +ch_in_1_rh75 = ch_in_1_rh75.rename(['ch_in_1_rh75']);ch_in_1_rh75 + + +var ch_in_1_rh50 = ch_in_1.select('rh50_class'); +ch_in_1_rh50 = ch_in_1_rh50.addBands(correction_in_1); +ch_in_1_rh50 = ch_in_1_rh50.unmask(-9999); +ch_in_1_rh50 = ch_in_1_rh50.expression( + "((b('rh50_in_1')!=b('rh50_class')) and (b('rh50_in_1')!=-9999)) ? (b('rh50_in_1'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_in_1_rh50 = ch_in_1_rh50.updateMask(ch_in_1_rh50.neq(-9999)); +ch_in_1_rh50 = ch_in_1_rh50.rename(['ch_in_1_rh50']); + + +ch_in_1 = ch_in_1_ch.addBands(ch_in_1_rh98).addBands(ch_in_1_rh75).addBands(ch_in_1_rh50); + +// print(ch_in_1); + +var ch_in_2_ch = ch_in_2.select('ch_class'); +ch_in_2_ch = ch_in_2_ch.addBands(correction_in_2); +ch_in_2_ch = ch_in_2_ch.unmask(-9999); +ch_in_2_ch = ch_in_2_ch.expression( + "((b('ch_in_2')!=b('ch_class')) and (b('ch_in_2')!=-9999)) ? (b('ch_in_2'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_in_2_ch = ch_in_2_ch.updateMask(ch_in_2_ch.neq(-9999)); +ch_in_2_ch = ch_in_2_ch.rename(['ch_in_2_ch']); + + +var ch_in_2_rh98 = ch_in_2.select('rh98_class'); +ch_in_2_rh98 = ch_in_2_rh98.addBands(correction_in_2); +ch_in_2_rh98 = ch_in_2_rh98.unmask(-9999); +ch_in_2_rh98 = ch_in_2_rh98.expression( + "((b('rh98_in_2')!=b('rh98_class')) and (b('rh98_in_2')!=-9999)) ? (b('rh98_in_2'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_in_2_rh98 = ch_in_2_rh98.updateMask(ch_in_2_rh98.neq(-9999)); +ch_in_2_rh98 = ch_in_2_rh98.rename(['ch_in_2_rh98']); + + +var ch_in_2_rh75 = ch_in_2.select('rh75_class'); +ch_in_2_rh75 = ch_in_2_rh75.addBands(correction_in_2); +ch_in_2_rh75 = ch_in_2_rh75.unmask(-9999); +ch_in_2_rh75 = ch_in_2_rh75.expression( + "((b('rh75_in_2')!=b('rh75_class')) and (b('rh75_in_2')!=-9999)) ? (b('rh75_in_2'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_in_2_rh75 = ch_in_2_rh75.updateMask(ch_in_2_rh75.neq(-9999)); +ch_in_2_rh75 = ch_in_2_rh75.rename(['ch_in_2_rh75']);ch_in_2_rh75 + + +var ch_in_2_rh50 = ch_in_2.select('rh50_class'); +ch_in_2_rh50 = ch_in_2_rh50.addBands(correction_in_2); +ch_in_2_rh50 = ch_in_2_rh50.unmask(-9999); +ch_in_2_rh50 = ch_in_2_rh50.expression( + "((b('rh50_in_2')!=b('rh50_class')) and (b('rh50_in_2')!=-9999)) ? (b('rh50_in_2'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_in_2_rh50 = ch_in_2_rh50.updateMask(ch_in_2_rh50.neq(-9999)); +ch_in_2_rh50 = ch_in_2_rh50.rename(['ch_in_2_rh50']); + + +ch_in_2 = ch_in_2_ch.addBands(ch_in_2_rh98).addBands(ch_in_2_rh75).addBands(ch_in_2_rh50); + + +var ch_fi_0_ch = ch_fi_0.select('ch_class'); +ch_fi_0_ch = ch_fi_0_ch.addBands(correction_fi_0); +ch_fi_0_ch = ch_fi_0_ch.unmask(-9999); +ch_fi_0_ch = ch_fi_0_ch.expression( + "((b('ch_fi_0')!=b('ch_class')) and (b('ch_fi_0')!=-9999)) ? (b('ch_fi_0'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_fi_0_ch = ch_fi_0_ch.updateMask(ch_fi_0_ch.neq(-9999)); +ch_fi_0_ch = ch_fi_0_ch.rename(['ch_fi_0_ch']); + + +var ch_fi_0_rh98 = ch_fi_0.select('rh98_class'); +ch_fi_0_rh98 = ch_fi_0_rh98.addBands(correction_fi_0); +ch_fi_0_rh98 = ch_fi_0_rh98.unmask(-9999); +ch_fi_0_rh98 = ch_fi_0_rh98.expression( + "((b('rh98_fi_0')!=b('rh98_class')) and (b('rh98_fi_0')!=-9999)) ? (b('rh98_fi_0'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_fi_0_rh98 = ch_fi_0_rh98.updateMask(ch_fi_0_rh98.neq(-9999)); +ch_fi_0_rh98 = ch_fi_0_rh98.rename(['ch_fi_0_rh98']); + + +var ch_fi_0_rh75 = ch_fi_0.select('rh75_class'); +ch_fi_0_rh75 = ch_fi_0_rh75.addBands(correction_fi_0); +ch_fi_0_rh75 = ch_fi_0_rh75.unmask(-9999); +ch_fi_0_rh75 = ch_fi_0_rh75.expression( + "((b('rh75_fi_0')!=b('rh75_class')) and (b('rh75_fi_0')!=-9999)) ? (b('rh75_fi_0'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_fi_0_rh75 = ch_fi_0_rh75.updateMask(ch_fi_0_rh75.neq(-9999)); +ch_fi_0_rh75 = ch_fi_0_rh75.rename(['ch_fi_0_rh75']);ch_fi_0_rh75 + + +var ch_fi_0_rh50 = ch_fi_0.select('rh50_class'); +ch_fi_0_rh50 = ch_fi_0_rh50.addBands(correction_fi_0); +ch_fi_0_rh50 = ch_fi_0_rh50.unmask(-9999); +ch_fi_0_rh50 = ch_fi_0_rh50.expression( + "((b('rh50_fi_0')!=b('rh50_class')) and (b('rh50_fi_0')!=-9999)) ? (b('rh50_fi_0'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_fi_0_rh50 = ch_fi_0_rh50.updateMask(ch_fi_0_rh50.neq(-9999)); +ch_fi_0_rh50 = ch_fi_0_rh50.rename(['ch_fi_0_rh50']); + + +ch_fi_0 = ch_fi_0_ch.addBands(ch_fi_0_rh98).addBands(ch_fi_0_rh75).addBands(ch_fi_0_rh50); + + +var ch_fi_1_ch = ch_fi_1.select('ch_class'); +ch_fi_1_ch = ch_fi_1_ch.addBands(correction_fi_1); +ch_fi_1_ch = ch_fi_1_ch.unmask(-9999); +ch_fi_1_ch = ch_fi_1_ch.expression( + "((b('ch_fi_1')!=b('ch_class')) and (b('ch_fi_1')!=-9999)) ? (b('ch_fi_1'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_fi_1_ch = ch_fi_1_ch.updateMask(ch_fi_1_ch.neq(-9999)); +ch_fi_1_ch = ch_fi_1_ch.rename(['ch_fi_1_ch']); + + +var ch_fi_1_rh98 = ch_fi_1.select('rh98_class'); +ch_fi_1_rh98 = ch_fi_1_rh98.addBands(correction_fi_1); +ch_fi_1_rh98 = ch_fi_1_rh98.unmask(-9999); +ch_fi_1_rh98 = ch_fi_1_rh98.expression( + "((b('rh98_fi_1')!=b('rh98_class')) and (b('rh98_fi_1')!=-9999)) ? (b('rh98_fi_1'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_fi_1_rh98 = ch_fi_1_rh98.updateMask(ch_fi_1_rh98.neq(-9999)); +ch_fi_1_rh98 = ch_fi_1_rh98.rename(['ch_fi_1_rh98']); + + +var ch_fi_1_rh75 = ch_fi_1.select('rh75_class'); +ch_fi_1_rh75 = ch_fi_1_rh75.addBands(correction_fi_1); +ch_fi_1_rh75 = ch_fi_1_rh75.unmask(-9999); +ch_fi_1_rh75 = ch_fi_1_rh75.expression( + "((b('rh75_fi_1')!=b('rh75_class')) and (b('rh75_fi_1')!=-9999)) ? (b('rh75_fi_1'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_fi_1_rh75 = ch_fi_1_rh75.updateMask(ch_fi_1_rh75.neq(-9999)); +ch_fi_1_rh75 = ch_fi_1_rh75.rename(['ch_fi_1_rh75']);ch_fi_1_rh75 + + +var ch_fi_1_rh50 = ch_fi_1.select('rh50_class'); +ch_fi_1_rh50 = ch_fi_1_rh50.addBands(correction_fi_1); +ch_fi_1_rh50 = ch_fi_1_rh50.unmask(-9999); +ch_fi_1_rh50 = ch_fi_1_rh50.expression( + "((b('rh50_fi_1')!=b('rh50_class')) and (b('rh50_fi_1')!=-9999)) ? (b('rh50_fi_1'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_fi_1_rh50 = ch_fi_1_rh50.updateMask(ch_fi_1_rh50.neq(-9999)); +ch_fi_1_rh50 = ch_fi_1_rh50.rename(['ch_fi_1_rh50']); + + +ch_fi_1 = ch_fi_1_ch.addBands(ch_fi_1_rh98).addBands(ch_fi_1_rh75).addBands(ch_fi_1_rh50); + + +var ch_fi_2_ch = ch_fi_2.select('ch_class'); +ch_fi_2_ch = ch_fi_2_ch.addBands(correction_fi_2); +ch_fi_2_ch = ch_fi_2_ch.unmask(-9999); +ch_fi_2_ch = ch_fi_2_ch.expression( + "((b('ch_fi_2')!=b('ch_class')) and (b('ch_fi_2')!=-9999)) ? (b('ch_fi_2'))"+ + ":b('ch_class')" + ).clip(aoi); +ch_fi_2_ch = ch_fi_2_ch.updateMask(ch_fi_2_ch.neq(-9999)); +ch_fi_2_ch = ch_fi_2_ch.rename(['ch_fi_2_ch']); + + +var ch_fi_2_rh98 = ch_fi_2.select('rh98_class'); +ch_fi_2_rh98 = ch_fi_2_rh98.addBands(correction_fi_2); +ch_fi_2_rh98 = ch_fi_2_rh98.unmask(-9999); +ch_fi_2_rh98 = ch_fi_2_rh98.expression( + "((b('rh98_fi_2')!=b('rh98_class')) and (b('rh98_fi_2')!=-9999)) ? (b('rh98_fi_2'))"+ + ":b('rh98_class')" + ).clip(aoi); +ch_fi_2_rh98 = ch_fi_2_rh98.updateMask(ch_fi_2_rh98.neq(-9999)); +ch_fi_2_rh98 = ch_fi_2_rh98.rename(['ch_fi_2_rh98']); + + +var ch_fi_2_rh75 = ch_fi_2.select('rh75_class'); +ch_fi_2_rh75 = ch_fi_2_rh75.addBands(correction_fi_2); +ch_fi_2_rh75 = ch_fi_2_rh75.unmask(-9999); +ch_fi_2_rh75 = ch_fi_2_rh75.expression( + "((b('rh75_fi_2')!=b('rh75_class')) and (b('rh75_fi_2')!=-9999)) ? (b('rh75_fi_2'))"+ + ":b('rh75_class')" + ).clip(aoi); +ch_fi_2_rh75 = ch_fi_2_rh75.updateMask(ch_fi_2_rh75.neq(-9999)); +ch_fi_2_rh75 = ch_fi_2_rh75.rename(['ch_fi_2_rh75']);ch_fi_2_rh75 + + +var ch_fi_2_rh50 = ch_fi_2.select('rh50_class'); +ch_fi_2_rh50 = ch_fi_2_rh50.addBands(correction_fi_2); +ch_fi_2_rh50 = ch_fi_2_rh50.unmask(-9999); +ch_fi_2_rh50 = ch_fi_2_rh50.expression( + "((b('rh50_fi_2')!=b('rh50_class')) and (b('rh50_fi_2')!=-9999)) ? (b('rh50_fi_2'))"+ + ":b('rh50_class')" + ).clip(aoi); +ch_fi_2_rh50 = ch_fi_2_rh50.updateMask(ch_fi_2_rh50.neq(-9999)); +ch_fi_2_rh50 = ch_fi_2_rh50.rename(['ch_fi_2_rh50']); + + +ch_fi_2 = ch_fi_2_ch.addBands(ch_fi_2_rh98).addBands(ch_fi_2_rh75).addBands(ch_fi_2_rh50); + +var initial_image = ch_in_0.addBands(ch_in_1).addBands(ch_in_2); +// print(initial_image); + +var initial_image_ch = initial_image.select(['ch_in_0_ch', 'ch_in_1_ch', 'ch_in_2_ch']); +initial_image_ch = initial_image_ch.unmask(-9999); +initial_image_ch = initial_image_ch.expression( + "((b('ch_in_0_ch')==b('ch_in_2_ch')) and (b('ch_in_0_ch')!=-9999)) ? (b('ch_in_0_ch'))"+ + ":((b('ch_in_0_ch')==-9999 or b('ch_in_2_ch')==-9999) and (b('ch_in_1_ch')!=-9999)) ? (b('ch_in_1_ch'))"+ + ":((b('ch_in_0_ch')!=-9999 and b('ch_in_1_ch')==-9999 and b('ch_in_2_ch')==-9999)) ? (b('ch_in_0_ch'))"+ + ":((b('ch_in_1_ch')==-9999 and b('ch_in_2_ch')!=-9999) and (b('ch_in_0_ch')!=b('ch_in_2_ch'))) ? (b('ch_in_2_ch'))"+ + ":(b('ch_in_1_ch'))" + ).clip(aoi); +initial_image_ch = initial_image_ch.rename(['ch_class']); +initial_image_ch = initial_image_ch.updateMask(initial_image_ch.neq(-9999)); + +var initial_image_rh98 = initial_image.select(['ch_in_0_rh98', 'ch_in_1_rh98', 'ch_in_2_rh98']); +initial_image_rh98 = initial_image_rh98.unmask(-9999); +initial_image_rh98 = initial_image_rh98.expression( + "((b('ch_in_0_rh98')==b('ch_in_2_rh98')) and (b('ch_in_0_rh98')!=-9999)) ? (b('ch_in_0_rh98'))"+ + ":((b('ch_in_0_rh98')==-9999 or b('ch_in_2_rh98')==-9999) and (b('ch_in_1_rh98')!=-9999)) ? (b('ch_in_1_rh98'))"+ + ":((b('ch_in_0_rh98')!=-9999 and b('ch_in_1_rh98')==-9999 and b('ch_in_2_rh98')==-9999)) ? (b('ch_in_0_rh98'))"+ + ":((b('ch_in_1_rh98')==-9999 and b('ch_in_2_rh98')!=-9999) and (b('ch_in_0_rh98')!=b('ch_in_2_rh98'))) ? (b('ch_in_2_rh98'))"+ + ":(b('ch_in_1_rh98'))" + ).clip(aoi); +initial_image_rh98 = initial_image_rh98.rename(['rh98_class']); +initial_image_rh98 = initial_image_rh98.updateMask(initial_image_rh98.neq(-9999)); + +var initial_image_rh75 = initial_image.select(['ch_in_0_rh75', 'ch_in_1_rh75', 'ch_in_2_rh75']); +initial_image_rh75 = initial_image_rh75.unmask(-9999); +initial_image_rh75 = initial_image_rh75.expression( + "((b('ch_in_0_rh75')==b('ch_in_2_rh75')) and (b('ch_in_0_rh75')!=-9999)) ? (b('ch_in_0_rh75'))"+ + ":((b('ch_in_0_rh75')==-9999 or b('ch_in_2_rh75')==-9999) and (b('ch_in_1_rh75')!=-9999)) ? (b('ch_in_1_rh75'))"+ + ":((b('ch_in_0_rh75')!=-9999 and b('ch_in_1_rh75')==-9999 and b('ch_in_2_rh75')==-9999)) ? (b('ch_in_0_rh75'))"+ + ":((b('ch_in_1_rh75')==-9999 and b('ch_in_2_rh75')!=-9999) and (b('ch_in_0_rh75')!=b('ch_in_2_rh75'))) ? (b('ch_in_2_rh75'))"+ + ":(b('ch_in_1_rh75'))" + ).clip(aoi); +initial_image_rh75 = initial_image_rh75.rename(['rh75_class']); +initial_image_rh75 = initial_image_rh75.updateMask(initial_image_rh75.neq(-9999)); + +var initial_image_rh50 = initial_image.select(['ch_in_0_rh50', 'ch_in_1_rh50', 'ch_in_2_rh50']); +initial_image_rh50 = initial_image_rh50.unmask(-9999); +initial_image_rh50 = initial_image_rh50.expression( + "((b('ch_in_0_rh50')==b('ch_in_2_rh50')) and (b('ch_in_0_rh50')!=-9999)) ? (b('ch_in_0_rh50'))"+ + ":((b('ch_in_0_rh50')==-9999 or b('ch_in_2_rh50')==-9999) and (b('ch_in_1_rh50')!=-9999)) ? (b('ch_in_1_rh50'))"+ + ":((b('ch_in_0_rh50')!=-9999 and b('ch_in_1_rh50')==-9999 and b('ch_in_2_rh50')==-9999)) ? (b('ch_in_0_rh50'))"+ + ":((b('ch_in_1_rh50')==-9999 and b('ch_in_2_rh50')!=-9999) and (b('ch_in_0_rh50')!=b('ch_in_2_rh50'))) ? (b('ch_in_2_rh50'))"+ + ":(b('ch_in_1_rh50'))" + ).clip(aoi); +initial_image_rh50 = initial_image_rh50.rename(['rh50_class']); +initial_image_rh50 = initial_image_rh50.updateMask(initial_image_rh50.neq(-9999)); + +initial_image_ch = initial_image_ch.addBands(tree_cover_initial); +initial_image_ch = initial_image_ch.unmask(-9999); + +// 4 denotes missing data +initial_image_ch = initial_image_ch.expression( + "((b('ch_class') == -9999) and (b('label') != -9999)) ? (4)"+ + ":b('ch_class')" + ).clip(aoi); +initial_image_ch = initial_image_ch.rename(['ch_class']); +initial_image_ch = initial_image_ch.updateMask(initial_image_ch.neq(-9999)); + +initial_image = initial_image_ch.addBands(initial_image_rh98).addBands(initial_image_rh75) + .addBands(initial_image_rh50); + + +var final_image = ch_fi_0.addBands(ch_fi_1).addBands(ch_fi_2); + +var final_image_ch = final_image.select(['ch_fi_0_ch', 'ch_fi_1_ch', 'ch_fi_2_ch']); +final_image_ch = final_image_ch.unmask(-9999); +final_image_ch = final_image_ch.expression( + "((b('ch_fi_0_ch')==b('ch_fi_2_ch')) and (b('ch_fi_0_ch')!=-9999)) ? (b('ch_fi_0_ch'))"+ + ":((b('ch_fi_0_ch')==-9999 or b('ch_fi_2_ch')==-9999) and (b('ch_fi_1_ch')!=-9999)) ? (b('ch_fi_1_ch'))"+ + ":((b('ch_fi_0_ch')!=-9999 and b('ch_fi_1_ch')==-9999 and b('ch_fi_2_ch')==-9999)) ? (b('ch_fi_0_ch'))"+ + ":((b('ch_fi_1_ch')==-9999 and b('ch_fi_2_ch')!=-9999) and (b('ch_fi_0_ch')!=b('ch_fi_2_ch'))) ? (b('ch_fi_2_ch'))"+ + ":(b('ch_fi_1_ch'))" + ).clip(aoi); +final_image_ch = final_image_ch.rename(['ch_class']); +final_image_ch = final_image_ch.updateMask(final_image_ch.neq(-9999)); + +var final_image_rh98 = final_image.select(['ch_fi_0_rh98', 'ch_fi_1_rh98', 'ch_fi_2_rh98']); +final_image_rh98 = final_image_rh98.unmask(-9999); +final_image_rh98 = final_image_rh98.expression( + "((b('ch_fi_0_rh98')==b('ch_fi_2_rh98')) and (b('ch_fi_0_rh98')!=-9999)) ? (b('ch_fi_0_rh98'))"+ + ":((b('ch_fi_0_rh98')==-9999 or b('ch_fi_2_rh98')==-9999) and (b('ch_fi_1_rh98')!=-9999)) ? (b('ch_fi_1_rh98'))"+ + ":((b('ch_fi_0_rh98')!=-9999 and b('ch_fi_1_rh98')==-9999 and b('ch_fi_2_rh98')==-9999)) ? (b('ch_fi_0_rh98'))"+ + ":((b('ch_fi_1_rh98')==-9999 and b('ch_fi_2_rh98')!=-9999) and (b('ch_fi_0_rh98')!=b('ch_fi_2_rh98'))) ? (b('ch_fi_2_rh98'))"+ + ":(b('ch_fi_1_rh98'))" + ).clip(aoi); +final_image_rh98 = final_image_rh98.rename(['rh98_class']); +final_image_rh98 = final_image_rh98.updateMask(final_image_rh98.neq(-9999)); + +var final_image_rh75 = final_image.select(['ch_fi_0_rh75', 'ch_fi_1_rh75', 'ch_fi_2_rh75']); +final_image_rh75 = final_image_rh75.unmask(-9999); +final_image_rh75 = final_image_rh75.expression( + "((b('ch_fi_0_rh75')==b('ch_fi_2_rh75')) and (b('ch_fi_0_rh75')!=-9999)) ? (b('ch_fi_0_rh75'))"+ + ":((b('ch_fi_0_rh75')==-9999 or b('ch_fi_2_rh75')==-9999) and (b('ch_fi_1_rh75')!=-9999)) ? (b('ch_fi_1_rh75'))"+ + ":((b('ch_fi_0_rh75')!=-9999 and b('ch_fi_1_rh75')==-9999 and b('ch_fi_2_rh75')==-9999)) ? (b('ch_fi_0_rh75'))"+ + ":((b('ch_fi_1_rh75')==-9999 and b('ch_fi_2_rh75')!=-9999) and (b('ch_fi_0_rh75')!=b('ch_fi_2_rh75'))) ? (b('ch_fi_2_rh75'))"+ + ":(b('ch_fi_1_rh75'))" + ).clip(aoi); +final_image_rh75 = final_image_rh75.rename(['rh75_class']); +final_image_rh75 = final_image_rh75.updateMask(final_image_rh75.neq(-9999)); + +var final_image_rh50 = final_image.select(['ch_fi_0_rh50', 'ch_fi_1_rh50', 'ch_fi_2_rh50']); +final_image_rh50 = final_image_rh50.unmask(-9999); +final_image_rh50 = final_image_rh50.expression( + "((b('ch_fi_0_rh50')==b('ch_fi_2_rh50')) and (b('ch_fi_0_rh50')!=-9999)) ? (b('ch_fi_0_rh50'))"+ + ":((b('ch_fi_0_rh50')==-9999 or b('ch_fi_2_rh50')==-9999) and (b('ch_fi_1_rh50')!=-9999)) ? (b('ch_fi_1_rh50'))"+ + ":((b('ch_fi_0_rh50')!=-9999 and b('ch_fi_1_rh50')==-9999 and b('ch_fi_2_rh50')==-9999)) ? (b('ch_fi_0_rh50'))"+ + ":((b('ch_fi_1_rh50')==-9999 and b('ch_fi_2_rh50')!=-9999) and (b('ch_fi_0_rh50')!=b('ch_fi_2_rh50'))) ? (b('ch_fi_2_rh50'))"+ + ":(b('ch_fi_1_rh50'))" + ).clip(aoi); +final_image_rh50 = final_image_rh50.rename(['rh50_class']); +final_image_rh50 = final_image_rh50.updateMask(final_image_rh50.neq(-9999)); + +final_image_ch = final_image_ch.addBands(tree_cover_final); +final_image_ch = final_image_ch.unmask(-9999); +// 4 denotes missing data +final_image_ch = final_image_ch.expression( + "((b('ch_class') == -9999) and (b('label') != -9999)) ? (4)"+ + ":b('ch_class')" + ).clip(aoi); +final_image_ch = final_image_ch.rename(['ch_class']); +final_image_ch = final_image_ch.updateMask(final_image_ch.neq(-9999)); + +final_image = final_image_ch.addBands(final_image_rh98).addBands(final_image_rh75) + .addBands(final_image_rh50); + + +var palette =['FFA500', 'DEE64C', 'DEE64C','007500', '000000']; +Map.addLayer(initial_image, {bands:['ch_class'], min: 0, max: 4, palette: palette}, 'initial modal image ch'); +Map.addLayer(final_image, {bands:['ch_class'], min: 0, max: 4, palette: palette}, 'final modal image ch'); + +initial_image = initial_image.unmask(-9999); +initial_image = initial_image.expression( + "((b('ch_class') == 4)) ? (8)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (0)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (1)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (2)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (3)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (4)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (5)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (6)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (7)"+ + ": ((b('rh50_class') != -9999) or (b('rh75_class') != -9999) and (b('rh98_class') != -9999)) ? (8)"+ + ":-9999" + ).clip(aoi); +initial_image = initial_image.updateMask(initial_image.neq(-9999)); +initial_image = initial_image.rename(['ch_class']); + + +final_image = final_image.unmask(-9999); +final_image = final_image.expression( + "((b('ch_class') == 4)) ? (8)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (0)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (1)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (2)"+ + ": ((b('rh50_class') == 0) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (3)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 0)) ? (4)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 0) and (b('rh98_class') == 1)) ? (5)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 0)) ? (6)"+ + ": ((b('rh50_class') == 1) and (b('rh75_class') == 1) and (b('rh98_class') == 1)) ? (7)"+ + ": ((b('rh50_class') != -9999) or (b('rh75_class') != -9999) and (b('rh98_class') != -9999)) ? (8)"+ + ":-9999" + ).clip(aoi); +final_image = final_image.updateMask(final_image.neq(-9999)); +final_image = final_image.rename(['ch_class']); + +var palette =['FFA500', 'FFA500', 'DEE64C', 'DEE64C', 'DEE64C', 'DEE64C', '007500', '007500', '000000']; +Map.addLayer(initial_image, {bands:['ch_class'], min: 0, max: 8, palette: palette}, 'initial modal image ch'); +Map.addLayer(final_image, {bands:['ch_class'], min: 0, max: 8, palette: palette}, 'final modal image ch'); + + +var change = initial_image.addBands(final_image); +change = change.unmask(-9999); +// 3 denotes missing data +change = change.expression( + "((b('ch_class') == -9999) and (b('ch_class_1') != -9999)) ? (2)" + + ": ((b('ch_class') != -9999) and (b('ch_class_1') == -9999)) ? (-2)"+ + ": ((b('ch_class') == 8) or (b('ch_class_1') == 8)) ? (3)" + + ": ((b('ch_class') == b('ch_class_1')) and (b('ch_class') != -9999)) ? (0)" + + ": ((b('ch_class') < b('ch_class_1')) and (b('ch_class') != -9999) and ((b('ch_class') <= 1) or (b('ch_class') >= 4))) ? (1)" + + ": ((b('ch_class') > b('ch_class_1')) and (b('ch_class_1') != -9999)) ? (-1)" + + ": ((b('ch_class') == 2) and ((b('ch_class_1') == 3) or (b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": ((b('ch_class') == 3) and ((b('ch_class_1') == 6) or (b('ch_class_1') == 7))) ? (1)" + + ": (((b('ch_class') == 2) or (b('ch_class') == 3)) and ((b('ch_class_1') == 4) or (b('ch_class_1') == 5))) ? (-1)"+ + ":-9999" +).clip(aoi); +change = change.updateMask(change.neq(-9999)); +// var palette = ['red', 'orange', 'white', 'lightgreen', 'green', 'black']; +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', '000000']; +Map.addLayer(change, {min: -2, max: 3, palette: palette}, 'change layer ch'); + +// Export modal initial image +Export.image.toAsset({ + image: initial_image, + description: 'ch_' + year_in_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ch_' + year_in_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// Export modal final image +Export.image.toAsset({ + image: final_image, + description: 'ch_' + year_fi_1 + '_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/modal_ch_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + +// // Export CH change image +// Export.image.toAsset({ +// image: change, +// description: 'ch_change_' + agroclimaticZoneAcronymDict[acz], +// assetId: project_path+'/ch_change_' + year_in_1 + '_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], +// region: aoi, +// scale: 25, +// crs: 'EPSG:4326', +// maxPixels: 10000000000 +// }); + +// Quantifying CH change classes statistics +// var change_chm = change; + +// for (var c=0; c year_1 and both must be of Integer type +var year_1 = 2017; +var year_2 = 2023; + +var india_boundary = ee.FeatureCollection("projects/ext-datasets/assets/datasets/ACZs"); + +var agroclimaticZoneAcronymDict = { + 'Eastern Plateau & Hills Region': 'EPAHR', + 'Southern Plateau and Hills Region': 'SPAHR', + 'East Coast Plains & Hills Region': 'ECPHR', + 'Western Plateau and Hills Region': 'WPAHR', + 'Central Plateau & Hills Region': 'CPAHR', + 'Lower Gangetic Plain Region': 'LGPR', + 'Middle Gangetic Plain Region': 'MGPR', + 'Upper Gangetic Plain Region': 'UGPR', + 'Trans Gangetic Plain Region': 'TGPR', + 'Eastern Himalayan Region': 'EHR', + 'Western Himalayan Region': 'WHR' +}; + +var acz = 'Eastern Plateau & Hills Region'; +// var acz = 'Lower Gangetic Plain Region'; +// var acz = 'Western Himalayan Region'; +// var acz = 'Eastern Himalayan Region'; +// var acz = 'Upper Gangetic Plain Region'; +// var acz = 'Middle Gangetic Plain Region'; +// var acz = 'Trans Gangetic Plain Region'; +// var acz = 'Central Plateau & Hills Region'; +// var acz = 'Western Plateau and Hills Region'; +// var acz = 'Southern Plateau and Hills Region'; +// var acz = 'East Coast Plains & Hills Region'; + +// var fc = ee.FeatureCollection('users/mtpictd/world_boundary'); +// fc = fc.filter(ee.Filter.eq('Name', 'India')); +// var aoi = fc.geometry(); + +var aoi = india_boundary.filter(ee.Filter.eq('regionname', acz)).geometry(); +var project_path = 'projects/corestack-trees/assets/tree_characteristics'; + +var year_in_1 = String(year_1); +var year_fi_1 = String(year_2); + + +var change_ccd = ee.ImageCollection(project_path+'/ccd_change_' + year_in_1 + '_' + year_fi_1) + .filterBounds(aoi) + .mean() + .clip(aoi); + +var change_chm = ee.ImageCollection(project_path+'/ch_change_' + year_in_1 + '_' + year_fi_1) + .filterBounds(aoi) + .mean() + .clip(aoi); + + +var palette = ['red', 'orange', 'white', 'lightgreen', 'green', 'black']; +Map.addLayer(change_ccd, {min: -2, max: 3, palette: palette}, 'ccd change'); +Map.addLayer(change_chm, {min: -2, max: 3, palette: palette}, 'ch change'); + + +var overall_change = change_ccd.addBands(change_chm); + +overall_change = overall_change.unmask(-9999); +overall_change = overall_change.expression( + "((b('constant') == 3) or (b('constant_1') == 3)) ? (5)"+ + ": ((b('constant') == -2) or (b('constant') == 2)) ? (b('constant'))"+ + ": ((b('constant') == 1) and (b('constant_1') == 1)) ? (1)"+ + ": ((b('constant') == 1) and (b('constant_1') == -1)) ? (3)"+ + ": ((b('constant') == 1) and (b('constant_1') == 0)) ? (1)"+ + ": ((b('constant') == -1) and (b('constant_1') == 1)) ? (4)"+ + ": ((b('constant') == -1) and (b('constant_1') == -1)) ? (-1)"+ + ": ((b('constant') == -1) and (b('constant_1') == 0)) ? (-1)"+ + ": ((b('constant') == 0) and (b('constant_1') == 1)) ? (1)"+ + ": ((b('constant') == 0) and (b('constant_1') == -1)) ? (-1)"+ + ": ((b('constant') == 0) and (b('constant_1') == 0)) ? (0)"+ + ": -9999" +).clip(aoi); +overall_change = overall_change.updateMask(overall_change.neq(-9999)); +// print(overall_change); +var palette = ['FF0000', 'FFA500', 'FFFFFF', '8AFF8A', '007500', 'DEE64C', 'DEE64C', '000000']; +Map.addLayer(overall_change, {min: -2, max: 5, palette: palette}, 'overall change'); + +Export.image.toAsset({ + image: overall_change, + description: 'overall_change_' + agroclimaticZoneAcronymDict[acz], + assetId: project_path+'/overall_change_' + year_in_1 + '_' + year_fi_1 + '/' + agroclimaticZoneAcronymDict[acz], + region: aoi, + scale: 25, + crs: 'EPSG:4326', + maxPixels: 10000000000 + }); + + +// // Set the change list according to the following: +// // -2 -> Deforestation +// // -1 -> Degradation +// // 0 -> No Change +// // 1 -> Improvement +// // 2 -> Afforestation +// // 3 denotes partial improvement and degradation where ccd improves and ch degrades +// // 4 denotes partial improvement and degradation where ccd degrades and ch improves +// // 5 -> Missing Data + +// // var change_list = [2, -2, 1, -1, 3, 4, 0, 5]; +// var change_list = [-1, 3, 4]; + +// for (var c=0; c Date: Tue, 16 Jun 2026 17:39:36 +0530 Subject: [PATCH 17/38] Fix/dev updated with staging (#1018) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Dev (#994) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari --------- Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Aman Verma Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: kapildadheech * Dev (#1008) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari * Feature/runtime layer (#998) * runtime layers using CSDB * crop grid layer * runtime layers using CSDB * runtime layers using CSDB -- merge resolved --------- Co-authored-by: Ksheetiz-24 Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Aman Verma Co-authored-by: pawangramvaani Co-authored-by: kapildadheech * merge fix --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari Co-authored-by: Amit Kumar From 01c8dc8b623c0f7b1630ca72abdf0dacea4148e1 Mon Sep 17 00:00:00 2001 From: Aman Verma Date: Tue, 16 Jun 2026 17:40:17 +0530 Subject: [PATCH 18/38] Dev to staging (#1011) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari * Feature/runtime layer (#998) * runtime layers using CSDB * crop grid layer * runtime layers using CSDB * runtime layers using CSDB -- merge resolved * Feature/tree health raw scripts (#1004) * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * tree health raw script * mws report revert --------- Co-authored-by: Ksheetiz-24 * Hotfix/email timeout (#1010) * patched name * email timeout increased to 15 minutes * Revert "Hotfix/email timeout (#1010)" (#1012) This reverts commit 0bfb37afbf7e93c572388374c1ea1d917af44bbc. * check missing layers and send mail (#1007) * check missing layers and send mail * add caching * increase email timeout * Features/missing layers (#1014) * check missing layers and send mail * add caching * increase email timeout * add retries and json as attach file * updated column with unit (#1001) * updated column with unit * updated sheet not aviable * chnages for handle exception * chnages for drainage density * added for livestock * chnages for excel * Features/multilingual dpr (#999) * convert till section c * add section d,e & f * transaltion till section g * send multiligual DPR on email * move files to data directory * fix section d data * read label json from data dir * add footnote * email timeout time inc --------- Co-authored-by: Ankit Kumar * plan count api (#1013) * chnages for conflict * Fix/dev updated with staging (#1018) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Dev (#994) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari --------- Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Aman Verma Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: kapildadheech * Dev (#1008) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) * Feature/antyodaya excel update (#992) * antyodaya_update * added Antyodaya data to excel and code update * facilities -- error handling (#991) * changes for LULC plain (#993) * Fix/merge fix (#996) * Fix : Updated LULC Legend * Dev -> Staging (#986) * Fix : Updated Colors (#984) * Fix : Updated the Colors in Change Detection (#985) --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari * Feature/runtime layer (#998) * runtime layers using CSDB * crop grid layer * runtime layers using CSDB * runtime layers using CSDB -- merge resolved --------- Co-authored-by: Ksheetiz-24 Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Aman Verma Co-authored-by: pawangramvaani Co-authored-by: kapildadheech * merge fix --------- Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: shiv1122prakash Co-authored-by: kapildadheech Co-authored-by: Ksheetiz Agrahari Co-authored-by: Amit Kumar --------- Co-authored-by: Ksheetiz-24 Co-authored-by: shiv1122prakash Co-authored-by: Amit Kumar Co-authored-by: Ankit K. Co-authored-by: pawangramvaani Co-authored-by: kapildadheech From 6cd63cd59a7869d19bd54aef78bcdcebdaa1ef87 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 16 Jun 2026 06:00:49 -0700 Subject: [PATCH 19/38] remove weasyprint use --- dpr/Plan Uchukabeda.docx | Bin 0 -> 46245 bytes dpr/api.py | 6 ++---- dpr/tasks.py | 24 ++++++++++-------------- dpr/utils.py | 14 +++++++------- dpr/views.py | 17 +++++++++-------- 5 files changed, 28 insertions(+), 33 deletions(-) create mode 100644 dpr/Plan Uchukabeda.docx diff --git a/dpr/Plan Uchukabeda.docx b/dpr/Plan Uchukabeda.docx new file mode 100644 index 0000000000000000000000000000000000000000..e493c9fe1dce9f6be8a0fce5cd6abe74040925ad GIT binary patch literal 46245 zcmZU(b6{lMvIp9+ZCevN6Wg|J+qRR5C!W~0C!9$pM#t79>DYYv&bjBkd+z(EyLYXs z+SP?sd+quu%RxY50ssJ508HwPeywWhw{$Q7U>q6%!1$!gPJ!xyj5x#Q7UW(LWFKRx{6jnSA@j)G?T zmizr7fOxG*pwGXJT2D+IWMGi1RZI?1?q9r7#B^@SeIW6TOa>^uN3<{k-O!Qik2lQ) z#tgd?Ui)N4B`e0ny z^X*TN7x|M|utfgbG846Co?Q-nR|rp5jPa$0WTbIR%CK9b=OlfztCfaJ)7sBcWbaV~ zC)BxphXE$92`%;$=%&i}TQ%KHblZr1kds|Fb;JBIM84LCBVXa^nkKzWPg1Ra_`}e{16^qnrQp+YwBf_Tyn@3Yjo@D>k(?Sr zjjG)uNg2P{zjA-LoQRPJ+!coFt$Y<%4CoOz5}*wc9`<{@LVh_kfm+3VL=35Wkbdng zJMdTwEi#j5$e`1EOD8Hp>;Go*;`fYDUX3R%`12K5zHuNl{y<*zf~jB#Q2wtd`cTUW zKYm7U6C40Q`203rEhIq+}q4=>viQquipYt-u zK_9~Oxe}R*^SxE$<^YYB+pBkP#j`Gm3Z)% zSWwW6xj}`Mkof|35-Tf>8M54Y&%vo+KatNhxwTi~pj$}qNxa23cH=C#!QI*pVHS}R zK@#=tBE&ovK?m!7%@lk6C12vp*V^>(!Ha?C`DTQ+#9-{&8}I40*Q2y!q22ej8zYF zEod8{*$6?NPf&;`VC=#w-&MUjG}*ReL2j-tThpEp2Gxk|QHaPHct2)A6*a=4KVaAO zJU5+&OB=@)7IxPe-Kc_v+|4`K8!+D5CU|HvO#*W_ejtx|30OtJ<)uB`UfHnbq{(2G zi{wJebV<)K^i38WHwS%4z2hwt(m!%vLE@fRAsf=odx^cj4qg-dcU*4Q)^JWg<6;j9 z0HFTwxR^LQ|0^u&6OL;vs68+AkOuCtHZUS8xGAAaBC}!}_zfw!?F-}Nvfm&(S$6{i zo}#h7Ml#>U@HO+jw2Z$1MJ!=j6suED3J%#tu2*Q*A@L5-o|NKuW-B+$ zRIRB}EBZPCl-#c^GFJGN6XH#bHvabm*kH7=1RU~WVw4}$Y(y$ zgi4j9dV6x1Uzv$7;c}jC2tF3?&Pe_{F@7gKU;d^60EWRJ|5dPF zPOj$6pH=JO@Y$UH$qZ0{f!kU~%Kh6rygCz?mD=|#fK2l3`M$bofj8Tet4@BZ?-l`$ z{4Dwy=8uCY&U~YO0NwAV;^ZdC(6nV7I7V6VVv*aic_fbaqk

        a(5g`n_qiIM!Vdj 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 ti~ehsKLW4k^3VOjf8_ Date: Tue, 16 Jun 2026 06:02:35 -0700 Subject: [PATCH 20/38] remove unwanted file --- dpr/Plan Uchukabeda.docx | Bin 46245 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dpr/Plan Uchukabeda.docx diff --git a/dpr/Plan Uchukabeda.docx b/dpr/Plan Uchukabeda.docx deleted file mode 100644 index e493c9fe1dce9f6be8a0fce5cd6abe74040925ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46245 zcmZU(b6{lMvIp9+ZCevN6Wg|J+qRR5C!W~0C!9$pM#t79>DYYv&bjBkd+z(EyLYXs z+SP?sd+quu%RxY50ssJ508HwPeywWhw{$Q7U>q6%!1$!gPJ!xyj5x#Q7UW(LWFKRx{6jnSA@j)G?T zmizr7fOxG*pwGXJT2D+IWMGi1RZI?1?q9r7#B^@SeIW6TOa>^uN3<{k-O!Qik2lQ) z#tgd?Ui)N4B`e0ny z^X*TN7x|M|utfgbG846Co?Q-nR|rp5jPa$0WTbIR%CK9b=OlfztCfaJ)7sBcWbaV~ zC)BxphXE$92`%;$=%&i}TQ%KHblZr1kds|Fb;JBIM84LCBVXa^nkKzWPg1Ra_`}e{16^qnrQp+YwBf_Tyn@3Yjo@D>k(?Sr zjjG)uNg2P{zjA-LoQRPJ+!coFt$Y<%4CoOz5}*wc9`<{@LVh_kfm+3VL=35Wkbdng zJMdTwEi#j5$e`1EOD8Hp>;Go*;`fYDUX3R%`12K5zHuNl{y<*zf~jB#Q2wtd`cTUW zKYm7U6C40Q`203rEhIq+}q4=>viQquipYt-u zK_9~Oxe}R*^SxE$<^YYB+pBkP#j`Gm3Z)% zSWwW6xj}`Mkof|35-Tf>8M54Y&%vo+KatNhxwTi~pj$}qNxa23cH=C#!QI*pVHS}R zK@#=tBE&ovK?m!7%@lk6C12vp*V^>(!Ha?C`DTQ+#9-{&8}I40*Q2y!q22ej8zYF zEod8{*$6?NPf&;`VC=#w-&MUjG}*ReL2j-tThpEp2Gxk|QHaPHct2)A6*a=4KVaAO zJU5+&OB=@)7IxPe-Kc_v+|4`K8!+D5CU|HvO#*W_ejtx|30OtJ<)uB`UfHnbq{(2G zi{wJebV<)K^i38WHwS%4z2hwt(m!%vLE@fRAsf=odx^cj4qg-dcU*4Q)^JWg<6;j9 z0HFTwxR^LQ|0^u&6OL;vs68+AkOuCtHZUS8xGAAaBC}!}_zfw!?F-}Nvfm&(S$6{i zo}#h7Ml#>U@HO+jw2Z$1MJ!=j6suED3J%#tu2*Q*A@L5-o|NKuW-B+$ zRIRB}EBZPCl-#c^GFJGN6XH#bHvabm*kH7=1RU~WVw4}$Y(y$ zgi4j9dV6x1Uzv$7;c}jC2tF3?&Pe_{F@7gKU;d^60EWRJ|5dPF zPOj$6pH=JO@Y$UH$qZ0{f!kU~%Kh6rygCz?mD=|#fK2l3`M$bofj8Tet4@BZ?-l`$ z{4Dwy=8uCY&U~YO0NwAV;^ZdC(6nV7I7V6VVv*aic_fbaqk

        a(5g`n_qiIM!Vdj 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 ti~ehsKLW4k^3VOjf8_ Date: Thu, 11 Jun 2026 17:46:12 +0530 Subject: [PATCH 21/38] Hotfix/email timeout (#1010) * patched name * email timeout increased to 15 minutes --- plans/tests.py | 12 ++++++++++++ plans/utils.py | 24 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/plans/tests.py b/plans/tests.py index bb68993b..d59c6045 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -471,6 +471,18 @@ def test_block_name_with_spaces_matches_underscore_block_param(self): self.assertTrue(result) + def test_block_name_with_underscores_matches_underscore_block_param(self): + _create_settlement(plan_id="42", block_name="test_block", settlement_id="SETT004") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_id"], "SETT004") + def test_only_matching_plan_id_returned(self): _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") diff --git a/plans/utils.py b/plans/utils.py index 7e96cfcf..f4f51403 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -5,6 +5,7 @@ import dateutil.parser import requests +from django.db.models import Q from dpr.models import ( ODK_settlement, ODK_well, ODK_waterbody, @@ -37,6 +38,27 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() + +def _block_filter_q(block): + """ + Accept both historical block-name storage styles in the DB: + `foo bar` and `foo_bar`. + """ + raw = (block or "").strip() + if not raw: + return Q() + + variants = { + raw, + raw.replace("_", " ").strip(), + raw.replace(" ", "_").strip(), + } + + query = Q() + for variant in variants: + query |= Q(block_name__icontains=variant) + return query + _RESOURCE_TYPES_FLAT_HEADER = frozenset({ "settlement", "well", "waterbody", "cropping", }) @@ -603,7 +625,7 @@ def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) if has_block_col: - qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) + qs = qs.filter(_block_filter_q(block)) raw_rows = list(qs.values(data_field, *projection_fields)) logger.info( From 142ff395198420cddbbcf4da1634bdc41d8af31f Mon Sep 17 00:00:00 2001 From: Aman Verma Date: Thu, 11 Jun 2026 17:51:57 +0530 Subject: [PATCH 22/38] Revert "Hotfix/email timeout (#1010)" (#1012) This reverts commit 0bfb37afbf7e93c572388374c1ea1d917af44bbc. --- plans/tests.py | 12 ------------ plans/utils.py | 24 +----------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/plans/tests.py b/plans/tests.py index d59c6045..bb68993b 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -471,18 +471,6 @@ def test_block_name_with_spaces_matches_underscore_block_param(self): self.assertTrue(result) - def test_block_name_with_underscores_matches_underscore_block_param(self): - _create_settlement(plan_id="42", block_name="test_block", settlement_id="SETT004") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_id"], "SETT004") - def test_only_matching_plan_id_returned(self): _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") diff --git a/plans/utils.py b/plans/utils.py index f4f51403..7e96cfcf 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -5,7 +5,6 @@ import dateutil.parser import requests -from django.db.models import Q from dpr.models import ( ODK_settlement, ODK_well, ODK_waterbody, @@ -38,27 +37,6 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() - -def _block_filter_q(block): - """ - Accept both historical block-name storage styles in the DB: - `foo bar` and `foo_bar`. - """ - raw = (block or "").strip() - if not raw: - return Q() - - variants = { - raw, - raw.replace("_", " ").strip(), - raw.replace(" ", "_").strip(), - } - - query = Q() - for variant in variants: - query |= Q(block_name__icontains=variant) - return query - _RESOURCE_TYPES_FLAT_HEADER = frozenset({ "settlement", "well", "waterbody", "cropping", }) @@ -625,7 +603,7 @@ def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) if has_block_col: - qs = qs.filter(_block_filter_q(block)) + qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) raw_rows = list(qs.values(data_field, *projection_fields)) logger.info( From 654649130bc27b44cd48dadd56fc10c79278b441 Mon Sep 17 00:00:00 2001 From: shiv1122prakash Date: Tue, 16 Jun 2026 16:15:51 +0530 Subject: [PATCH 23/38] updated column with unit (#1001) * updated column with unit * updated sheet not aviable * chnages for handle exception * chnages for drainage density * added for livestock * chnages for excel --- computing/lulc_X_terrain/lulc_on_plain_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 00c073ed..5c706e32 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -31,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters_bk02_june" + + "_lulcXplains_clusters" ) asset_id = get_gee_asset_path(state, district, block) + asset_description From e55854dda621ba0908040da4a4d052b39eb2cda4 Mon Sep 17 00:00:00 2001 From: "shiv.prakash1" Date: Tue, 16 Jun 2026 17:10:24 +0530 Subject: [PATCH 24/38] chnages for conflict --- computing/lulc_X_terrain/lulc_on_plain_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 5c706e32..00c073ed 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -31,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters" + + "_lulcXplains_clusters_bk02_june" ) asset_id = get_gee_asset_path(state, district, block) + asset_description From c418e53936540fd6a2ed224c640bfe87e021b5be Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 16 Jun 2026 06:00:49 -0700 Subject: [PATCH 25/38] remove weasyprint use --- dpr/Plan Uchukabeda.docx | Bin 0 -> 46245 bytes dpr/api.py | 6 ++---- dpr/tasks.py | 24 ++++++++++-------------- dpr/utils.py | 14 +++++++------- dpr/views.py | 17 +++++++++-------- 5 files changed, 28 insertions(+), 33 deletions(-) create mode 100644 dpr/Plan Uchukabeda.docx diff --git a/dpr/Plan Uchukabeda.docx b/dpr/Plan Uchukabeda.docx new file mode 100644 index 0000000000000000000000000000000000000000..e493c9fe1dce9f6be8a0fce5cd6abe74040925ad GIT binary patch literal 46245 zcmZU(b6{lMvIp9+ZCevN6Wg|J+qRR5C!W~0C!9$pM#t79>DYYv&bjBkd+z(EyLYXs z+SP?sd+quu%RxY50ssJ508HwPeywWhw{$Q7U>q6%!1$!gPJ!xyj5x#Q7UW(LWFKRx{6jnSA@j)G?T zmizr7fOxG*pwGXJT2D+IWMGi1RZI?1?q9r7#B^@SeIW6TOa>^uN3<{k-O!Qik2lQ) z#tgd?Ui)N4B`e0ny z^X*TN7x|M|utfgbG846Co?Q-nR|rp5jPa$0WTbIR%CK9b=OlfztCfaJ)7sBcWbaV~ zC)BxphXE$92`%;$=%&i}TQ%KHblZr1kds|Fb;JBIM84LCBVXa^nkKzWPg1Ra_`}e{16^qnrQp+YwBf_Tyn@3Yjo@D>k(?Sr zjjG)uNg2P{zjA-LoQRPJ+!coFt$Y<%4CoOz5}*wc9`<{@LVh_kfm+3VL=35Wkbdng zJMdTwEi#j5$e`1EOD8Hp>;Go*;`fYDUX3R%`12K5zHuNl{y<*zf~jB#Q2wtd`cTUW zKYm7U6C40Q`203rEhIq+}q4=>viQquipYt-u zK_9~Oxe}R*^SxE$<^YYB+pBkP#j`Gm3Z)% zSWwW6xj}`Mkof|35-Tf>8M54Y&%vo+KatNhxwTi~pj$}qNxa23cH=C#!QI*pVHS}R zK@#=tBE&ovK?m!7%@lk6C12vp*V^>(!Ha?C`DTQ+#9-{&8}I40*Q2y!q22ej8zYF zEod8{*$6?NPf&;`VC=#w-&MUjG}*ReL2j-tThpEp2Gxk|QHaPHct2)A6*a=4KVaAO zJU5+&OB=@)7IxPe-Kc_v+|4`K8!+D5CU|HvO#*W_ejtx|30OtJ<)uB`UfHnbq{(2G zi{wJebV<)K^i38WHwS%4z2hwt(m!%vLE@fRAsf=odx^cj4qg-dcU*4Q)^JWg<6;j9 z0HFTwxR^LQ|0^u&6OL;vs68+AkOuCtHZUS8xGAAaBC}!}_zfw!?F-}Nvfm&(S$6{i zo}#h7Ml#>U@HO+jw2Z$1MJ!=j6suED3J%#tu2*Q*A@L5-o|NKuW-B+$ zRIRB}EBZPCl-#c^GFJGN6XH#bHvabm*kH7=1RU~WVw4}$Y(y$ zgi4j9dV6x1Uzv$7;c}jC2tF3?&Pe_{F@7gKU;d^60EWRJ|5dPF zPOj$6pH=JO@Y$UH$qZ0{f!kU~%Kh6rygCz?mD=|#fK2l3`M$bofj8Tet4@BZ?-l`$ z{4Dwy=8uCY&U~YO0NwAV;^ZdC(6nV7I7V6VVv*aic_fbaqk

        a(5g`n_qiIM!Vdj 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 ti~ehsKLW4k^3VOjf8_ Date: Tue, 16 Jun 2026 06:02:35 -0700 Subject: [PATCH 26/38] remove unwanted file --- dpr/Plan Uchukabeda.docx | Bin 46245 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dpr/Plan Uchukabeda.docx diff --git a/dpr/Plan Uchukabeda.docx b/dpr/Plan Uchukabeda.docx deleted file mode 100644 index e493c9fe1dce9f6be8a0fce5cd6abe74040925ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46245 zcmZU(b6{lMvIp9+ZCevN6Wg|J+qRR5C!W~0C!9$pM#t79>DYYv&bjBkd+z(EyLYXs z+SP?sd+quu%RxY50ssJ508HwPeywWhw{$Q7U>q6%!1$!gPJ!xyj5x#Q7UW(LWFKRx{6jnSA@j)G?T zmizr7fOxG*pwGXJT2D+IWMGi1RZI?1?q9r7#B^@SeIW6TOa>^uN3<{k-O!Qik2lQ) z#tgd?Ui)N4B`e0ny z^X*TN7x|M|utfgbG846Co?Q-nR|rp5jPa$0WTbIR%CK9b=OlfztCfaJ)7sBcWbaV~ zC)BxphXE$92`%;$=%&i}TQ%KHblZr1kds|Fb;JBIM84LCBVXa^nkKzWPg1Ra_`}e{16^qnrQp+YwBf_Tyn@3Yjo@D>k(?Sr zjjG)uNg2P{zjA-LoQRPJ+!coFt$Y<%4CoOz5}*wc9`<{@LVh_kfm+3VL=35Wkbdng zJMdTwEi#j5$e`1EOD8Hp>;Go*;`fYDUX3R%`12K5zHuNl{y<*zf~jB#Q2wtd`cTUW zKYm7U6C40Q`203rEhIq+}q4=>viQquipYt-u zK_9~Oxe}R*^SxE$<^YYB+pBkP#j`Gm3Z)% zSWwW6xj}`Mkof|35-Tf>8M54Y&%vo+KatNhxwTi~pj$}qNxa23cH=C#!QI*pVHS}R zK@#=tBE&ovK?m!7%@lk6C12vp*V^>(!Ha?C`DTQ+#9-{&8}I40*Q2y!q22ej8zYF zEod8{*$6?NPf&;`VC=#w-&MUjG}*ReL2j-tThpEp2Gxk|QHaPHct2)A6*a=4KVaAO zJU5+&OB=@)7IxPe-Kc_v+|4`K8!+D5CU|HvO#*W_ejtx|30OtJ<)uB`UfHnbq{(2G zi{wJebV<)K^i38WHwS%4z2hwt(m!%vLE@fRAsf=odx^cj4qg-dcU*4Q)^JWg<6;j9 z0HFTwxR^LQ|0^u&6OL;vs68+AkOuCtHZUS8xGAAaBC}!}_zfw!?F-}Nvfm&(S$6{i zo}#h7Ml#>U@HO+jw2Z$1MJ!=j6suED3J%#tu2*Q*A@L5-o|NKuW-B+$ zRIRB}EBZPCl-#c^GFJGN6XH#bHvabm*kH7=1RU~WVw4}$Y(y$ zgi4j9dV6x1Uzv$7;c}jC2tF3?&Pe_{F@7gKU;d^60EWRJ|5dPF zPOj$6pH=JO@Y$UH$qZ0{f!kU~%Kh6rygCz?mD=|#fK2l3`M$bofj8Tet4@BZ?-l`$ z{4Dwy=8uCY&U~YO0NwAV;^ZdC(6nV7I7V6VVv*aic_fbaqk

        a(5g`n_qiIM!Vdj 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 ti~ehsKLW4k^3VOjf8_ Date: Tue, 16 Jun 2026 13:55:01 +0000 Subject: [PATCH 27/38] rebased staging --- .../lulc_X_terrain/lulc_on_plain_cluster.py | 608 +-- computing/utils.py | 2242 ++++---- computing/views.py | 958 ++-- dpr/tasks.py | 246 +- dpr/utils.py | 1080 ++-- dpr/views.py | 128 +- nrm_app/settings.py | 872 ++-- plans/api.py | 1578 +++--- plans/tests.py | 1112 ++-- plans/utils.py | 1478 +++--- stats_generator/mws_indicators.py | 2260 ++++---- stats_generator/utils.py | 4538 ++++++++--------- 12 files changed, 8550 insertions(+), 8550 deletions(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 00c073ed..1f2d04e6 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -1,304 +1,304 @@ -import ee -from nrm_app.celery import app -from computing.utils import ( - sync_layer_to_geoserver, - save_layer_info_to_db, - update_layer_sync_status, - create_chunk, - merge_chunks, -) -from utilities.gee_utils import ( - ee_initialize, - check_task_status, - valid_gee_text, - get_gee_asset_path, - is_gee_asset_exists, - export_vector_asset_to_gee, - make_asset_public, - get_gee_dir_path, -) -from .utils import aez_lulcXterrain_cluster_centroids, process_mws, calculate_area -from utilities.constants import AEZ, GEE_HELPER_PATH - - -@app.task(bind=True) -def lulc_on_plain_cluster( - self, state, district, block, start_year, end_year, gee_account_id -): - ee_initialize(gee_account_id) - - asset_description = ( - valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_lulcXplains_clusters_bk02_june" - ) - asset_id = get_gee_asset_path(state, district, block) + asset_description - - if not is_gee_asset_exists(asset_id): - aez_india = ee.FeatureCollection(AEZ) - - landforms = ee.Image( - get_gee_asset_path(state, district, block) - + "terrain_raster_" - + valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - ) # The eleven landforms raster - - mwsheds = ee.FeatureCollection( - get_gee_asset_path(state, district, block) - + "filtered_mws_" - + valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_uid" - ) - - filtered_aez = aez_india.filterBounds(mwsheds.geometry()) - - aez_no = filtered_aez.first().get("ae_regcode").getInfo() - - lulc_imgs = [] - for y in range(start_year, end_year + 1): - lulc_img = ee.Image( - get_gee_asset_path(state, district, block) - + valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_" - + str(y) - + "-07-01_" - + str(y + 1) - + "-06-30_LULCmap_10m" - ) - lulc_imgs.append(lulc_img) - - lulc_img_collection = ee.ImageCollection.fromImages(lulc_imgs) - study_area_lulc = lulc_img_collection.mode().clip(mwsheds) - study_area_landforms = landforms.clip(mwsheds) - - mwsheds_with_clusters = process_mws(mwsheds) - plain_mwsheds = mwsheds_with_clusters.filter( - ee.Filter.neq("terrain_cluster", 2) - ) - plain_centroids = aez_lulcXterrain_cluster_centroids[f"aez{aez_no}"]["plains"] - - chunk_size = 50 - rois, descs = create_chunk(mwsheds, asset_description, chunk_size) - - - tasks = [] - temp_assets = [] - for roi, desc in zip(rois, descs): - chunk_with_clusters = process_mws(roi) - plain_chunk = chunk_with_clusters.filter( - ee.Filter.neq("terrain_cluster", 2) - ) - - - result_chunk = process_feature_collection( - plain_chunk, study_area_landforms, study_area_lulc, plain_centroids - ) - - chunk_asset_id = get_gee_dir_path([state, district, block], GEE_HELPER_PATH) + desc - temp_assets.append(chunk_asset_id) - - - task = export_vector_asset_to_gee( - result_chunk, desc, chunk_asset_id - ) - if task: - tasks.append(task) - - - print("Started all chunk tasks") - task_id_list = check_task_status(tasks) - print("All chunk tasks completed:", task_id_list) - - - # Merge all chunks into one feature collection - print("Starting merge task") - final_task_id = merge_chunks( - mwsheds, - [state, district, block], - asset_description, - chunk_size, - merge_asset_id=asset_id, - ) - if final_task_id: - final_task_status = check_task_status([final_task_id]) - print("Final merge task completed:", final_task_status) - - - # Clean up temporary assets - for chunk_id in temp_assets: - if is_gee_asset_exists(chunk_id): - try: - ee.data.deleteAsset(chunk_id) - print(f"Deleted temp asset {chunk_id}") - except Exception as e: - print(f"Failed to delete {chunk_id}: {e}") - - - layer_at_geoserver = False - if is_gee_asset_exists(asset_id): - layer_id = save_layer_info_to_db( - state, - district, - block, - layer_name=f"{valid_gee_text(district.lower())}_{valid_gee_text(block.lower())}_lulc_plain", - asset_id=asset_id, - dataset_name="Terrain LULC", - misc={ - "start_year": start_year, - "end_year": end_year, - }, - ) - make_asset_public(asset_id) - - fc = ee.FeatureCollection(asset_id).getInfo() - fc = {"features": fc["features"], "type": fc["type"]} - res = sync_layer_to_geoserver( - state, - fc, - valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_lulc_plain", - "terrain_lulc", - ) - print(res) - if res["status_code"] == 201 and layer_id: - update_layer_sync_status(layer_id=layer_id, sync_to_geoserver=True) - print("sync to geoserver flag updated") - layer_at_geoserver = True - return layer_at_geoserver - - -def process_feature_collection(fc, landforms, area_lulc, plain_centroids): - """ - Process an entire FeatureCollection by applying the L2 cluster assignment. - """ - return fc.map(lambda f: assign_l2_cluster(f, landforms, area_lulc, plain_centroids)) - - -def assign_l2_cluster(feature, landforms, area_lulc, plain_centroids): - """ - Assigns L2 clusters to features based on landform and land use characteristics. - """ - study_area = feature.geometry() - lf300x2k = landforms.clip(study_area) - - # Get LULC data - lulc = area_lulc.select("predicted_label") - - # # Convert 10 landforms to 4 general landforms - # slopy = lf300x2k.eq(6) - # plains = lf300x2k.eq(5).Or(lf300x2k.gte(12)) - # steep_slopes = lf300x2k.eq(8) - # ridge = lf300x2k.eq(7).Or(lf300x2k.gte(9).And(lf300x2k.lte(11))) - # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) - - # Convert 10 landforms to 4 general landforms - slopy = lf300x2k.eq(6) - plains = lf300x2k.eq(5) - steep_slopes = lf300x2k.eq(8) - # ridge = lf300x2k.gte(9).Or(lf300x2k.eq(7)) - # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) - ridge = lf300x2k.eq(3).Or(lf300x2k.eq(7)).Or(lf300x2k.eq(10)).Or(lf300x2k.eq(11)) - valleys = lf300x2k.eq(1).Or(lf300x2k.eq(2)).Or(lf300x2k.eq(4)).Or(lf300x2k.eq(9)) - - # Calculate areas - plain_area = calculate_area(plains, study_area) - valley_area = calculate_area(valleys, study_area) - hill_slopes_area = calculate_area(steep_slopes, study_area) - slopy_area = calculate_area(slopy, study_area) - - plain_plus_slope_area = plain_area.add(slopy_area) - - # Calculate LULC proportions - def calculate_lulc_proportion(lulc_class): - area_image = ( - plains.eq(1) - .And(lulc.eq(lulc_class)) - .multiply(ee.Image.pixelArea()) - .rename("area") - ) - - area = area_image.reduceRegion( - reducer=ee.Reducer.sum(), geometry=study_area, scale=30, maxPixels=1e10 - ) - - return ee.Number(area.get("area")).divide(1e6).divide(plain_plus_slope_area) - - # Calculate all proportions - plains_barren = calculate_lulc_proportion(7) # Barren - plains_double_crop = calculate_lulc_proportion(10) # Double crop - plains_shrubs_scrubs = calculate_lulc_proportion(12) # Shrubs/scrubs - plains_single_crop = calculate_lulc_proportion(8) # Single crop - plains_single_non_kharif_crop = calculate_lulc_proportion(9) # Single non-kharif - plains_forest = calculate_lulc_proportion(6) # Forest - plains_triple_crop = calculate_lulc_proportion(11) # Triple crop - - # Create feature vector - plain_new_feature_vector = ee.List( - [ - plains_barren, - plains_double_crop, - plains_shrubs_scrubs, - plains_single_crop, - plains_single_non_kharif_crop, - plains_forest, - plains_triple_crop, - ] - ) - - # Convert centroids to ee.List format - centroid_vectors = [ - plain_centroids[str(i)]["cluster_vector"] for i in range(len(plain_centroids)) - ] - ee_centroid_vectors = ee.List(centroid_vectors) - - # Calculate distances - def diff_func(value_pair): - return ( - ee.Number(ee.List(value_pair).get(0)) - .subtract(ee.Number(ee.List(value_pair).get(1))) - .pow(2) - ) - - def calculate_distances(centroid): - centroid_list = ee.List(centroid) - paired_values = centroid_list.zip(plain_new_feature_vector) - return paired_values.map(diff_func).reduce(ee.Reducer.sum()) - - distances_plain = ee_centroid_vectors.map(calculate_distances) - - # Find closest cluster - min_distance_plain = distances_plain.reduce(ee.Reducer.min()) - closest_cluster_index_plain = distances_plain.indexOf(min_distance_plain) - - # Create cluster names dictionary - cluster_names = ee.Dictionary( - { - str(i): plain_centroids[str(i)]["cluster_name"] - for i in range(len(plain_centroids)) - } - ) - - # Set cluster index and name - return ( - feature.set("LxP_cluster", closest_cluster_index_plain) - .set( - "clust_name", - cluster_names.get(closest_cluster_index_plain.format()), - ) - .set("barren", plains_barren.multiply(100)) - .set("double_crop", plains_double_crop.multiply(100)) - .set("shrubs_scrubs", plains_shrubs_scrubs.multiply(100)) - .set("sing_crop", plains_single_crop.multiply(100)) - .set("sing_non_kharif_crop", plains_single_non_kharif_crop.multiply(100)) - .set("forest", plains_forest.multiply(100)) - .set("triple_crop", plains_triple_crop.multiply(100)) - ) +import ee +from nrm_app.celery import app +from computing.utils import ( + sync_layer_to_geoserver, + save_layer_info_to_db, + update_layer_sync_status, + create_chunk, + merge_chunks, +) +from utilities.gee_utils import ( + ee_initialize, + check_task_status, + valid_gee_text, + get_gee_asset_path, + is_gee_asset_exists, + export_vector_asset_to_gee, + make_asset_public, + get_gee_dir_path, +) +from .utils import aez_lulcXterrain_cluster_centroids, process_mws, calculate_area +from utilities.constants import AEZ, GEE_HELPER_PATH + + +@app.task(bind=True) +def lulc_on_plain_cluster( + self, state, district, block, start_year, end_year, gee_account_id +): + ee_initialize(gee_account_id) + + asset_description = ( + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_lulcXplains_clusters_bk02_june" + ) + asset_id = get_gee_asset_path(state, district, block) + asset_description + + if not is_gee_asset_exists(asset_id): + aez_india = ee.FeatureCollection(AEZ) + + landforms = ee.Image( + get_gee_asset_path(state, district, block) + + "terrain_raster_" + + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + ) # The eleven landforms raster + + mwsheds = ee.FeatureCollection( + get_gee_asset_path(state, district, block) + + "filtered_mws_" + + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_uid" + ) + + filtered_aez = aez_india.filterBounds(mwsheds.geometry()) + + aez_no = filtered_aez.first().get("ae_regcode").getInfo() + + lulc_imgs = [] + for y in range(start_year, end_year + 1): + lulc_img = ee.Image( + get_gee_asset_path(state, district, block) + + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_" + + str(y) + + "-07-01_" + + str(y + 1) + + "-06-30_LULCmap_10m" + ) + lulc_imgs.append(lulc_img) + + lulc_img_collection = ee.ImageCollection.fromImages(lulc_imgs) + study_area_lulc = lulc_img_collection.mode().clip(mwsheds) + study_area_landforms = landforms.clip(mwsheds) + + mwsheds_with_clusters = process_mws(mwsheds) + plain_mwsheds = mwsheds_with_clusters.filter( + ee.Filter.neq("terrain_cluster", 2) + ) + plain_centroids = aez_lulcXterrain_cluster_centroids[f"aez{aez_no}"]["plains"] + + chunk_size = 50 + rois, descs = create_chunk(mwsheds, asset_description, chunk_size) + + + tasks = [] + temp_assets = [] + for roi, desc in zip(rois, descs): + chunk_with_clusters = process_mws(roi) + plain_chunk = chunk_with_clusters.filter( + ee.Filter.neq("terrain_cluster", 2) + ) + + + result_chunk = process_feature_collection( + plain_chunk, study_area_landforms, study_area_lulc, plain_centroids + ) + + chunk_asset_id = get_gee_dir_path([state, district, block], GEE_HELPER_PATH) + desc + temp_assets.append(chunk_asset_id) + + + task = export_vector_asset_to_gee( + result_chunk, desc, chunk_asset_id + ) + if task: + tasks.append(task) + + + print("Started all chunk tasks") + task_id_list = check_task_status(tasks) + print("All chunk tasks completed:", task_id_list) + + + # Merge all chunks into one feature collection + print("Starting merge task") + final_task_id = merge_chunks( + mwsheds, + [state, district, block], + asset_description, + chunk_size, + merge_asset_id=asset_id, + ) + if final_task_id: + final_task_status = check_task_status([final_task_id]) + print("Final merge task completed:", final_task_status) + + + # Clean up temporary assets + for chunk_id in temp_assets: + if is_gee_asset_exists(chunk_id): + try: + ee.data.deleteAsset(chunk_id) + print(f"Deleted temp asset {chunk_id}") + except Exception as e: + print(f"Failed to delete {chunk_id}: {e}") + + + layer_at_geoserver = False + if is_gee_asset_exists(asset_id): + layer_id = save_layer_info_to_db( + state, + district, + block, + layer_name=f"{valid_gee_text(district.lower())}_{valid_gee_text(block.lower())}_lulc_plain", + asset_id=asset_id, + dataset_name="Terrain LULC", + misc={ + "start_year": start_year, + "end_year": end_year, + }, + ) + make_asset_public(asset_id) + + fc = ee.FeatureCollection(asset_id).getInfo() + fc = {"features": fc["features"], "type": fc["type"]} + res = sync_layer_to_geoserver( + state, + fc, + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_lulc_plain", + "terrain_lulc", + ) + print(res) + if res["status_code"] == 201 and layer_id: + update_layer_sync_status(layer_id=layer_id, sync_to_geoserver=True) + print("sync to geoserver flag updated") + layer_at_geoserver = True + return layer_at_geoserver + + +def process_feature_collection(fc, landforms, area_lulc, plain_centroids): + """ + Process an entire FeatureCollection by applying the L2 cluster assignment. + """ + return fc.map(lambda f: assign_l2_cluster(f, landforms, area_lulc, plain_centroids)) + + +def assign_l2_cluster(feature, landforms, area_lulc, plain_centroids): + """ + Assigns L2 clusters to features based on landform and land use characteristics. + """ + study_area = feature.geometry() + lf300x2k = landforms.clip(study_area) + + # Get LULC data + lulc = area_lulc.select("predicted_label") + + # # Convert 10 landforms to 4 general landforms + # slopy = lf300x2k.eq(6) + # plains = lf300x2k.eq(5).Or(lf300x2k.gte(12)) + # steep_slopes = lf300x2k.eq(8) + # ridge = lf300x2k.eq(7).Or(lf300x2k.gte(9).And(lf300x2k.lte(11))) + # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) + + # Convert 10 landforms to 4 general landforms + slopy = lf300x2k.eq(6) + plains = lf300x2k.eq(5) + steep_slopes = lf300x2k.eq(8) + # ridge = lf300x2k.gte(9).Or(lf300x2k.eq(7)) + # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) + ridge = lf300x2k.eq(3).Or(lf300x2k.eq(7)).Or(lf300x2k.eq(10)).Or(lf300x2k.eq(11)) + valleys = lf300x2k.eq(1).Or(lf300x2k.eq(2)).Or(lf300x2k.eq(4)).Or(lf300x2k.eq(9)) + + # Calculate areas + plain_area = calculate_area(plains, study_area) + valley_area = calculate_area(valleys, study_area) + hill_slopes_area = calculate_area(steep_slopes, study_area) + slopy_area = calculate_area(slopy, study_area) + + plain_plus_slope_area = plain_area.add(slopy_area) + + # Calculate LULC proportions + def calculate_lulc_proportion(lulc_class): + area_image = ( + plains.eq(1) + .And(lulc.eq(lulc_class)) + .multiply(ee.Image.pixelArea()) + .rename("area") + ) + + area = area_image.reduceRegion( + reducer=ee.Reducer.sum(), geometry=study_area, scale=30, maxPixels=1e10 + ) + + return ee.Number(area.get("area")).divide(1e6).divide(plain_plus_slope_area) + + # Calculate all proportions + plains_barren = calculate_lulc_proportion(7) # Barren + plains_double_crop = calculate_lulc_proportion(10) # Double crop + plains_shrubs_scrubs = calculate_lulc_proportion(12) # Shrubs/scrubs + plains_single_crop = calculate_lulc_proportion(8) # Single crop + plains_single_non_kharif_crop = calculate_lulc_proportion(9) # Single non-kharif + plains_forest = calculate_lulc_proportion(6) # Forest + plains_triple_crop = calculate_lulc_proportion(11) # Triple crop + + # Create feature vector + plain_new_feature_vector = ee.List( + [ + plains_barren, + plains_double_crop, + plains_shrubs_scrubs, + plains_single_crop, + plains_single_non_kharif_crop, + plains_forest, + plains_triple_crop, + ] + ) + + # Convert centroids to ee.List format + centroid_vectors = [ + plain_centroids[str(i)]["cluster_vector"] for i in range(len(plain_centroids)) + ] + ee_centroid_vectors = ee.List(centroid_vectors) + + # Calculate distances + def diff_func(value_pair): + return ( + ee.Number(ee.List(value_pair).get(0)) + .subtract(ee.Number(ee.List(value_pair).get(1))) + .pow(2) + ) + + def calculate_distances(centroid): + centroid_list = ee.List(centroid) + paired_values = centroid_list.zip(plain_new_feature_vector) + return paired_values.map(diff_func).reduce(ee.Reducer.sum()) + + distances_plain = ee_centroid_vectors.map(calculate_distances) + + # Find closest cluster + min_distance_plain = distances_plain.reduce(ee.Reducer.min()) + closest_cluster_index_plain = distances_plain.indexOf(min_distance_plain) + + # Create cluster names dictionary + cluster_names = ee.Dictionary( + { + str(i): plain_centroids[str(i)]["cluster_name"] + for i in range(len(plain_centroids)) + } + ) + + # Set cluster index and name + return ( + feature.set("LxP_cluster", closest_cluster_index_plain) + .set( + "clust_name", + cluster_names.get(closest_cluster_index_plain.format()), + ) + .set("barren", plains_barren.multiply(100)) + .set("double_crop", plains_double_crop.multiply(100)) + .set("shrubs_scrubs", plains_shrubs_scrubs.multiply(100)) + .set("sing_crop", plains_single_crop.multiply(100)) + .set("sing_non_kharif_crop", plains_single_non_kharif_crop.multiply(100)) + .set("forest", plains_forest.multiply(100)) + .set("triple_crop", plains_triple_crop.multiply(100)) + ) diff --git a/computing/utils.py b/computing/utils.py index b0d9a4a8..da6b632b 100644 --- a/computing/utils.py +++ b/computing/utils.py @@ -1,1121 +1,1121 @@ -import copy -import json -import logging -import os -import shutil -import zipfile -from datetime import datetime, timedelta - -import ee -import fiona -import geopandas as gpd -import requests -from django.conf import settings -from shapely.geometry import shape -from shapely.validation import explain_validity - -from computing.models import Dataset, Layer -from geoadmin.models import ( - DistrictSOI, - State_Disritct_Block_Properties, - StateSOI, - TehsilSOI, -) -from projects.models import Project -from utilities.constants import ( - ADMIN_BOUNDARY_OUTPUT_DIR, - GEE_ASSET_PATH, - GEE_HELPER_PATH, - GEE_PATHS, - SHAPEFILE_DIR, -) -from utilities.gee_utils import ( - check_task_status, - ee_initialize, - get_gee_asset_path, - get_gee_dir_path, - get_geojson_from_gcs, - is_asset_public, - is_gee_asset_exists, - sync_vector_to_gcs, - valid_gee_text, -) -from utilities.geoserver_utils import Geoserver -from django.core.mail import EmailMessage, get_connection -import time - -logger = logging.getLogger(__name__) - - -def generate_shape_files(path): - gdf = gpd.read_file(path + ".json") - if os.path.exists(path): - # Only replace the target shapefile directory. Removing the parent - # state/workspace directory here corrupts sibling outputs on reruns. - shutil.rmtree(path) - - os.makedirs(os.path.dirname(path), exist_ok=True) - gdf.to_file( - path, - driver="ESRI Shapefile", - ) - return path - - -def convert_to_zip(dir_name, file_type): - if file_type == "gpkg": - with zipfile.ZipFile(dir_name + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf: - zipf.write(dir_name + ".gpkg", arcname=os.path.basename(dir_name + ".gpkg")) - return dir_name + ".zip" - else: - return shutil.make_archive(dir_name, "zip", dir_name + "/") - - -def push_shape_to_geoserver( - path, store_name=None, workspace=None, layer_name=None, file_type="shp" -): - geo = Geoserver() - - print(f"layer_name: {layer_name}") - if layer_name: - try: - print(f"Attempting to delete store: {layer_name}") - geo.delete_vector_store(workspace=workspace, store=layer_name) - print(f"Successfully deleted store: {layer_name}") - except Exception as e: - print(f"Store does not exist or error deleting: {str(e)}") - - zip_path = convert_to_zip(path, file_type) - print(f"Zip path: {zip_path}") - print(f"Store name: {store_name}") - print(f"Workspace: {workspace}") - - response = geo.create_shp_datastore( - path=zip_path, - store_name=store_name, - workspace=workspace, - file_extension=file_type, - ) - print(f"Response: {response}") - return response - - -def kml_to_geojson(state_name, district_name, block_name, kml_path): - fiona.drvsupport.supported_drivers["kml"] = ( - "rw" # enable KML support which is disabled by default - ) - fiona.drvsupport.supported_drivers["KML"] = ( - "rw" # enable KML support which is disabled by default - ) - gdf = gpd.read_file(kml_path) - geometry_types = gdf.geometry.geometry.type.unique() - state_dir = os.path.join(ADMIN_BOUNDARY_OUTPUT_DIR, state_name) - - for gtype in geometry_types: - df = gdf.loc[gdf.geometry.geometry.type == gtype] - path = os.path.join(state_dir, f"{district_name}_{block_name}_{gtype}") - df.to_file(path + ".json", driver="GeoJSON") - generate_shape_files(path) - push_shape_to_geoserver(path, workspace="test_workspace") - - -def convert_kml_to_shapefile(kml_path, output_dir, shapefile_name): - if not os.path.exists(output_dir + "/" + shapefile_name): - os.makedirs(output_dir + "/" + shapefile_name) - - shapefile_path = os.path.join( - output_dir + "/" + shapefile_name, shapefile_name + ".shp" - ) - print("path path", shapefile_path) - cmd = f"ogr2ogr -f 'ESRI Shapefile' {shapefile_path} {kml_path}" # output.shp input.kml - os.system(command=cmd) - - return output_dir + "/" + shapefile_name - - -def kml_to_shp(state_name, district_name, block_name, kml_path): - shapefile_name = f"{district_name}_{block_name}" - shapefile_layer_path = convert_kml_to_shapefile( - kml_path, SHAPEFILE_DIR, shapefile_name - ) - - push_shape_to_geoserver(shapefile_layer_path, workspace="customkml") - - # os.remove(kml_path) - # shutil.rmtree(shapefile_layer_path) - os.remove(shapefile_layer_path + ".zip") - - -def sync_layer_to_geoserver(state_name, fc, layer_name, workspace): - state_dir = os.path.join("data/fc_to_shape", state_name) - if not os.path.exists(state_dir): - os.mkdir(state_dir) - path = os.path.join(state_dir, f"{layer_name}") - # Write the feature collection into json file - with open(path + ".json", "w") as f: - try: - f.write(f"{json.dumps(fc)}") - except Exception as e: - print(e) - - path = generate_shape_files(path) - return push_shape_to_geoserver(path, workspace=workspace, layer_name=layer_name) - - -def sync_fc_to_geoserver(fc, shp_folder, layer_name, workspace, style_name=None): - try: - geojson_fc = fc.getInfo() - except Exception as e: - print("Exception in getInfo()", e) - task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") - check_task_status([task_id]) - - geojson_fc = get_geojson_from_gcs(layer_name) - geo = Geoserver() - if len(geojson_fc["features"]) > 0: - state_dir = os.path.join("data/fc_to_shape", shp_folder) - if not os.path.exists(state_dir): - os.mkdir(state_dir) - path = os.path.join(state_dir, f"{layer_name}") - - # Convert to GeoDataFrame - gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) - - # Set CRS (Earth Engine uses EPSG:4326 by default) - gdf.crs = "EPSG:4326" - - gdf = fix_invalid_geometry_in_gdf(gdf) - - # Save as GeoPackage - gdf.to_file(path + ".gpkg", driver="GPKG") - res = push_shape_to_geoserver(path, workspace=workspace, file_type="gpkg") - if style_name: - style_res = geo.publish_style( - layer_name=layer_name, style_name=style_name, workspace=workspace - ) - print("Style response:", style_res) - return res - else: - return "No features in FeatureCollection" - - -def sync_project_fc_to_geoserver(fc, project_name, layer_name, workspace): - print("inside") - print(layer_name) - try: - geojson_fc = fc.getInfo() - except Exception as e: - print("Exception in getInfo()", e) - task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") - check_task_status([task_id]) - - geojson_fc = get_geojson_from_gcs(layer_name) - print(len(geojson_fc["features"])) - if len(geojson_fc["features"]) > 0: - state_dir = os.path.join("data/fc_to_shape", project_name) - if not os.path.exists(state_dir): - os.mkdir(state_dir) - path = os.path.join(state_dir, f"{layer_name}") - - # Convert to GeoDataFrame - gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) - - # Set CRS (Earth Engine uses EPSG:4326 by default) - gdf.crs = "EPSG:4326" - - gdf = fix_invalid_geometry_in_gdf(gdf) - - # Save as GeoPackage - gdf.to_file(path + ".gpkg", driver="GPKG") - print("pushed to geoserver") - return push_shape_to_geoserver( - path, workspace=workspace, layer_name=layer_name, file_type="gpkg" - ) - else: - print("no features found") - return - - -def to_camelcase(text): - words = text.split() - camelcase = words[0].lower() - for word in words[1:]: - camelcase += word.capitalize() - return camelcase - - -def create_chunk(aoi, description, chunk_size): - size = aoi.size().getInfo() - parts = size // chunk_size - # task_ids = [] - rois = [] - descs = [] - for part in range(parts + 1): - start = part * chunk_size - end = start + chunk_size - block_name_for_parts = description + "_" + str(start) + "-" + str(end) - roi = ee.FeatureCollection(aoi.toList(aoi.size()).slice(start, end)) - if roi.size().getInfo() > 0: - descs.append(block_name_for_parts) - rois.append(roi) - - return rois, descs - - -def merge_chunks( - aoi, - folder_list, - description, - chunk_size, - chunk_asset_path=GEE_HELPER_PATH, - merge_asset_path=GEE_ASSET_PATH, - merge_asset_id=None, -): - print("Merge Chunk task initiated") - ee_initialize() - size = aoi.size().getInfo() - parts = size // chunk_size - assets = [] - for part in range(parts + 1): - start = part * chunk_size - end = start + chunk_size - block_name_for_parts = description + "_" + str(start) + "-" + str(end) - src_asset_id = ( - get_gee_dir_path(folder_list, chunk_asset_path) + block_name_for_parts - ) - if is_gee_asset_exists(src_asset_id): - assets.append(ee.FeatureCollection(src_asset_id)) - - asset = ee.FeatureCollection(assets).flatten() - - asset_id = merge_asset_id or ( - get_gee_dir_path(folder_list, merge_asset_path) + description - ) - try: - # Export an ee.FeatureCollection as an Earth Engine asset. - task = ee.batch.Export.table.toAsset( - **{ - "collection": asset, - "description": description, - "assetId": asset_id, - } - ) - - task.start() - print("Successfully started the merge chunk", task.status()) - return task.status()["id"] - except Exception as e: - print(f"Error occurred in running merge task: {e}") - return None - - -def fix_invalid_geometry_in_gdf(gdf): - invalid = gdf[~gdf.is_valid] - if not invalid.empty: - print("Invalid geometries found:") - for idx, geom in invalid.geometry.items(): - print(f"Index {idx}: {explain_validity(geom)}") - gdf.loc[idx, "geometry"] = gdf.loc[idx, "geometry"].buffer(0) - - return gdf - - -def get_season_key(date): - """Return season key like 'rabi_2017-2018' based on Indian cropping seasons.""" - month = date.month - year = date.year - next_year = year + 1 - - if month in [1, 2]: - return f"rabi_{year - 1}-{year}" # Jan–Feb → Rabi of previous year - elif month in [11, 12]: - return f"rabi_{year}-{next_year}" # Nov–Dec → Rabi starting this year - elif month in [3, 4, 5, 6]: - return f"zaid_{year}-{next_year}" - elif month in [7, 8, 9, 10]: - return f"kharif_{year}-{next_year}" - else: - return None - - -def get_agri_year_key(season_key): - """Convert a season key to agricultural year key (e.g., rabi_2017-2018 → 2017-2018).""" - season, years = season_key.split("_") - start_year, end_year = map(int, years.split("-")) - - if season in ["kharif", "rabi"]: - return f"{start_year}-{end_year}" - elif season == "zaid": - return f"{start_year - 1}-{start_year}" # Zaid 2018-2019 → Agri year 2017-2018 - else: - return None - - -def calculate_precipitation_season( - geojson_filepath, draught_asset_id, start_year=2017, end_year=2024 -): - - # Load the GeoJSON file - with open(geojson_filepath, "r") as f: - feature_collection = json.load(f) - - features_ee = [] - - for feature in feature_collection["features"]: - original_props = feature["properties"] - new_props = {} - - # Copy UID - if "uid" in original_props: - new_props["uid"] = original_props["uid"] - - agri_year_totals = {} - - # Parse precipitation date keys - for key, val in original_props.items(): - try: - date = datetime.strptime(key, "%Y-%m-%d") - season_key = get_season_key(date) - if not season_key: - continue - - agri_key = get_agri_year_key(season_key) - if not agri_key: - continue - - agri_start = int(agri_key.split("-")[0]) - if not (start_year <= agri_start <= end_year): - continue - - season = season_key.split("_")[0] # kharif, rabi etc - full_key = f"{season}_{agri_key}" - - agri_year_totals[full_key] = agri_year_totals.get(full_key, 0) + float( - val - ) - - except Exception: - continue - - # Add all seasonal totals to new_props - for agri_key, total in agri_year_totals.items(): - new_props[f"precipitation_{agri_key}"] = total - - # Create EE Feature - geom_ee = ee.Geometry(feature["geometry"]) - feature_ee = ee.Feature(geom_ee, new_props) - features_ee.append(feature_ee) - - # Left side FC - mws_fc = ee.FeatureCollection(features_ee) - - return mws_fc - - -def generate_geojson_with_ci_and_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): - # Load project - proj_obj = Project.objects.get(pk=proj_id) - - # Build CI and NDVI asset paths - asset_path_ci = ( - get_gee_dir_path( - [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] - ) - + ci_asset - ) - - asset_path_ndvi = ( - get_gee_dir_path( - [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] - ) - + ndvi_asset - ) - - # Load FeatureCollections - zoi = ee.FeatureCollection(zoi_asset) - ci = ee.FeatureCollection(asset_path_ci) - ndvi = ee.FeatureCollection(asset_path_ndvi) - - # ------------------------- - # STEP 1: Join ZOI with Cropping Intensity - # ------------------------- - join = ee.Join.inner() - filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") - zoi_ci_joined = join.apply(zoi, ci, filter) - - def merge_zoi_ci(pair): - zoi_feat = ee.Feature(pair.get("primary")) - ci_feat = ee.Feature(pair.get("secondary")) - merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) - return ee.Feature(zoi_feat.geometry(), merged_props) - - zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) - - # ------------------------- - # STEP 2: Join ZOI+CI with NDVI - # ------------------------- - zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) - - def merge_zoi_ci_ndvi(pair): - ci_feat = ee.Feature(pair.get("primary")) - ndvi_feat = ee.Feature(pair.get("secondary")) - merged_props = ci_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) - return ee.Feature(ci_feat.geometry(), merged_props) - - final_merged = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) - - # ------------------------- - # STEP 3: Export or Push to GeoServer - # ------------------------- - layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" - sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") - - -def get_directory_size(path): - total_size = 0 - for dirpath, dirnames, filenames in os.walk(path): - for filename in filenames: - file_path = os.path.join(dirpath, filename) - if os.path.isfile(file_path): - total_size += os.path.getsize(file_path) - return total_size - - -def generate_geojson_with_ci_ndvi_ndmi( - zoi_asset, ci_asset, ndvi_asset, ndmi_asset, proj_id -): - - # Load project - proj_obj = Project.objects.get(pk=proj_id) - - zoi = ee.FeatureCollection(zoi_asset) - print("Number of features zoi:", zoi.size().getInfo()) - - ci = ee.FeatureCollection(ci_asset) - print("Number of features zoi:", ci.size().getInfo()) - ndvi = ee.FeatureCollection(ndmi_asset) - print("Number of features zoi:", ndvi.size().getInfo()) - ndmi = ee.FeatureCollection(ndmi_asset) - print("Number of features zoi:", ndmi.size().getInfo()) - - # ------------------------- - # STEP 1: Join ZOI with CI - # ------------------------- - join = ee.Join.inner() - filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") - zoi_ci_joined = join.apply(zoi, ci, filter) - - def merge_zoi_ci(pair): - zoi_feat = ee.Feature(pair.get("primary")) - ci_feat = ee.Feature(pair.get("secondary")) - merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) - return ee.Feature(zoi_feat.geometry(), merged_props) # ✅ keep ZOI geom - - zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) - - # ------------------------- - # STEP 2: Join with NDVI - # ------------------------- - zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) - - def merge_zoi_ci_ndvi(pair): - prev_feat = ee.Feature(pair.get("primary")) - ndvi_feat = ee.Feature(pair.get("secondary")) - merged_props = prev_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) - return ee.Feature(prev_feat.geometry(), merged_props) # ✅ still ZOI geom - - zoi_ci_ndvi = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) - - # ------------------------- - # STEP 3: Join with NDMI - # ------------------------- - zoi_ndmi_joined = join.apply(zoi_ci_ndvi, ndmi, filter) - - def merge_zoi_ci_ndvi_ndmi(pair): - prev_feat = ee.Feature(pair.get("primary")) - ndmi_feat = ee.Feature(pair.get("secondary")) - merged_props = prev_feat.toDictionary().combine(ndmi_feat.toDictionary(), True) - return ee.Feature(prev_feat.geometry(), merged_props) # ✅ keep ZOI geom - - final_merged = ee.FeatureCollection(zoi_ndmi_joined.map(merge_zoi_ci_ndvi_ndmi)) - - # ------------------------- - # STEP 4: Export or Push to GeoServer - # ------------------------- - layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" - print(layer_name) - sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") - - -def generate_geojson_with_ci_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): - # Load project - proj_obj = Project.objects.get(pk=proj_id) - - # Initialize Earth Engine - ee_initialize(4) - - # Load FeatureCollections - zoi = ee.FeatureCollection(zoi_asset) - ci = ee.FeatureCollection(ci_asset) - ndvi = ee.FeatureCollection(ndvi_asset) - - print("ZOI:", zoi.size().getInfo()) - print("CI:", ci.size().getInfo()) - print("NDVI:", ndvi.size().getInfo()) - - # Common join logic on UID - join = ee.Join.inner() - uid_filter = ee.Filter.equals(leftField="UID", rightField="UID") - - # --- Join ZOI + CI --- - zoi_ci_joined = join.apply(zoi, ci, uid_filter) - - def merge_zoi_ci(pair): - zoi_feat = ee.Feature(pair.get("primary")) - ci_feat = ee.Feature(pair.get("secondary")) - merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) - # Keep ZOI geometry only - return ee.Feature(zoi_feat.geometry(), merged_props) - - zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) - - # --- Join with NDVI --- - zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, uid_filter) - - def merge_with_ndvi(pair): - base_feat = ee.Feature(pair.get("primary")) - ndvi_feat = ee.Feature(pair.get("secondary")) - merged_props = base_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) - # Always retain ZOI geometry - return ee.Feature(base_feat.geometry(), merged_props) - - merged_final = ee.FeatureCollection(zoi_ndvi_joined.map(merge_with_ndvi)) - - # --- Ensure ZOI geometry retained in all features --- - merged_final = merged_final.map( - lambda f: ee.Feature( - f.setGeometry( - ee.Feature( - zoi.filter(ee.Filter.eq("UID", f.get("UID"))).first() - ).geometry() - ) - ) - ) - - layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" - print(layer_name) - - sync_project_fc_to_geoserver(merged_final, proj_obj.name, layer_name, "waterrej") - - -def save_layer_info_to_db( - state, - district, - block, - layer_name, - asset_id, - dataset_name, - sync_to_geoserver=False, - layer_version="1.0", - algorithm=None, - algorithm_version="1.0", - misc=None, - is_override=False, -): - print("inside the save_layer_info_to_db function") - - dataset = Dataset.objects.get(name=dataset_name) - - try: - state_obj = StateSOI.objects.get(state_name__iexact=state) - district_obj = DistrictSOI.objects.get( - district_name__iexact=district, state=state_obj - ) - block_obj = TehsilSOI.objects.get( - tehsil_name__iexact=block, district=district_obj - ) - except Exception as e: - print("Error fetching in state district block:", e) - return - - is_public = is_asset_public(asset_id) - - # Check if there’s an existing layer - existing_layer = ( - Layer.objects.filter( - dataset=dataset, - layer_name=layer_name, - state=state_obj, - district=district_obj, - block=block_obj, - ) - .order_by("-layer_version") - .first() - ) - - if existing_layer: - if existing_layer.algorithm_version != algorithm_version: - # Algorithm version changed --> create new record with incremented layer_version - new_layer_version = str(float(existing_layer.layer_version) + 1) - print( - f"Algorithm version changed. Creating new layer version: {new_layer_version}" - ) - layer_obj = Layer.objects.create( - dataset=dataset, - layer_name=layer_name, - state=state_obj, - district=district_obj, - block=block_obj, - layer_version=new_layer_version, - algorithm=algorithm, - algorithm_version=algorithm_version, - is_sync_to_geoserver=sync_to_geoserver, - is_public_gee_asset=is_public, - is_override=is_override, - misc=misc, - gee_asset_path=asset_id, - ) - else: - # Algorithm version is same --> update existing layer - print("Algorithm version same. Updating existing layer.") - for field, value in { - "algorithm": algorithm, - "algorithm_version": algorithm_version, - "is_sync_to_geoserver": sync_to_geoserver, - "is_public_gee_asset": is_public, - "is_override": is_override, - "misc": misc, - "gee_asset_path": asset_id, - }.items(): - setattr(existing_layer, field, value) - existing_layer.save() - layer_obj = existing_layer - else: - # No existing record --> create a new one - print("No existing layer found. Creating new one.") - layer_obj = Layer.objects.create( - dataset=dataset, - layer_name=layer_name, - state=state_obj, - district=district_obj, - block=block_obj, - layer_version=layer_version, - algorithm=algorithm, - algorithm_version=algorithm_version, - is_sync_to_geoserver=sync_to_geoserver, - is_public_gee_asset=is_public, - is_override=is_override, - misc=misc, - gee_asset_path=asset_id, - ) - - print(f"Saved layer info (id={layer_obj.id}, version={layer_obj.layer_version})") - return layer_obj.id - - -def get_existing_end_year(dataset_name, layer_name): - """fetch objects from db on the basis of dataset name and layer_name""" - dataset = Dataset.objects.get(name=dataset_name) - layer_obj = Layer.objects.get(dataset=dataset, layer_name=layer_name) - existing_end_date = layer_obj.misc["end_year"] - print("existing_end_date", existing_end_date) - return existing_end_date - - -def get_layer_object(state, district, block, layer_name, dataset_name): - state_obj = StateSOI.objects.get(state_name__iexact=state) - district_obj = DistrictSOI.objects.get( - district_name__iexact=district, state=state_obj - ) - block_obj = TehsilSOI.objects.get(tehsil_name__iexact=block, district=district_obj) - layer_obj = ( - Layer.objects.filter( - state=state_obj, - district=district_obj, - block=block_obj, - layer_name=layer_name, - dataset__name=dataset_name, - ) - .order_by("-layer_version") - .first() - ) - return layer_obj - - -def update_dashboard_geojson( - state=None, - district=None, - block=None, - layer_name=None, - workspace_name=None, - proj_id=None, -): - if state and block and block: - print(f"🔄 Updating GeoJSON for {state}, {district}, {block}") - - # Get related objects - state_obj = StateSOI.objects.get(state_name=state) - district_obj = DistrictSOI.objects.get(district_name=district) - tehsil_obj = TehsilSOI.objects.get(tehsil_name=block) # fixed typo - - # Get or create main record - obj, created = State_Disritct_Block_Properties.objects.get_or_create( - state=state_obj, district=district_obj, tehsil=tehsil_obj - ) - else: - obj = Project.objects.get(pk=proj_id) - - # Map suffix to json_key - suffix_to_key = { - "wb": "wb_geojson", - "zoi": "zoi_geojson", - "mws": "mws_geojson", - } - - # Detect which key this layer corresponds to - json_key = None - for suffix, key in suffix_to_key.items(): - if layer_name == f"{state}_{district}_{block}_{suffix}": - json_key = key - break - - if not json_key: - print(f"⚠️ Layer name {layer_name} did not match any known type.") - return - - # Construct GeoServer URL - waterrej_url = ( - f"https://geoserver.core-stack.org:8443/geoserver/waterrej/ows?" - f"service=WFS&version=1.0.0&request=GetFeature&typeName={workspace_name}:{layer_name}" - f"&outputFormat=application%2Fjson" - ) - - # Load existing dashboard_geojson or create new - if proj_id: - misc = obj.dashboard_geojson or {} - else: - misc = obj.geojson_path or {} - - # Ensure waterrej section exists - if "waterrej" not in misc: - misc["waterrej"] = {} - - # Update or add this specific json_key - misc["waterrej"][json_key] = waterrej_url - - # Save the updated JSON field - obj.dashboard_geojson = misc - obj.save() - - print(f"✅ Added/Updated {json_key} for {state}, {district}, {block}") - - -def clean_geometry(geom): - """ - Clean geometry: - - Dissolve multipolygon → single polygon - - Remove holes automatically - - Fix invalid topology - - Buffer tiny polygons - """ - - # 1. Dissolve multi-polygons and remove holes - geom = geom.dissolve(maxError=1) - - # 2. Fix invalid rings by simplifying slightly (NEVER buffer(0)) - geom = geom.simplify(1) - - # 3. Buffer polygons smaller than 1 pixel (< 900 m²) - area = geom.area() - geom = ee.Algorithms.If( - area.lt(900), - geom.buffer(15), - geom, # ensure raster pixel center is captured - ) - - return ee.Geometry(geom) - - -def safe_reduce_max(image, geom, scale=30): - geom = clean_geometry(geom) - - val = ( - image.unmask(0) - .reduceRegion( - reducer=ee.Reducer.max(), - geometry=geom, - scale=scale, - maxPixels=1e13, - tileScale=4, - bestEffort=True, - ) - .get("b1") - ) - - return ee.Number(ee.Algorithms.If(val, val, 0)) - - -# ------------------------------------------------------ -# SAFE REDUCE MAX FUNCTION -# ------------------------------------------------------ -def safe_reduce_max(image, geom, scale=30): - geom = clean_geometry(geom) - - result = ( - image.unmask(0) - .reduceRegion( - reducer=ee.Reducer.max(), - geometry=geom, - scale=scale, - maxPixels=1e13, - tileScale=4, - bestEffort=True, - ) - .get("b1") - ) - - # Convert null → 0 - return ee.Number(ee.Algorithms.If(result, result, 0)) - - -# ------------------------------------------------------ -# MAIN FUNCTION TO PROCESS SWB LAYER -# ------------------------------------------------------ -def generate_swb_layer_with_max_so_catchment( - roi=None, - app_type="MWS", - asset_suffix=None, - asset_folder=None, - gee_account_id=None, -): - ee_initialize(gee_account_id) - - # Build asset paths - base_path = get_gee_dir_path( - asset_folder, asset_path=GEE_PATHS[app_type]["GEE_ASSET_PATH"] - ) - - so_asset = f"{base_path}stream_order_{asset_suffix}_raster" - ca_asset = f"{base_path}catchment_area_{asset_suffix}_raster" - - # Load rasters - stream_order_band = ee.Image(so_asset).select("b1") - catchment_band = ee.Image(ca_asset).select("b1") - - # Processing per waterbody - def compute_for_feature(feature): - geom = feature.geometry() - - max_so = safe_reduce_max(stream_order_band, geom, scale=30) - max_ca = safe_reduce_max(catchment_band, geom, scale=30) - - return feature.set( - { - "max_stream_order": max_so, - "max_catchment_area": max_ca, - } - ) - - # Map over the feature collection - return roi.map(compute_for_feature) - - -def _get_prod_backend_url(): - return getattr(settings, "PROD_BACKEND_URL", "").rstrip("/") - - -def _get_prod_api_key(): - return getattr(settings, "PROD_BACKEND_API_KEY", "") - - -def _sync_layer_to_prod_db(payload: dict): - prod_url = _get_prod_backend_url() - if not prod_url: - return None - - endpoint = prod_url + "/api/v1/sync_layer_remote/" - try: - response = requests.post( - endpoint, - json=payload, - headers={"X-Api-Key": _get_prod_api_key()}, - timeout=30, - ) - if response.status_code not in (200, 201): - logger.warning( - "Prod DB sync returned %s for layer %s: %s", - response.status_code, - payload.get("layer_name"), - response.text, - ) - return None - layer_id = response.json().get("layer_id") - logger.info( - "Layer %s synced to prod DB (id=%s).", payload.get("layer_name"), layer_id - ) - return layer_id - except requests.RequestException as e: - logger.error( - "Failed to sync layer %s to prod DB: %s", payload.get("layer_name"), e - ) - return None - - -def _update_layer_sync_remote( - layer_id, sync_to_geoserver=None, is_stac_specs_generated=None -): - prod_url = _get_prod_backend_url() - if not prod_url or layer_id is None: - return - - endpoint = prod_url + "/api/v1/update_layer_sync_remote/" - payload = { - "layer_id": layer_id, - "sync_to_geoserver": sync_to_geoserver, - "is_stac_specs_generated": is_stac_specs_generated, - } - try: - response = requests.post( - endpoint, - json=payload, - headers={"X-Api-Key": _get_prod_api_key()}, - timeout=30, - ) - if response.status_code not in (200, 201): - logger.warning( - "Prod layer sync status update returned %s for layer %s: %s", - response.status_code, - layer_id, - response.text, - ) - else: - logger.info("Layer sync status updated on prod DB for id=%s.", layer_id) - except requests.RequestException as e: - logger.error( - "Failed to update layer sync status on prod DB for id=%s: %s", layer_id, e - ) - - -def update_layer_sync_status( - layer_id, sync_to_geoserver=None, is_stac_specs_generated=None -): - if _get_prod_backend_url(): - _update_layer_sync_remote( - layer_id, - sync_to_geoserver=sync_to_geoserver, - is_stac_specs_generated=is_stac_specs_generated, - ) - return layer_id - - try: - layer_obj = Layer.objects.filter(id=layer_id).first() - if layer_obj is None: - return None - - update_fields = [] - if sync_to_geoserver is not None: - layer_obj.is_sync_to_geoserver = sync_to_geoserver - update_fields.append("is_sync_to_geoserver") - if is_stac_specs_generated is not None: - layer_obj.is_stac_specs_generated = is_stac_specs_generated - update_fields.append("is_stac_specs_generated") - - # `save(update_fields=...)` fires the post_save signal so the STAC - # auto-trigger handler in `computing.signals` can pick up the flip. - if update_fields: - layer_obj.save(update_fields=update_fields) - print( - f"Updated {update_fields} for layer ID: {layer_id} " - f"(sync={sync_to_geoserver}, stac={is_stac_specs_generated})" - ) - return layer_id - - except Exception as e: - print(f"Error updating layer sync status: {e}") - - -# send missing layer to recipient email -def send_missing_layers_report(result: dict, recipients: list = None) -> bool: - if recipients is None: - recipients = getattr(settings, "MISSING_LAYER_RECIPIENTS", []) - - if isinstance(recipients, str): - recipients = [recipients] - - if not recipients: - logger.error("No recipients configured for missing layers report.") - return False - - summary = [] - total_missing = 0 - - for layer, data in result.items(): - count = len(data.get("missing_layers", [])) - total_missing += count - summary.append(f"{layer}: {count}") - body = ( - "Missing Layers Report\n\n" - f"Total Missing: {total_missing}\n\n" - + "\n".join(summary) - + "\n\nDetailed report attached." - ) - - attachment_content = json.dumps(result, indent=4) - max_retries = 3 - - for attempt in range(max_retries): - connection = None - try: - connection = get_connection(timeout=120) - connection.open() - email = EmailMessage( - subject="Missing Layers Report", - body=body, - from_email=settings.EMAIL_HOST_USER, - to=recipients, - connection=connection, - ) - email.attach( - "missing_layers.json", - attachment_content, - "application/json", - ) - email.send() - logger.info(f"Missing layers report sent to {recipients}") - logger.info( - f"Attachment size: " - f"{len(attachment_content.encode('utf-8')) / 1024:.2f} KB" - ) - return True - except Exception as e: - logger.exception(f"Attempt {attempt + 1}/{max_retries} failed: {e}") - if attempt < max_retries - 1: - wait_time = 5 * (attempt + 1) - logger.info(f"Retrying after {wait_time} seconds...") - time.sleep(wait_time) - else: - logger.error("All attempts to send email failed.") - return False - finally: - if connection: - try: - connection.close() - except Exception: - pass - - -def _is_cache_valid(cache: dict, workspace: str) -> bool: - if workspace not in cache: - return False - age = time.time() - cache[workspace]["cached_at"] - if age > 3600: - logger.info(f"Cache expired for {workspace} (age: {int(age)}s)") - return False - return True - - -def _set_cache(cache: dict, workspace: str, data: set): - cache[workspace] = { - "data": data, - "cached_at": time.time(), - } +import copy +import json +import logging +import os +import shutil +import zipfile +from datetime import datetime, timedelta + +import ee +import fiona +import geopandas as gpd +import requests +from django.conf import settings +from shapely.geometry import shape +from shapely.validation import explain_validity + +from computing.models import Dataset, Layer +from geoadmin.models import ( + DistrictSOI, + State_Disritct_Block_Properties, + StateSOI, + TehsilSOI, +) +from projects.models import Project +from utilities.constants import ( + ADMIN_BOUNDARY_OUTPUT_DIR, + GEE_ASSET_PATH, + GEE_HELPER_PATH, + GEE_PATHS, + SHAPEFILE_DIR, +) +from utilities.gee_utils import ( + check_task_status, + ee_initialize, + get_gee_asset_path, + get_gee_dir_path, + get_geojson_from_gcs, + is_asset_public, + is_gee_asset_exists, + sync_vector_to_gcs, + valid_gee_text, +) +from utilities.geoserver_utils import Geoserver +from django.core.mail import EmailMessage, get_connection +import time + +logger = logging.getLogger(__name__) + + +def generate_shape_files(path): + gdf = gpd.read_file(path + ".json") + if os.path.exists(path): + # Only replace the target shapefile directory. Removing the parent + # state/workspace directory here corrupts sibling outputs on reruns. + shutil.rmtree(path) + + os.makedirs(os.path.dirname(path), exist_ok=True) + gdf.to_file( + path, + driver="ESRI Shapefile", + ) + return path + + +def convert_to_zip(dir_name, file_type): + if file_type == "gpkg": + with zipfile.ZipFile(dir_name + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf: + zipf.write(dir_name + ".gpkg", arcname=os.path.basename(dir_name + ".gpkg")) + return dir_name + ".zip" + else: + return shutil.make_archive(dir_name, "zip", dir_name + "/") + + +def push_shape_to_geoserver( + path, store_name=None, workspace=None, layer_name=None, file_type="shp" +): + geo = Geoserver() + + print(f"layer_name: {layer_name}") + if layer_name: + try: + print(f"Attempting to delete store: {layer_name}") + geo.delete_vector_store(workspace=workspace, store=layer_name) + print(f"Successfully deleted store: {layer_name}") + except Exception as e: + print(f"Store does not exist or error deleting: {str(e)}") + + zip_path = convert_to_zip(path, file_type) + print(f"Zip path: {zip_path}") + print(f"Store name: {store_name}") + print(f"Workspace: {workspace}") + + response = geo.create_shp_datastore( + path=zip_path, + store_name=store_name, + workspace=workspace, + file_extension=file_type, + ) + print(f"Response: {response}") + return response + + +def kml_to_geojson(state_name, district_name, block_name, kml_path): + fiona.drvsupport.supported_drivers["kml"] = ( + "rw" # enable KML support which is disabled by default + ) + fiona.drvsupport.supported_drivers["KML"] = ( + "rw" # enable KML support which is disabled by default + ) + gdf = gpd.read_file(kml_path) + geometry_types = gdf.geometry.geometry.type.unique() + state_dir = os.path.join(ADMIN_BOUNDARY_OUTPUT_DIR, state_name) + + for gtype in geometry_types: + df = gdf.loc[gdf.geometry.geometry.type == gtype] + path = os.path.join(state_dir, f"{district_name}_{block_name}_{gtype}") + df.to_file(path + ".json", driver="GeoJSON") + generate_shape_files(path) + push_shape_to_geoserver(path, workspace="test_workspace") + + +def convert_kml_to_shapefile(kml_path, output_dir, shapefile_name): + if not os.path.exists(output_dir + "/" + shapefile_name): + os.makedirs(output_dir + "/" + shapefile_name) + + shapefile_path = os.path.join( + output_dir + "/" + shapefile_name, shapefile_name + ".shp" + ) + print("path path", shapefile_path) + cmd = f"ogr2ogr -f 'ESRI Shapefile' {shapefile_path} {kml_path}" # output.shp input.kml + os.system(command=cmd) + + return output_dir + "/" + shapefile_name + + +def kml_to_shp(state_name, district_name, block_name, kml_path): + shapefile_name = f"{district_name}_{block_name}" + shapefile_layer_path = convert_kml_to_shapefile( + kml_path, SHAPEFILE_DIR, shapefile_name + ) + + push_shape_to_geoserver(shapefile_layer_path, workspace="customkml") + + # os.remove(kml_path) + # shutil.rmtree(shapefile_layer_path) + os.remove(shapefile_layer_path + ".zip") + + +def sync_layer_to_geoserver(state_name, fc, layer_name, workspace): + state_dir = os.path.join("data/fc_to_shape", state_name) + if not os.path.exists(state_dir): + os.mkdir(state_dir) + path = os.path.join(state_dir, f"{layer_name}") + # Write the feature collection into json file + with open(path + ".json", "w") as f: + try: + f.write(f"{json.dumps(fc)}") + except Exception as e: + print(e) + + path = generate_shape_files(path) + return push_shape_to_geoserver(path, workspace=workspace, layer_name=layer_name) + + +def sync_fc_to_geoserver(fc, shp_folder, layer_name, workspace, style_name=None): + try: + geojson_fc = fc.getInfo() + except Exception as e: + print("Exception in getInfo()", e) + task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") + check_task_status([task_id]) + + geojson_fc = get_geojson_from_gcs(layer_name) + geo = Geoserver() + if len(geojson_fc["features"]) > 0: + state_dir = os.path.join("data/fc_to_shape", shp_folder) + if not os.path.exists(state_dir): + os.mkdir(state_dir) + path = os.path.join(state_dir, f"{layer_name}") + + # Convert to GeoDataFrame + gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) + + # Set CRS (Earth Engine uses EPSG:4326 by default) + gdf.crs = "EPSG:4326" + + gdf = fix_invalid_geometry_in_gdf(gdf) + + # Save as GeoPackage + gdf.to_file(path + ".gpkg", driver="GPKG") + res = push_shape_to_geoserver(path, workspace=workspace, file_type="gpkg") + if style_name: + style_res = geo.publish_style( + layer_name=layer_name, style_name=style_name, workspace=workspace + ) + print("Style response:", style_res) + return res + else: + return "No features in FeatureCollection" + + +def sync_project_fc_to_geoserver(fc, project_name, layer_name, workspace): + print("inside") + print(layer_name) + try: + geojson_fc = fc.getInfo() + except Exception as e: + print("Exception in getInfo()", e) + task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") + check_task_status([task_id]) + + geojson_fc = get_geojson_from_gcs(layer_name) + print(len(geojson_fc["features"])) + if len(geojson_fc["features"]) > 0: + state_dir = os.path.join("data/fc_to_shape", project_name) + if not os.path.exists(state_dir): + os.mkdir(state_dir) + path = os.path.join(state_dir, f"{layer_name}") + + # Convert to GeoDataFrame + gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) + + # Set CRS (Earth Engine uses EPSG:4326 by default) + gdf.crs = "EPSG:4326" + + gdf = fix_invalid_geometry_in_gdf(gdf) + + # Save as GeoPackage + gdf.to_file(path + ".gpkg", driver="GPKG") + print("pushed to geoserver") + return push_shape_to_geoserver( + path, workspace=workspace, layer_name=layer_name, file_type="gpkg" + ) + else: + print("no features found") + return + + +def to_camelcase(text): + words = text.split() + camelcase = words[0].lower() + for word in words[1:]: + camelcase += word.capitalize() + return camelcase + + +def create_chunk(aoi, description, chunk_size): + size = aoi.size().getInfo() + parts = size // chunk_size + # task_ids = [] + rois = [] + descs = [] + for part in range(parts + 1): + start = part * chunk_size + end = start + chunk_size + block_name_for_parts = description + "_" + str(start) + "-" + str(end) + roi = ee.FeatureCollection(aoi.toList(aoi.size()).slice(start, end)) + if roi.size().getInfo() > 0: + descs.append(block_name_for_parts) + rois.append(roi) + + return rois, descs + + +def merge_chunks( + aoi, + folder_list, + description, + chunk_size, + chunk_asset_path=GEE_HELPER_PATH, + merge_asset_path=GEE_ASSET_PATH, + merge_asset_id=None, +): + print("Merge Chunk task initiated") + ee_initialize() + size = aoi.size().getInfo() + parts = size // chunk_size + assets = [] + for part in range(parts + 1): + start = part * chunk_size + end = start + chunk_size + block_name_for_parts = description + "_" + str(start) + "-" + str(end) + src_asset_id = ( + get_gee_dir_path(folder_list, chunk_asset_path) + block_name_for_parts + ) + if is_gee_asset_exists(src_asset_id): + assets.append(ee.FeatureCollection(src_asset_id)) + + asset = ee.FeatureCollection(assets).flatten() + + asset_id = merge_asset_id or ( + get_gee_dir_path(folder_list, merge_asset_path) + description + ) + try: + # Export an ee.FeatureCollection as an Earth Engine asset. + task = ee.batch.Export.table.toAsset( + **{ + "collection": asset, + "description": description, + "assetId": asset_id, + } + ) + + task.start() + print("Successfully started the merge chunk", task.status()) + return task.status()["id"] + except Exception as e: + print(f"Error occurred in running merge task: {e}") + return None + + +def fix_invalid_geometry_in_gdf(gdf): + invalid = gdf[~gdf.is_valid] + if not invalid.empty: + print("Invalid geometries found:") + for idx, geom in invalid.geometry.items(): + print(f"Index {idx}: {explain_validity(geom)}") + gdf.loc[idx, "geometry"] = gdf.loc[idx, "geometry"].buffer(0) + + return gdf + + +def get_season_key(date): + """Return season key like 'rabi_2017-2018' based on Indian cropping seasons.""" + month = date.month + year = date.year + next_year = year + 1 + + if month in [1, 2]: + return f"rabi_{year - 1}-{year}" # Jan–Feb → Rabi of previous year + elif month in [11, 12]: + return f"rabi_{year}-{next_year}" # Nov–Dec → Rabi starting this year + elif month in [3, 4, 5, 6]: + return f"zaid_{year}-{next_year}" + elif month in [7, 8, 9, 10]: + return f"kharif_{year}-{next_year}" + else: + return None + + +def get_agri_year_key(season_key): + """Convert a season key to agricultural year key (e.g., rabi_2017-2018 → 2017-2018).""" + season, years = season_key.split("_") + start_year, end_year = map(int, years.split("-")) + + if season in ["kharif", "rabi"]: + return f"{start_year}-{end_year}" + elif season == "zaid": + return f"{start_year - 1}-{start_year}" # Zaid 2018-2019 → Agri year 2017-2018 + else: + return None + + +def calculate_precipitation_season( + geojson_filepath, draught_asset_id, start_year=2017, end_year=2024 +): + + # Load the GeoJSON file + with open(geojson_filepath, "r") as f: + feature_collection = json.load(f) + + features_ee = [] + + for feature in feature_collection["features"]: + original_props = feature["properties"] + new_props = {} + + # Copy UID + if "uid" in original_props: + new_props["uid"] = original_props["uid"] + + agri_year_totals = {} + + # Parse precipitation date keys + for key, val in original_props.items(): + try: + date = datetime.strptime(key, "%Y-%m-%d") + season_key = get_season_key(date) + if not season_key: + continue + + agri_key = get_agri_year_key(season_key) + if not agri_key: + continue + + agri_start = int(agri_key.split("-")[0]) + if not (start_year <= agri_start <= end_year): + continue + + season = season_key.split("_")[0] # kharif, rabi etc + full_key = f"{season}_{agri_key}" + + agri_year_totals[full_key] = agri_year_totals.get(full_key, 0) + float( + val + ) + + except Exception: + continue + + # Add all seasonal totals to new_props + for agri_key, total in agri_year_totals.items(): + new_props[f"precipitation_{agri_key}"] = total + + # Create EE Feature + geom_ee = ee.Geometry(feature["geometry"]) + feature_ee = ee.Feature(geom_ee, new_props) + features_ee.append(feature_ee) + + # Left side FC + mws_fc = ee.FeatureCollection(features_ee) + + return mws_fc + + +def generate_geojson_with_ci_and_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): + # Load project + proj_obj = Project.objects.get(pk=proj_id) + + # Build CI and NDVI asset paths + asset_path_ci = ( + get_gee_dir_path( + [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] + ) + + ci_asset + ) + + asset_path_ndvi = ( + get_gee_dir_path( + [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] + ) + + ndvi_asset + ) + + # Load FeatureCollections + zoi = ee.FeatureCollection(zoi_asset) + ci = ee.FeatureCollection(asset_path_ci) + ndvi = ee.FeatureCollection(asset_path_ndvi) + + # ------------------------- + # STEP 1: Join ZOI with Cropping Intensity + # ------------------------- + join = ee.Join.inner() + filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") + zoi_ci_joined = join.apply(zoi, ci, filter) + + def merge_zoi_ci(pair): + zoi_feat = ee.Feature(pair.get("primary")) + ci_feat = ee.Feature(pair.get("secondary")) + merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) + return ee.Feature(zoi_feat.geometry(), merged_props) + + zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) + + # ------------------------- + # STEP 2: Join ZOI+CI with NDVI + # ------------------------- + zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) + + def merge_zoi_ci_ndvi(pair): + ci_feat = ee.Feature(pair.get("primary")) + ndvi_feat = ee.Feature(pair.get("secondary")) + merged_props = ci_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) + return ee.Feature(ci_feat.geometry(), merged_props) + + final_merged = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) + + # ------------------------- + # STEP 3: Export or Push to GeoServer + # ------------------------- + layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" + sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") + + +def get_directory_size(path): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(path): + for filename in filenames: + file_path = os.path.join(dirpath, filename) + if os.path.isfile(file_path): + total_size += os.path.getsize(file_path) + return total_size + + +def generate_geojson_with_ci_ndvi_ndmi( + zoi_asset, ci_asset, ndvi_asset, ndmi_asset, proj_id +): + + # Load project + proj_obj = Project.objects.get(pk=proj_id) + + zoi = ee.FeatureCollection(zoi_asset) + print("Number of features zoi:", zoi.size().getInfo()) + + ci = ee.FeatureCollection(ci_asset) + print("Number of features zoi:", ci.size().getInfo()) + ndvi = ee.FeatureCollection(ndmi_asset) + print("Number of features zoi:", ndvi.size().getInfo()) + ndmi = ee.FeatureCollection(ndmi_asset) + print("Number of features zoi:", ndmi.size().getInfo()) + + # ------------------------- + # STEP 1: Join ZOI with CI + # ------------------------- + join = ee.Join.inner() + filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") + zoi_ci_joined = join.apply(zoi, ci, filter) + + def merge_zoi_ci(pair): + zoi_feat = ee.Feature(pair.get("primary")) + ci_feat = ee.Feature(pair.get("secondary")) + merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) + return ee.Feature(zoi_feat.geometry(), merged_props) # ✅ keep ZOI geom + + zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) + + # ------------------------- + # STEP 2: Join with NDVI + # ------------------------- + zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) + + def merge_zoi_ci_ndvi(pair): + prev_feat = ee.Feature(pair.get("primary")) + ndvi_feat = ee.Feature(pair.get("secondary")) + merged_props = prev_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) + return ee.Feature(prev_feat.geometry(), merged_props) # ✅ still ZOI geom + + zoi_ci_ndvi = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) + + # ------------------------- + # STEP 3: Join with NDMI + # ------------------------- + zoi_ndmi_joined = join.apply(zoi_ci_ndvi, ndmi, filter) + + def merge_zoi_ci_ndvi_ndmi(pair): + prev_feat = ee.Feature(pair.get("primary")) + ndmi_feat = ee.Feature(pair.get("secondary")) + merged_props = prev_feat.toDictionary().combine(ndmi_feat.toDictionary(), True) + return ee.Feature(prev_feat.geometry(), merged_props) # ✅ keep ZOI geom + + final_merged = ee.FeatureCollection(zoi_ndmi_joined.map(merge_zoi_ci_ndvi_ndmi)) + + # ------------------------- + # STEP 4: Export or Push to GeoServer + # ------------------------- + layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" + print(layer_name) + sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") + + +def generate_geojson_with_ci_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): + # Load project + proj_obj = Project.objects.get(pk=proj_id) + + # Initialize Earth Engine + ee_initialize(4) + + # Load FeatureCollections + zoi = ee.FeatureCollection(zoi_asset) + ci = ee.FeatureCollection(ci_asset) + ndvi = ee.FeatureCollection(ndvi_asset) + + print("ZOI:", zoi.size().getInfo()) + print("CI:", ci.size().getInfo()) + print("NDVI:", ndvi.size().getInfo()) + + # Common join logic on UID + join = ee.Join.inner() + uid_filter = ee.Filter.equals(leftField="UID", rightField="UID") + + # --- Join ZOI + CI --- + zoi_ci_joined = join.apply(zoi, ci, uid_filter) + + def merge_zoi_ci(pair): + zoi_feat = ee.Feature(pair.get("primary")) + ci_feat = ee.Feature(pair.get("secondary")) + merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) + # Keep ZOI geometry only + return ee.Feature(zoi_feat.geometry(), merged_props) + + zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) + + # --- Join with NDVI --- + zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, uid_filter) + + def merge_with_ndvi(pair): + base_feat = ee.Feature(pair.get("primary")) + ndvi_feat = ee.Feature(pair.get("secondary")) + merged_props = base_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) + # Always retain ZOI geometry + return ee.Feature(base_feat.geometry(), merged_props) + + merged_final = ee.FeatureCollection(zoi_ndvi_joined.map(merge_with_ndvi)) + + # --- Ensure ZOI geometry retained in all features --- + merged_final = merged_final.map( + lambda f: ee.Feature( + f.setGeometry( + ee.Feature( + zoi.filter(ee.Filter.eq("UID", f.get("UID"))).first() + ).geometry() + ) + ) + ) + + layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" + print(layer_name) + + sync_project_fc_to_geoserver(merged_final, proj_obj.name, layer_name, "waterrej") + + +def save_layer_info_to_db( + state, + district, + block, + layer_name, + asset_id, + dataset_name, + sync_to_geoserver=False, + layer_version="1.0", + algorithm=None, + algorithm_version="1.0", + misc=None, + is_override=False, +): + print("inside the save_layer_info_to_db function") + + dataset = Dataset.objects.get(name=dataset_name) + + try: + state_obj = StateSOI.objects.get(state_name__iexact=state) + district_obj = DistrictSOI.objects.get( + district_name__iexact=district, state=state_obj + ) + block_obj = TehsilSOI.objects.get( + tehsil_name__iexact=block, district=district_obj + ) + except Exception as e: + print("Error fetching in state district block:", e) + return + + is_public = is_asset_public(asset_id) + + # Check if there’s an existing layer + existing_layer = ( + Layer.objects.filter( + dataset=dataset, + layer_name=layer_name, + state=state_obj, + district=district_obj, + block=block_obj, + ) + .order_by("-layer_version") + .first() + ) + + if existing_layer: + if existing_layer.algorithm_version != algorithm_version: + # Algorithm version changed --> create new record with incremented layer_version + new_layer_version = str(float(existing_layer.layer_version) + 1) + print( + f"Algorithm version changed. Creating new layer version: {new_layer_version}" + ) + layer_obj = Layer.objects.create( + dataset=dataset, + layer_name=layer_name, + state=state_obj, + district=district_obj, + block=block_obj, + layer_version=new_layer_version, + algorithm=algorithm, + algorithm_version=algorithm_version, + is_sync_to_geoserver=sync_to_geoserver, + is_public_gee_asset=is_public, + is_override=is_override, + misc=misc, + gee_asset_path=asset_id, + ) + else: + # Algorithm version is same --> update existing layer + print("Algorithm version same. Updating existing layer.") + for field, value in { + "algorithm": algorithm, + "algorithm_version": algorithm_version, + "is_sync_to_geoserver": sync_to_geoserver, + "is_public_gee_asset": is_public, + "is_override": is_override, + "misc": misc, + "gee_asset_path": asset_id, + }.items(): + setattr(existing_layer, field, value) + existing_layer.save() + layer_obj = existing_layer + else: + # No existing record --> create a new one + print("No existing layer found. Creating new one.") + layer_obj = Layer.objects.create( + dataset=dataset, + layer_name=layer_name, + state=state_obj, + district=district_obj, + block=block_obj, + layer_version=layer_version, + algorithm=algorithm, + algorithm_version=algorithm_version, + is_sync_to_geoserver=sync_to_geoserver, + is_public_gee_asset=is_public, + is_override=is_override, + misc=misc, + gee_asset_path=asset_id, + ) + + print(f"Saved layer info (id={layer_obj.id}, version={layer_obj.layer_version})") + return layer_obj.id + + +def get_existing_end_year(dataset_name, layer_name): + """fetch objects from db on the basis of dataset name and layer_name""" + dataset = Dataset.objects.get(name=dataset_name) + layer_obj = Layer.objects.get(dataset=dataset, layer_name=layer_name) + existing_end_date = layer_obj.misc["end_year"] + print("existing_end_date", existing_end_date) + return existing_end_date + + +def get_layer_object(state, district, block, layer_name, dataset_name): + state_obj = StateSOI.objects.get(state_name__iexact=state) + district_obj = DistrictSOI.objects.get( + district_name__iexact=district, state=state_obj + ) + block_obj = TehsilSOI.objects.get(tehsil_name__iexact=block, district=district_obj) + layer_obj = ( + Layer.objects.filter( + state=state_obj, + district=district_obj, + block=block_obj, + layer_name=layer_name, + dataset__name=dataset_name, + ) + .order_by("-layer_version") + .first() + ) + return layer_obj + + +def update_dashboard_geojson( + state=None, + district=None, + block=None, + layer_name=None, + workspace_name=None, + proj_id=None, +): + if state and block and block: + print(f"🔄 Updating GeoJSON for {state}, {district}, {block}") + + # Get related objects + state_obj = StateSOI.objects.get(state_name=state) + district_obj = DistrictSOI.objects.get(district_name=district) + tehsil_obj = TehsilSOI.objects.get(tehsil_name=block) # fixed typo + + # Get or create main record + obj, created = State_Disritct_Block_Properties.objects.get_or_create( + state=state_obj, district=district_obj, tehsil=tehsil_obj + ) + else: + obj = Project.objects.get(pk=proj_id) + + # Map suffix to json_key + suffix_to_key = { + "wb": "wb_geojson", + "zoi": "zoi_geojson", + "mws": "mws_geojson", + } + + # Detect which key this layer corresponds to + json_key = None + for suffix, key in suffix_to_key.items(): + if layer_name == f"{state}_{district}_{block}_{suffix}": + json_key = key + break + + if not json_key: + print(f"⚠️ Layer name {layer_name} did not match any known type.") + return + + # Construct GeoServer URL + waterrej_url = ( + f"https://geoserver.core-stack.org:8443/geoserver/waterrej/ows?" + f"service=WFS&version=1.0.0&request=GetFeature&typeName={workspace_name}:{layer_name}" + f"&outputFormat=application%2Fjson" + ) + + # Load existing dashboard_geojson or create new + if proj_id: + misc = obj.dashboard_geojson or {} + else: + misc = obj.geojson_path or {} + + # Ensure waterrej section exists + if "waterrej" not in misc: + misc["waterrej"] = {} + + # Update or add this specific json_key + misc["waterrej"][json_key] = waterrej_url + + # Save the updated JSON field + obj.dashboard_geojson = misc + obj.save() + + print(f"✅ Added/Updated {json_key} for {state}, {district}, {block}") + + +def clean_geometry(geom): + """ + Clean geometry: + - Dissolve multipolygon → single polygon + - Remove holes automatically + - Fix invalid topology + - Buffer tiny polygons + """ + + # 1. Dissolve multi-polygons and remove holes + geom = geom.dissolve(maxError=1) + + # 2. Fix invalid rings by simplifying slightly (NEVER buffer(0)) + geom = geom.simplify(1) + + # 3. Buffer polygons smaller than 1 pixel (< 900 m²) + area = geom.area() + geom = ee.Algorithms.If( + area.lt(900), + geom.buffer(15), + geom, # ensure raster pixel center is captured + ) + + return ee.Geometry(geom) + + +def safe_reduce_max(image, geom, scale=30): + geom = clean_geometry(geom) + + val = ( + image.unmask(0) + .reduceRegion( + reducer=ee.Reducer.max(), + geometry=geom, + scale=scale, + maxPixels=1e13, + tileScale=4, + bestEffort=True, + ) + .get("b1") + ) + + return ee.Number(ee.Algorithms.If(val, val, 0)) + + +# ------------------------------------------------------ +# SAFE REDUCE MAX FUNCTION +# ------------------------------------------------------ +def safe_reduce_max(image, geom, scale=30): + geom = clean_geometry(geom) + + result = ( + image.unmask(0) + .reduceRegion( + reducer=ee.Reducer.max(), + geometry=geom, + scale=scale, + maxPixels=1e13, + tileScale=4, + bestEffort=True, + ) + .get("b1") + ) + + # Convert null → 0 + return ee.Number(ee.Algorithms.If(result, result, 0)) + + +# ------------------------------------------------------ +# MAIN FUNCTION TO PROCESS SWB LAYER +# ------------------------------------------------------ +def generate_swb_layer_with_max_so_catchment( + roi=None, + app_type="MWS", + asset_suffix=None, + asset_folder=None, + gee_account_id=None, +): + ee_initialize(gee_account_id) + + # Build asset paths + base_path = get_gee_dir_path( + asset_folder, asset_path=GEE_PATHS[app_type]["GEE_ASSET_PATH"] + ) + + so_asset = f"{base_path}stream_order_{asset_suffix}_raster" + ca_asset = f"{base_path}catchment_area_{asset_suffix}_raster" + + # Load rasters + stream_order_band = ee.Image(so_asset).select("b1") + catchment_band = ee.Image(ca_asset).select("b1") + + # Processing per waterbody + def compute_for_feature(feature): + geom = feature.geometry() + + max_so = safe_reduce_max(stream_order_band, geom, scale=30) + max_ca = safe_reduce_max(catchment_band, geom, scale=30) + + return feature.set( + { + "max_stream_order": max_so, + "max_catchment_area": max_ca, + } + ) + + # Map over the feature collection + return roi.map(compute_for_feature) + + +def _get_prod_backend_url(): + return getattr(settings, "PROD_BACKEND_URL", "").rstrip("/") + + +def _get_prod_api_key(): + return getattr(settings, "PROD_BACKEND_API_KEY", "") + + +def _sync_layer_to_prod_db(payload: dict): + prod_url = _get_prod_backend_url() + if not prod_url: + return None + + endpoint = prod_url + "/api/v1/sync_layer_remote/" + try: + response = requests.post( + endpoint, + json=payload, + headers={"X-Api-Key": _get_prod_api_key()}, + timeout=30, + ) + if response.status_code not in (200, 201): + logger.warning( + "Prod DB sync returned %s for layer %s: %s", + response.status_code, + payload.get("layer_name"), + response.text, + ) + return None + layer_id = response.json().get("layer_id") + logger.info( + "Layer %s synced to prod DB (id=%s).", payload.get("layer_name"), layer_id + ) + return layer_id + except requests.RequestException as e: + logger.error( + "Failed to sync layer %s to prod DB: %s", payload.get("layer_name"), e + ) + return None + + +def _update_layer_sync_remote( + layer_id, sync_to_geoserver=None, is_stac_specs_generated=None +): + prod_url = _get_prod_backend_url() + if not prod_url or layer_id is None: + return + + endpoint = prod_url + "/api/v1/update_layer_sync_remote/" + payload = { + "layer_id": layer_id, + "sync_to_geoserver": sync_to_geoserver, + "is_stac_specs_generated": is_stac_specs_generated, + } + try: + response = requests.post( + endpoint, + json=payload, + headers={"X-Api-Key": _get_prod_api_key()}, + timeout=30, + ) + if response.status_code not in (200, 201): + logger.warning( + "Prod layer sync status update returned %s for layer %s: %s", + response.status_code, + layer_id, + response.text, + ) + else: + logger.info("Layer sync status updated on prod DB for id=%s.", layer_id) + except requests.RequestException as e: + logger.error( + "Failed to update layer sync status on prod DB for id=%s: %s", layer_id, e + ) + + +def update_layer_sync_status( + layer_id, sync_to_geoserver=None, is_stac_specs_generated=None +): + if _get_prod_backend_url(): + _update_layer_sync_remote( + layer_id, + sync_to_geoserver=sync_to_geoserver, + is_stac_specs_generated=is_stac_specs_generated, + ) + return layer_id + + try: + layer_obj = Layer.objects.filter(id=layer_id).first() + if layer_obj is None: + return None + + update_fields = [] + if sync_to_geoserver is not None: + layer_obj.is_sync_to_geoserver = sync_to_geoserver + update_fields.append("is_sync_to_geoserver") + if is_stac_specs_generated is not None: + layer_obj.is_stac_specs_generated = is_stac_specs_generated + update_fields.append("is_stac_specs_generated") + + # `save(update_fields=...)` fires the post_save signal so the STAC + # auto-trigger handler in `computing.signals` can pick up the flip. + if update_fields: + layer_obj.save(update_fields=update_fields) + print( + f"Updated {update_fields} for layer ID: {layer_id} " + f"(sync={sync_to_geoserver}, stac={is_stac_specs_generated})" + ) + return layer_id + + except Exception as e: + print(f"Error updating layer sync status: {e}") + + +# send missing layer to recipient email +def send_missing_layers_report(result: dict, recipients: list = None) -> bool: + if recipients is None: + recipients = getattr(settings, "MISSING_LAYER_RECIPIENTS", []) + + if isinstance(recipients, str): + recipients = [recipients] + + if not recipients: + logger.error("No recipients configured for missing layers report.") + return False + + summary = [] + total_missing = 0 + + for layer, data in result.items(): + count = len(data.get("missing_layers", [])) + total_missing += count + summary.append(f"{layer}: {count}") + body = ( + "Missing Layers Report\n\n" + f"Total Missing: {total_missing}\n\n" + + "\n".join(summary) + + "\n\nDetailed report attached." + ) + + attachment_content = json.dumps(result, indent=4) + max_retries = 3 + + for attempt in range(max_retries): + connection = None + try: + connection = get_connection(timeout=120) + connection.open() + email = EmailMessage( + subject="Missing Layers Report", + body=body, + from_email=settings.EMAIL_HOST_USER, + to=recipients, + connection=connection, + ) + email.attach( + "missing_layers.json", + attachment_content, + "application/json", + ) + email.send() + logger.info(f"Missing layers report sent to {recipients}") + logger.info( + f"Attachment size: " + f"{len(attachment_content.encode('utf-8')) / 1024:.2f} KB" + ) + return True + except Exception as e: + logger.exception(f"Attempt {attempt + 1}/{max_retries} failed: {e}") + if attempt < max_retries - 1: + wait_time = 5 * (attempt + 1) + logger.info(f"Retrying after {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error("All attempts to send email failed.") + return False + finally: + if connection: + try: + connection.close() + except Exception: + pass + + +def _is_cache_valid(cache: dict, workspace: str) -> bool: + if workspace not in cache: + return False + age = time.time() - cache[workspace]["cached_at"] + if age > 3600: + logger.info(f"Cache expired for {workspace} (age: {int(age)}s)") + return False + return True + + +def _set_cache(cache: dict, workspace: str, data: set): + cache[workspace] = { + "data": data, + "cached_at": time.time(), + } diff --git a/computing/views.py b/computing/views.py index 1d22bb02..3f3595c8 100644 --- a/computing/views.py +++ b/computing/views.py @@ -1,479 +1,479 @@ -import requests -from nrm_app.settings import GEOSERVER_URL -from utilities.gee_utils import valid_gee_text -import xml.etree.ElementTree as ET -from lxml import etree as LET -from nrm_app.celery import app -from computing.models import * -from utilities.geoserver_utils import Geoserver -import json -from django.conf import settings -from pathlib import Path -from utilities.constants import GEOSERVER_BASE -from utilities.logger import setup_logger -from concurrent.futures import ThreadPoolExecutor, as_completed -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry -from rest_framework.response import Response -from rest_framework import status -from .utils import send_missing_layers_report, _is_cache_valid, _set_cache - -logger = setup_logger(__name__) - - -def get_url(geoserver_url, workspace, layer_name): - return ( - f"{geoserver_url}/{workspace}/ows" - f"?service=WFS" - f"&version=1.1.0" - f"&request=GetFeature" - f"&typeName={workspace}:{layer_name}" - f"&resultType=hits" - ) - - -def load_workspace_config(): - """ - Load workspace configuration from JSON file. - """ - config_path = ( - Path(settings.BASE_DIR) - / "data" - / "layers" - / "layer_status" - / "layer_mapping.json" - ) - with open(config_path, "r") as f: - return json.load(f) - - -@app.task(bind=True) -def layer_status(self, state, district, block): - """ - Check the status of all layers for a particular location. - - Args: - self: Instance reference - state: State name - district: District name - block: Block name - - Returns: - Dictionary with status of all workspace layers - """ - print(f"{state=}") - all_workspace_statuses = {} - capabilities_cache = {} - district = valid_gee_text(district.lower()) - block = valid_gee_text(block.lower()) - - # Load workspace configuration from JSON - workspace_config = load_workspace_config() - - for workspace_display, config in workspace_config.items(): - workspace = config.get("name") - suffix = config.get("suffix", "") - prefix = config.get("prefix", "") - layer_type = config.get("type", "") - - # constructing layer name - layer_name_parts = [prefix, district, block, suffix] - layer_name = "_".join(part for part in layer_name_parts if part) - - total_features = 0 - end_date = None - start_date = None - status_code = 400 - # checking for vector layer - if layer_type == "vector": - layer_url = get_url(GEOSERVER_URL, workspace, layer_name) - res_layer_url = requests.get(layer_url) - - if res_layer_url.status_code == 200: - try: - root = ET.fromstring(res_layer_url.text) - # Extract feature count from WFS hits response - total_features = int(root.attrib.get("numberOfFeatures", 0)) - status_code = 200 if total_features > 0 else 400 - layer = ( - Layer.objects.filter(layer_name=layer_name) - .order_by("-layer_version") - .first() - ) - if layer and layer.misc: - start_date = layer.misc.get("start_date") - end_date = layer.misc.get("end_date") - - except ET.ParseError: - print(f"Invalid XML for layer: {layer_name}") - status_code = 400 - else: - if workspace not in capabilities_cache: - capabilities_url = f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" - try: - response = requests.get(capabilities_url, timeout=30) - if response.status_code == 200: - parser = LET.XMLParser(recover=True, encoding="utf-8") - root = LET.fromstring(response.content, parser=parser) - ns = {"wms": root.tag.split("}")[0].strip("{")} - layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) - capabilities_cache[workspace] = { - layer.text for layer in layers if layer.text - } - else: - capabilities_cache[workspace] = set() - except Exception as e: - print( - f"Failed to fetch capabilities for workspace {workspace}: {e}" - ) - capabilities_cache[workspace] = set() - - if layer_name in capabilities_cache.get(workspace, set()): - status_code = 200 - all_workspace_statuses[workspace_display] = { - "workspace": workspace, - "layer_name": layer_name, - "status_code": status_code, - "totalFeature": total_features, - "endDate": end_date, - "startDate": start_date, - } - - return all_workspace_statuses - - -def load_workspace_types(): - """ - Load workspace types configuration from JSON file. - """ - config_path = ( - Path(settings.BASE_DIR) - / "data" - / "layers" - / "workspace_layers" - / "layers_in_workspace.json" - ) - with open(config_path, "r") as f: - return json.load(f) - - -@app.task(bind=True) -def get_layers_of_workspace(self, workspace): - """ - It will take workspace as argument and returns all the layers which is present on geoserver. - """ - # Load workspace types from JSON - workspace_types = load_workspace_types() - raster_workspace = workspace_types["raster_workspace"] - vector_workspace = workspace_types["vector_workspace"] - raster_and_vector_workspace = workspace_types["raster_and_vector_workspace"] - - geo = Geoserver() - layers = geo.get_layers(workspace) - layer_names = [layer["name"] for layer in layers["layers"]["layer"]] - if workspace in raster_workspace: - print("you passed raster workspace") - available_layers = valid_raster_layers_for_workspace(workspace) - valid_layers = [ln for ln in layer_names if ln in available_layers] - invalid_layers = [ln for ln in layer_names if ln not in available_layers] - return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} - elif workspace in vector_workspace: - print("you passed vector workspace") - valid_layers = [] - invalid_layers = [] - for layer_name in layer_names: - if is_valid_vector_layer(workspace, layer_name): - valid_layers.append(layer_name) - else: - invalid_layers.append(layer_name) - return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} - elif workspace in raster_and_vector_workspace: - print("you passed workspace which contain both layers(raster and vector)") - valid_layers = [] - invalid_layers = [] - for layer_name in layer_names: - if "vector" in layer_name.lower(): - if is_valid_vector_layer(workspace, layer_name): - valid_layers.append(layer_name) - else: - invalid_layers.append(layer_name) - elif "raster" in layer_name.lower(): - available_layers = valid_raster_layers_for_workspace(workspace) - if layer_name in available_layers: - valid_layers.append(layer_name) - else: - invalid_layers.append(layer_name) - return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} - else: - print("you passed wrong workspace") - return [] - - -_raster_cache = {} -_vector_cache = {} - - -def valid_raster_layers_for_workspace(workspace: str) -> set: - if _is_cache_valid(_raster_cache, workspace): - logger.info(f"Cache hit for raster: {workspace}") - return _raster_cache[workspace]["data"] - - session = get_session_with_retry() - capabilities_url = ( - f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" - ) - try: - response = session.get(capabilities_url, timeout=(5, 15)) - response.raise_for_status() - except requests.exceptions.Timeout: - logger.warning( - f"Timeout fetching raster capabilities for {workspace} — not caching" - ) - return set() - except requests.exceptions.RequestException as e: - logger.error(f"Failed raster capabilities for {workspace}: {e} — not caching") - return set() - - root = ET.fromstring(response.content) - ns = {"wms": root.tag.split("}")[0].strip("{")} - layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) - result = {layer.text for layer in layers} - - _set_cache(_raster_cache, workspace, result) # cache with timestamp - logger.info(f"Cached raster layers for {workspace}: {len(result)} layers") - return result - - -def valid_vector_layers_for_workspace(workspace: str) -> set: - if _is_cache_valid(_vector_cache, workspace): - logger.info(f"Cache hit for vector: {workspace}") - return _vector_cache[workspace]["data"] - - session = get_session_with_retry() - capabilities_url = ( - f"{GEOSERVER_BASE}{workspace}/wfs" - f"?service=WFS&version=2.0.0&request=GetCapabilities" - ) - try: - response = session.get(capabilities_url, timeout=(5, 15)) - response.raise_for_status() - except requests.exceptions.Timeout: - logger.warning( - f"Timeout fetching vector capabilities for {workspace} — not caching" - ) - return set() - except requests.exceptions.RequestException as e: - logger.error(f"Failed vector capabilities for {workspace}: {e} — not caching") - return set() - - root = ET.fromstring(response.content) - ns = {"wfs": "http://www.opengis.net/wfs/2.0"} - names = root.findall(".//wfs:FeatureType/wfs:Name", namespaces=ns) - result = {name.text.split(":")[-1] for name in names} - - _set_cache(_vector_cache, workspace, result) # cache with timestamp - logger.info(f"Cached vector layers for {workspace}: {len(result)} layers") - return result - - -def is_valid_vector_layer(workspace: str, layer_name: str) -> bool: - """Used in parallel fallback only.""" - session = get_session_with_retry() - try: - layer_url = get_url(GEOSERVER_URL, workspace, layer_name) - res = session.get(layer_url, timeout=(5, 10)) # shorter timeout per layer - if res.status_code == 200: - root = ET.fromstring(res.text) - return int(root.attrib.get("numberOfFeatures", 0)) > 0 - except requests.exceptions.Timeout: - logger.warning(f"Timeout checking vector layer: {layer_name}") - except Exception as e: - logger.warning(f"Vector check failed for {layer_name}: {e}") - return False - - -def bulk_check_vector_layers( - workspace: str, - layer_names: list[str], - max_workers: int = 20, -) -> dict[str, bool]: - """ - Checks multiple vector layers concurrently using a thread pool. - Returns {layer_name: is_valid} mapping. - """ - results = {} - with ThreadPoolExecutor(max_workers=max_workers) as executor: - future_to_layer = { - executor.submit(is_valid_vector_layer, workspace, name): name - for name in layer_names - } - for future in as_completed(future_to_layer): - layer_name = future_to_layer[future] - try: - results[layer_name] = future.result() - except Exception as e: - logger.warning(f"Failed check for {layer_name}: {e}") - results[layer_name] = False - return results - - -@app.task(bind=True) -def missing_layer_for_all_workspace(self): - workspaces = ( - Dataset.objects.filter(workspace__isnull=False) - .values_list("workspace", flat=True) - .distinct() - ) - workspaces = [w.strip() for w in workspaces if w and w.strip()] - result = {} - for workspace in workspaces: - result[workspace] = check_missing_layers(workspace) - send_missing_layers_report(result) - return result - - -def check_missing_layers(workspace: str) -> dict: - logger.info(f"{workspace=}") - workspace_config = load_workspace_config() - workspace_types = get_workspace_types(workspace) - logger.info(f"Found types: {workspace_types}") - - if not workspace_types: - logger.critical(f"No config found for workspace: {workspace}") - return {"no config found": []} - - layer_config = get_layer_config_by_type( - workspace_config, workspace, workspace_types - ) - - # ── Fetch all available layers ONCE (bulk, cached) ────────────────────── - available_raster_layers = valid_raster_layers_for_workspace(workspace) - available_vector_layers = valid_vector_layers_for_workspace(workspace) # bulk WFS` - use_bulk_vector = bool(available_vector_layers) # fallback if WFS unavailable - - # ── Pre-build all (tehsil, layer_type, layer_name, state) combos ──────── - active_tehsils = TehsilSOI.objects.select_related( - "district__state" # eliminates N+1 DB queries - ).filter(active_status=True) - - tasks = [] # (state, layer_type, layer_name) - for tehsil_obj in active_tehsils: - state = tehsil_obj.district.state.state_name - district_name = valid_gee_text(tehsil_obj.district.district_name.lower()) - tehsil_name = valid_gee_text(tehsil_obj.tehsil_name.lower()) - - for layer_type, configs in layer_config.items(): - for config in configs: - prefix = config.get("prefix") - suffix = config.get("suffix") - layer_name = "_".join( - p for p in [prefix, district_name, tehsil_name, suffix] if p - ) - tasks.append( - (state, district_name, tehsil_name, layer_type, layer_name) - ) - - # ── Check raster layers (pure set lookup, no HTTP) ─────────────────────── - missing_layers = [] - vector_tasks = [] # collect for batch/parallel processing - - for state, district_name, tehsil_name, layer_type, layer_name in tasks: - if layer_type == "raster": - if layer_name not in available_raster_layers: - missing_layers.append(f"{state}, {district_name}, {tehsil_name}") - - elif layer_type == "vector": - if use_bulk_vector: - # O(1) set lookup — no HTTP call needed - if layer_name not in available_vector_layers: - missing_layers.append(f"{state}, {district_name}, {tehsil_name}") - else: - vector_tasks.append( - (state, district_name, tehsil_name, layer_name) - ) # queue for parallel check - - # ── Parallel fallback for vector layers (if WFS bulk fetch failed) ─────── - if vector_tasks: - layer_names = [ln for _, _, _, ln in vector_tasks] - info_by_name = {ln: (st, dn, tn) for st, dn, tn, ln in vector_tasks} - validity = bulk_check_vector_layers(workspace, layer_names) - - for layer_name, is_valid in validity.items(): - if not is_valid: - state, district_name, tehsil_name = info_by_name[layer_name] - missing_layers.append(f"{state}, {district_name}, {tehsil_name}") - - return {"missing_layers": missing_layers} - - -def get_workspace_types(workspace_name): - """ - Return all layer types for a given workspace name from DB. - Example: ['raster', 'vector'] - """ - return list( - Dataset.objects.filter(workspace=workspace_name) - .values_list("layer_type", flat=True) - .distinct() - ) - - -def get_layer_config_by_type(workspace_config, workspace_name, layer_types): - """ - Return list of prefix/suffix configs for each layer type. - - Args: - workspace_config (dict): config JSON - workspace_name (str): dataset name - layer_types (list): ['raster', 'vector'] - - Returns: - dict: {'raster': [{'prefix': ..., 'suffix': ...}, ...], 'vector': [...]} - """ - result = {} - - for config in workspace_config.values(): - if config.get("name") == workspace_name and config.get("type") in layer_types: - layer_type = config["type"] - - if layer_type not in result: - result[layer_type] = [] - - result[layer_type].append( - { - "prefix": config.get("prefix"), - "suffix": config.get("suffix"), - } - ) - - return result - - -def get_session_with_retry(): - """Creates a requests session with retry and timeout handling.""" - session = requests.Session() - retry = Retry( - total=3, - backoff_factor=1, # waits 1s, 2s, 4s between retries - status_forcelist=[500, 502, 503, 504], - ) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - return session - - -def clear_layer_cache(workspace: str = None): - if workspace: - _raster_cache.pop(workspace, None) - _vector_cache.pop(workspace, None) - logger.info(f"Cleared cache for {workspace}") - else: - _raster_cache.clear() - _vector_cache.clear() - logger.info("Cleared all layer caches") - - -def refresh_layer_cache(request, workspace=None): - clear_layer_cache(workspace) - return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) +import requests +from nrm_app.settings import GEOSERVER_URL +from utilities.gee_utils import valid_gee_text +import xml.etree.ElementTree as ET +from lxml import etree as LET +from nrm_app.celery import app +from computing.models import * +from utilities.geoserver_utils import Geoserver +import json +from django.conf import settings +from pathlib import Path +from utilities.constants import GEOSERVER_BASE +from utilities.logger import setup_logger +from concurrent.futures import ThreadPoolExecutor, as_completed +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +from rest_framework.response import Response +from rest_framework import status +from .utils import send_missing_layers_report, _is_cache_valid, _set_cache + +logger = setup_logger(__name__) + + +def get_url(geoserver_url, workspace, layer_name): + return ( + f"{geoserver_url}/{workspace}/ows" + f"?service=WFS" + f"&version=1.1.0" + f"&request=GetFeature" + f"&typeName={workspace}:{layer_name}" + f"&resultType=hits" + ) + + +def load_workspace_config(): + """ + Load workspace configuration from JSON file. + """ + config_path = ( + Path(settings.BASE_DIR) + / "data" + / "layers" + / "layer_status" + / "layer_mapping.json" + ) + with open(config_path, "r") as f: + return json.load(f) + + +@app.task(bind=True) +def layer_status(self, state, district, block): + """ + Check the status of all layers for a particular location. + + Args: + self: Instance reference + state: State name + district: District name + block: Block name + + Returns: + Dictionary with status of all workspace layers + """ + print(f"{state=}") + all_workspace_statuses = {} + capabilities_cache = {} + district = valid_gee_text(district.lower()) + block = valid_gee_text(block.lower()) + + # Load workspace configuration from JSON + workspace_config = load_workspace_config() + + for workspace_display, config in workspace_config.items(): + workspace = config.get("name") + suffix = config.get("suffix", "") + prefix = config.get("prefix", "") + layer_type = config.get("type", "") + + # constructing layer name + layer_name_parts = [prefix, district, block, suffix] + layer_name = "_".join(part for part in layer_name_parts if part) + + total_features = 0 + end_date = None + start_date = None + status_code = 400 + # checking for vector layer + if layer_type == "vector": + layer_url = get_url(GEOSERVER_URL, workspace, layer_name) + res_layer_url = requests.get(layer_url) + + if res_layer_url.status_code == 200: + try: + root = ET.fromstring(res_layer_url.text) + # Extract feature count from WFS hits response + total_features = int(root.attrib.get("numberOfFeatures", 0)) + status_code = 200 if total_features > 0 else 400 + layer = ( + Layer.objects.filter(layer_name=layer_name) + .order_by("-layer_version") + .first() + ) + if layer and layer.misc: + start_date = layer.misc.get("start_date") + end_date = layer.misc.get("end_date") + + except ET.ParseError: + print(f"Invalid XML for layer: {layer_name}") + status_code = 400 + else: + if workspace not in capabilities_cache: + capabilities_url = f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" + try: + response = requests.get(capabilities_url, timeout=30) + if response.status_code == 200: + parser = LET.XMLParser(recover=True, encoding="utf-8") + root = LET.fromstring(response.content, parser=parser) + ns = {"wms": root.tag.split("}")[0].strip("{")} + layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) + capabilities_cache[workspace] = { + layer.text for layer in layers if layer.text + } + else: + capabilities_cache[workspace] = set() + except Exception as e: + print( + f"Failed to fetch capabilities for workspace {workspace}: {e}" + ) + capabilities_cache[workspace] = set() + + if layer_name in capabilities_cache.get(workspace, set()): + status_code = 200 + all_workspace_statuses[workspace_display] = { + "workspace": workspace, + "layer_name": layer_name, + "status_code": status_code, + "totalFeature": total_features, + "endDate": end_date, + "startDate": start_date, + } + + return all_workspace_statuses + + +def load_workspace_types(): + """ + Load workspace types configuration from JSON file. + """ + config_path = ( + Path(settings.BASE_DIR) + / "data" + / "layers" + / "workspace_layers" + / "layers_in_workspace.json" + ) + with open(config_path, "r") as f: + return json.load(f) + + +@app.task(bind=True) +def get_layers_of_workspace(self, workspace): + """ + It will take workspace as argument and returns all the layers which is present on geoserver. + """ + # Load workspace types from JSON + workspace_types = load_workspace_types() + raster_workspace = workspace_types["raster_workspace"] + vector_workspace = workspace_types["vector_workspace"] + raster_and_vector_workspace = workspace_types["raster_and_vector_workspace"] + + geo = Geoserver() + layers = geo.get_layers(workspace) + layer_names = [layer["name"] for layer in layers["layers"]["layer"]] + if workspace in raster_workspace: + print("you passed raster workspace") + available_layers = valid_raster_layers_for_workspace(workspace) + valid_layers = [ln for ln in layer_names if ln in available_layers] + invalid_layers = [ln for ln in layer_names if ln not in available_layers] + return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} + elif workspace in vector_workspace: + print("you passed vector workspace") + valid_layers = [] + invalid_layers = [] + for layer_name in layer_names: + if is_valid_vector_layer(workspace, layer_name): + valid_layers.append(layer_name) + else: + invalid_layers.append(layer_name) + return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} + elif workspace in raster_and_vector_workspace: + print("you passed workspace which contain both layers(raster and vector)") + valid_layers = [] + invalid_layers = [] + for layer_name in layer_names: + if "vector" in layer_name.lower(): + if is_valid_vector_layer(workspace, layer_name): + valid_layers.append(layer_name) + else: + invalid_layers.append(layer_name) + elif "raster" in layer_name.lower(): + available_layers = valid_raster_layers_for_workspace(workspace) + if layer_name in available_layers: + valid_layers.append(layer_name) + else: + invalid_layers.append(layer_name) + return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} + else: + print("you passed wrong workspace") + return [] + + +_raster_cache = {} +_vector_cache = {} + + +def valid_raster_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_raster_cache, workspace): + logger.info(f"Cache hit for raster: {workspace}") + return _raster_cache[workspace]["data"] + + session = get_session_with_retry() + capabilities_url = ( + f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" + ) + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching raster capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed raster capabilities for {workspace}: {e} — not caching") + return set() + + root = ET.fromstring(response.content) + ns = {"wms": root.tag.split("}")[0].strip("{")} + layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) + result = {layer.text for layer in layers} + + _set_cache(_raster_cache, workspace, result) # cache with timestamp + logger.info(f"Cached raster layers for {workspace}: {len(result)} layers") + return result + + +def valid_vector_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_vector_cache, workspace): + logger.info(f"Cache hit for vector: {workspace}") + return _vector_cache[workspace]["data"] + + session = get_session_with_retry() + capabilities_url = ( + f"{GEOSERVER_BASE}{workspace}/wfs" + f"?service=WFS&version=2.0.0&request=GetCapabilities" + ) + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching vector capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed vector capabilities for {workspace}: {e} — not caching") + return set() + + root = ET.fromstring(response.content) + ns = {"wfs": "http://www.opengis.net/wfs/2.0"} + names = root.findall(".//wfs:FeatureType/wfs:Name", namespaces=ns) + result = {name.text.split(":")[-1] for name in names} + + _set_cache(_vector_cache, workspace, result) # cache with timestamp + logger.info(f"Cached vector layers for {workspace}: {len(result)} layers") + return result + + +def is_valid_vector_layer(workspace: str, layer_name: str) -> bool: + """Used in parallel fallback only.""" + session = get_session_with_retry() + try: + layer_url = get_url(GEOSERVER_URL, workspace, layer_name) + res = session.get(layer_url, timeout=(5, 10)) # shorter timeout per layer + if res.status_code == 200: + root = ET.fromstring(res.text) + return int(root.attrib.get("numberOfFeatures", 0)) > 0 + except requests.exceptions.Timeout: + logger.warning(f"Timeout checking vector layer: {layer_name}") + except Exception as e: + logger.warning(f"Vector check failed for {layer_name}: {e}") + return False + + +def bulk_check_vector_layers( + workspace: str, + layer_names: list[str], + max_workers: int = 20, +) -> dict[str, bool]: + """ + Checks multiple vector layers concurrently using a thread pool. + Returns {layer_name: is_valid} mapping. + """ + results = {} + with ThreadPoolExecutor(max_workers=max_workers) as executor: + future_to_layer = { + executor.submit(is_valid_vector_layer, workspace, name): name + for name in layer_names + } + for future in as_completed(future_to_layer): + layer_name = future_to_layer[future] + try: + results[layer_name] = future.result() + except Exception as e: + logger.warning(f"Failed check for {layer_name}: {e}") + results[layer_name] = False + return results + + +@app.task(bind=True) +def missing_layer_for_all_workspace(self): + workspaces = ( + Dataset.objects.filter(workspace__isnull=False) + .values_list("workspace", flat=True) + .distinct() + ) + workspaces = [w.strip() for w in workspaces if w and w.strip()] + result = {} + for workspace in workspaces: + result[workspace] = check_missing_layers(workspace) + send_missing_layers_report(result) + return result + + +def check_missing_layers(workspace: str) -> dict: + logger.info(f"{workspace=}") + workspace_config = load_workspace_config() + workspace_types = get_workspace_types(workspace) + logger.info(f"Found types: {workspace_types}") + + if not workspace_types: + logger.critical(f"No config found for workspace: {workspace}") + return {"no config found": []} + + layer_config = get_layer_config_by_type( + workspace_config, workspace, workspace_types + ) + + # ── Fetch all available layers ONCE (bulk, cached) ────────────────────── + available_raster_layers = valid_raster_layers_for_workspace(workspace) + available_vector_layers = valid_vector_layers_for_workspace(workspace) # bulk WFS` + use_bulk_vector = bool(available_vector_layers) # fallback if WFS unavailable + + # ── Pre-build all (tehsil, layer_type, layer_name, state) combos ──────── + active_tehsils = TehsilSOI.objects.select_related( + "district__state" # eliminates N+1 DB queries + ).filter(active_status=True) + + tasks = [] # (state, layer_type, layer_name) + for tehsil_obj in active_tehsils: + state = tehsil_obj.district.state.state_name + district_name = valid_gee_text(tehsil_obj.district.district_name.lower()) + tehsil_name = valid_gee_text(tehsil_obj.tehsil_name.lower()) + + for layer_type, configs in layer_config.items(): + for config in configs: + prefix = config.get("prefix") + suffix = config.get("suffix") + layer_name = "_".join( + p for p in [prefix, district_name, tehsil_name, suffix] if p + ) + tasks.append( + (state, district_name, tehsil_name, layer_type, layer_name) + ) + + # ── Check raster layers (pure set lookup, no HTTP) ─────────────────────── + missing_layers = [] + vector_tasks = [] # collect for batch/parallel processing + + for state, district_name, tehsil_name, layer_type, layer_name in tasks: + if layer_type == "raster": + if layer_name not in available_raster_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + elif layer_type == "vector": + if use_bulk_vector: + # O(1) set lookup — no HTTP call needed + if layer_name not in available_vector_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + else: + vector_tasks.append( + (state, district_name, tehsil_name, layer_name) + ) # queue for parallel check + + # ── Parallel fallback for vector layers (if WFS bulk fetch failed) ─────── + if vector_tasks: + layer_names = [ln for _, _, _, ln in vector_tasks] + info_by_name = {ln: (st, dn, tn) for st, dn, tn, ln in vector_tasks} + validity = bulk_check_vector_layers(workspace, layer_names) + + for layer_name, is_valid in validity.items(): + if not is_valid: + state, district_name, tehsil_name = info_by_name[layer_name] + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + return {"missing_layers": missing_layers} + + +def get_workspace_types(workspace_name): + """ + Return all layer types for a given workspace name from DB. + Example: ['raster', 'vector'] + """ + return list( + Dataset.objects.filter(workspace=workspace_name) + .values_list("layer_type", flat=True) + .distinct() + ) + + +def get_layer_config_by_type(workspace_config, workspace_name, layer_types): + """ + Return list of prefix/suffix configs for each layer type. + + Args: + workspace_config (dict): config JSON + workspace_name (str): dataset name + layer_types (list): ['raster', 'vector'] + + Returns: + dict: {'raster': [{'prefix': ..., 'suffix': ...}, ...], 'vector': [...]} + """ + result = {} + + for config in workspace_config.values(): + if config.get("name") == workspace_name and config.get("type") in layer_types: + layer_type = config["type"] + + if layer_type not in result: + result[layer_type] = [] + + result[layer_type].append( + { + "prefix": config.get("prefix"), + "suffix": config.get("suffix"), + } + ) + + return result + + +def get_session_with_retry(): + """Creates a requests session with retry and timeout handling.""" + session = requests.Session() + retry = Retry( + total=3, + backoff_factor=1, # waits 1s, 2s, 4s between retries + status_forcelist=[500, 502, 503, 504], + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + + +def clear_layer_cache(workspace: str = None): + if workspace: + _raster_cache.pop(workspace, None) + _vector_cache.pop(workspace, None) + logger.info(f"Cleared cache for {workspace}") + else: + _raster_cache.clear() + _vector_cache.clear() + logger.info("Cleared all layer caches") + + +def refresh_layer_cache(request, workspace=None): + clear_layer_cache(workspace) + return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) diff --git a/dpr/tasks.py b/dpr/tasks.py index d894df34..a22ac1d0 100644 --- a/dpr/tasks.py +++ b/dpr/tasks.py @@ -1,123 +1,123 @@ -from django.templatetags.i18n import language -from django.utils import timezone -from nrm_app.celery import app -from utilities.logger import setup_logger -import requests - -from .gen_dpr import ( - create_dpr_document, - get_mws_ids_for_report, - get_plan_details, -) -from .models import DPR_Report -from .utils import ( - transform_name, - send_dpr_email, - upload_dpr_to_s3, - check_dpr_exists_on_s3, -) - -# from .views import generate_dpr_pdf - -logger = setup_logger(__name__) - - -def get_or_generate_dpr(plan, regenerate=False): - dpr_report, created = DPR_Report.objects.get_or_create( - plan_id=plan, defaults={"plan_name": plan.plan, "status": "PENDING"} - ) - - if not regenerate: - s3_exists = check_dpr_exists_on_s3(dpr_report.dpr_report_s3_url) - if not created and not dpr_report.needs_regeneration() and s3_exists: - logger.info( - f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}" - ) - return dpr_report, False - - logger.info(f"Generating new DPR for plan {plan.id}") - dpr_report.status = "GENERATING" - dpr_report.save(update_fields=["status"]) - - doc = create_dpr_document(plan) - s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan) - - dpr_report.dpr_report_s3_url = s3_url - dpr_report.dpr_generated_at = timezone.now() - dpr_report.status = "COMPLETED" - dpr_report.last_updated_at = timezone.now() - dpr_report.save( - update_fields=[ - "dpr_report_s3_url", - "dpr_generated_at", - "status", - "last_updated_at", - ] - ) - - logger.info(f"DPR generated and saved to S3: {s3_url}") - return dpr_report, True - - -@app.task(bind=True, name="dpr.generate_dpr_task") -def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = False): - plan = get_plan_details(plan_id) - if plan is None: - logger.error(f"Plan not found for ID: {plan_id}") - return {"error": "Plan not found"} - - dpr_report, was_regenerated = get_or_generate_dpr(plan, regenerate=regenerate) - mws_Ids = get_mws_ids_for_report(plan) - - mws_reports = [] - successful_mws_ids = [] - - state = transform_name(str(plan.state_soi.state_name)) - district = transform_name(str(plan.district_soi.district_name)) - block = transform_name(str(plan.tehsil_soi.tehsil_name)) - - for ids in mws_Ids: - try: - report_url = ( - f"https://geoserver.core-stack.org/api/v1/download_report/" - f"?report_type=mws&state={state}&district={district}&block={block}&uid={ids}" - ) - mws_reports.append(report_url) - successful_mws_ids.append(ids) - except Exception as e: - logger.error(f"Failed to generate MWS report for ID {ids}: {e}") - - # Fetch Resource Report PDF - resource_report_url = ( - f"https://geoserver.core-stack.org/api/v1/download_report/" - f"?report_type=resource&district={district}&block={block}&plan_id={plan_id}&plan_name={plan.plan}" - ) - - resource_report = None - try: - response = requests.get(resource_report_url, timeout=30) - response.raise_for_status() - resource_report = response.content - except Exception as e: - logger.error(f"Failed to fetch resource report: {e}") - - send_dpr_email( - email_id=email_id, - plan_name=plan.plan, - mws_reports=mws_reports, - mws_Ids=successful_mws_ids, - resource_report=resource_report, - resource_report_url=resource_report_url, - dpr_s3_url=dpr_report.dpr_report_s3_url, - state_name=plan.state_soi.state_name, - district_name=plan.district_soi.district_name, - tehsil_name=plan.tehsil_soi.tehsil_name, - ) - - return { - "status": "success", - "email_id": email_id, - "plan_id": plan_id, - "s3_url": dpr_report.dpr_report_s3_url, - "was_regenerated": was_regenerated, - } +from django.templatetags.i18n import language +from django.utils import timezone +from nrm_app.celery import app +from utilities.logger import setup_logger +import requests + +from .gen_dpr import ( + create_dpr_document, + get_mws_ids_for_report, + get_plan_details, +) +from .models import DPR_Report +from .utils import ( + transform_name, + send_dpr_email, + upload_dpr_to_s3, + check_dpr_exists_on_s3, +) + +# from .views import generate_dpr_pdf + +logger = setup_logger(__name__) + + +def get_or_generate_dpr(plan, regenerate=False): + dpr_report, created = DPR_Report.objects.get_or_create( + plan_id=plan, defaults={"plan_name": plan.plan, "status": "PENDING"} + ) + + if not regenerate: + s3_exists = check_dpr_exists_on_s3(dpr_report.dpr_report_s3_url) + if not created and not dpr_report.needs_regeneration() and s3_exists: + logger.info( + f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}" + ) + return dpr_report, False + + logger.info(f"Generating new DPR for plan {plan.id}") + dpr_report.status = "GENERATING" + dpr_report.save(update_fields=["status"]) + + doc = create_dpr_document(plan) + s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan) + + dpr_report.dpr_report_s3_url = s3_url + dpr_report.dpr_generated_at = timezone.now() + dpr_report.status = "COMPLETED" + dpr_report.last_updated_at = timezone.now() + dpr_report.save( + update_fields=[ + "dpr_report_s3_url", + "dpr_generated_at", + "status", + "last_updated_at", + ] + ) + + logger.info(f"DPR generated and saved to S3: {s3_url}") + return dpr_report, True + + +@app.task(bind=True, name="dpr.generate_dpr_task") +def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = False): + plan = get_plan_details(plan_id) + if plan is None: + logger.error(f"Plan not found for ID: {plan_id}") + return {"error": "Plan not found"} + + dpr_report, was_regenerated = get_or_generate_dpr(plan, regenerate=regenerate) + mws_Ids = get_mws_ids_for_report(plan) + + mws_reports = [] + successful_mws_ids = [] + + state = transform_name(str(plan.state_soi.state_name)) + district = transform_name(str(plan.district_soi.district_name)) + block = transform_name(str(plan.tehsil_soi.tehsil_name)) + + for ids in mws_Ids: + try: + report_url = ( + f"https://geoserver.core-stack.org/api/v1/download_report/" + f"?report_type=mws&state={state}&district={district}&block={block}&uid={ids}" + ) + mws_reports.append(report_url) + successful_mws_ids.append(ids) + except Exception as e: + logger.error(f"Failed to generate MWS report for ID {ids}: {e}") + + # Fetch Resource Report PDF + resource_report_url = ( + f"https://geoserver.core-stack.org/api/v1/download_report/" + f"?report_type=resource&district={district}&block={block}&plan_id={plan_id}&plan_name={plan.plan}" + ) + + resource_report = None + try: + response = requests.get(resource_report_url, timeout=30) + response.raise_for_status() + resource_report = response.content + except Exception as e: + logger.error(f"Failed to fetch resource report: {e}") + + send_dpr_email( + email_id=email_id, + plan_name=plan.plan, + mws_reports=mws_reports, + mws_Ids=successful_mws_ids, + resource_report=resource_report, + resource_report_url=resource_report_url, + dpr_s3_url=dpr_report.dpr_report_s3_url, + state_name=plan.state_soi.state_name, + district_name=plan.district_soi.district_name, + tehsil_name=plan.tehsil_soi.tehsil_name, + ) + + return { + "status": "success", + "email_id": email_id, + "plan_id": plan_id, + "s3_url": dpr_report.dpr_report_s3_url, + "was_regenerated": was_regenerated, + } diff --git a/dpr/utils.py b/dpr/utils.py index 98a3083e..669e9ee3 100644 --- a/dpr/utils.py +++ b/dpr/utils.py @@ -1,540 +1,540 @@ -import json -import re -import ssl -import socket -from io import BytesIO -import warnings -import time -from urllib.parse import urlparse -import pytz -import requests -from django.db.models import Max -from django.utils import timezone -from django.core.mail import EmailMessage -from django.core.mail.backends.smtp import EmailBackend -from docx import Document - -from nrm_app.settings import ( - EMAIL_HOST, - EMAIL_HOST_PASSWORD, - EMAIL_HOST_USER, - EMAIL_PORT, - EMAIL_TIMEOUT, - EMAIL_USE_SSL, - ODK_PASSWORD, - ODK_USERNAME, -) -from utilities.constants import ( - ODK_URL_AGRI_MAINTENANCE, - ODK_URL_GW_MAINTENANCE, - ODK_URL_RS_WATERBODY_MAINTENANCE, - ODK_URL_WATERBODY_MAINTENANCE, - ODK_URL_agri, - ODK_URL_crop, - ODK_URL_gw, - ODK_URL_livelihood, - ODK_URL_settlement, - ODK_URL_waterbody, - ODK_URL_well, -) -from utilities.logger import setup_logger - -from .models import ( - Agri_maintenance, - GW_maintenance, - ODK_agri, - ODK_crop, - ODK_groundwater, - ODK_livelihood, - ODK_settlement, - ODK_waterbody, - ODK_well, - SWB_maintenance, - SWB_RS_maintenance, -) - -import boto3 -from nrm_app.settings import ( - DPR_S3_ACCESS_KEY, - DPR_S3_SECRET_KEY, - DPR_S3_REGION, - DPR_S3_BUCKET, - DPR_S3_FOLDER, -) -from botocore.exceptions import ClientError - -warnings.filterwarnings("ignore") - -logger = setup_logger(__name__) - - -def get_url(geoserver_url, 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 get_vector_layer_geoserver(geoserver_url, workspace, layer_name): - """Fetch vector layer data from GeoServer and return as GeoJSON.""" - url = get_url(geoserver_url, workspace, layer_name) - try: - response = requests.get(url) - response.raise_for_status() - - # Check if the response content is not empty and is valid JSON - if response.content: - return response.json() - else: - print(f"Empty response for layer '{layer_name}'.") - return None - - except requests.exceptions.RequestException as e: - print(f"Failed to fetch the vector layer '{layer_name}' from GeoServer: {e}") - print(f"Request URL: {url}") - if response is not None: - print(f"Response status code: {response.status_code}") - # print(f"Response content: {response.text}") - return None - - -def determine_caste_fields(record): - """ - Determine caste group whether it's a Single Caste Group or Mixed Caste Group - """ - count_sc = record.get("count_sc") - count_st = record.get("count_st") - count_obc = record.get("count_obc") - count_general = record.get("count_general") - - caste_counts_mapping = { - "SC": count_sc, - "ST": count_st, - "OBC": count_obc, - "GENERAL": count_general, - } - - valid_castes = [] - for caste, count in caste_counts_mapping.items(): - if count is not None and count != "": - try: - count_value = float(count) if isinstance(count, str) else count - if count_value > 0: - valid_castes.append(caste) - except (ValueError, TypeError): - if count: - valid_castes.append(caste) - - if not valid_castes: - return None, None, None, True - - if len(valid_castes) == 1: - largest_caste = "Single Caste Group" - smallest_caste = valid_castes[0] - settlement_status = "NA" - return largest_caste, smallest_caste, settlement_status, False - - else: - largest_caste = "Mixed Caste Group" - smallest_caste = "NA" - settlement_status = ", ".join(sorted(valid_castes)) - return largest_caste, smallest_caste, settlement_status, False - - -def validate_email(emailid): - email_regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" - if not re.match(email_regex, emailid): - return False - else: - return True - - -def check_submission_time(record, model): - submission_date = timezone.datetime.strptime( - record.get("__system", {}).get("submissionDate", ""), "%Y-%m-%dT%H:%M:%S.%fZ" - ) - submission_date = timezone.make_aware(submission_date, pytz.UTC) - latest_submission_time = model.objects.aggregate(Max("submission_time"))[ - "submission_time__max" - ] - - if latest_submission_time and submission_date <= latest_submission_time: - return False, True - return True, False - - -def extract_coordinates(record): - try: - gps_point = record.get("GPS_point", {}) - if not gps_point: - return None, None - - # Check for both possible key names - maps_appearance = gps_point.get("point_mapsappearance") or gps_point.get( - "point_mapappearance" - ) - if not maps_appearance: - return None, None - - coordinates = maps_appearance.get("coordinates", []) - if len(coordinates) < 2: - return None, None - - return coordinates[1], coordinates[0] # latitude, longitude - except (AttributeError, IndexError, TypeError): - return None, None - - -def format_text_demands(text): - """ - Helps in converting demands in proper text - """ - if not text: - return "" - - items = text.split() - formatted_items = [] - - for item in items: - item_with_spaces = item.replace("_", " ") - formatted_item = " ".join( - word.capitalize() for word in item_with_spaces.split() - ) - formatted_items.append(formatted_item) - - formatted_text = "\n".join(formatted_items) - return formatted_text - - -def ensure_str(value): - """Normalize a value that may be a list (from Kobo multi-select fields) into a string.""" - if isinstance(value, list): - return " ".join(str(v) for v in value) - return value if value is not None else "" - - -def format_text(text): - """ - Converts text with underscores to properly formatted text. - Example: 'Delayed_payments_for_works' -> 'Delayed Payments For Works' - """ - if not text: - return "" - - text = ensure_str(text) - formatted_text = text.replace("_", " ") - return formatted_text.capitalize() + "\n\n" - - -def get_waterbody_repair_activities(data_waterbody, water_structure_type): - """ - Extract repair activities based on water structure type from data_waterbody. - Handles 'other' cases where the specific repair activity is in a separate field. - - Args: - data_waterbody (dict): The nested waterbody data dictionary - water_structure_type (str): The type of water structure - - Returns: - str: The repair activities or "NA" if none found - """ - if not data_waterbody or not water_structure_type: - return "NA" - - structure_type_mapping = { - "canal": "Repair_of_canal", - "bunding": "Repair_of_bunding", - "check dam": "Repair_of_check_dam", - "farm bund": "Repair_of_farm_bund", - "farm pond": "Repair_of_farm_ponds", - "soakage pits": "Repair_of_soakage_pits", - "recharge pits": "Repair_of_recharge_pits", - "rock fill dam": "Repair_of_rock_fill_dam", - "stone bunding": "Repair_of_stone_bunding", - "community pond": "Repair_of_community_pond", - "diversion drains": "Repair_of_diversion_drains", - "large water body": "Repair_of_large_water_body", - "model5 structure": "Repair_of_model5_structure", - "percolation tank": "Repair_of_percolation_tank", - "earthen gully plug": "Repair_of_earthen_gully_plug", - "30-40 model structure": "Repair_of_30_40_model_structure", - "loose boulder structure": "Repair_of_loose_boulder_structure", - "trench cum bund network": "Repair_of_trench_cum_bund_network", - "water absorption trenches": "Repair_of_Water_absorption_trenches", - "drainage soakage channels": "Repair_of_drainage_soakage_channels", - "staggered contour trenches": "Repair_of_Staggered_contour_trenches", - "continuous contour trenches": "Repair_of_Continuous_contour_trenches", - } - - structure_type_lower = water_structure_type.lower().strip() - if structure_type_lower.startswith("other:"): - repair_fields = [ - key for key in data_waterbody.keys() if key.startswith("Repair_of_") - ] - for field in repair_fields: - if data_waterbody.get(field): - repair_value = ensure_str(data_waterbody.get(field)) - other_field = field + "_other" - if ( - repair_value - and repair_value.lower() == "other" - and data_waterbody.get(other_field) - ): - return f"Other: {data_waterbody.get(other_field)}" - # elif repair_value: - # return repair_value.replace("_", " ").title() - return "NA" - - repair_field = structure_type_mapping.get(structure_type_lower) - - if not repair_field: - return "NA" - - repair_activity = ensure_str(data_waterbody.get(repair_field)) - - if not repair_activity: - return "NA" - - if repair_activity.lower() == "other": - other_field = repair_field + "_other" - other_value = data_waterbody.get(other_field) - if other_value: - return f"Other: {other_value}" - else: - return "Other" - - return repair_activity - - -def sort_key(settlement): - return (settlement == "NA", settlement.lower() if settlement != "NA" else "") - - -def transform_name(name): - if not name: - return name - - name = re.sub(r"[()]", "", name) - name = re.sub(r"[-\s]+", "_", name) - name = re.sub(r"_+", "_", name) - name = re.sub(r"^_|_$", "", name) - return name.lower() - - -def to_utf8(value): - """Ensure value is a properly encoded UTF-8 string for Word document. - - Handles cases where UTF-8 text was incorrectly decoded as Latin-1, - resulting in garbled characters like 'ಪಾà²...' for Kannada/Hindi text. - """ - if value is None: - return "NA" - if isinstance(value, list): - value = " ".join(str(v) for v in value) - if isinstance(value, bytes): - try: - return value.decode("utf-8") - except UnicodeDecodeError: - return value.decode("latin-1") - if not isinstance(value, str): - value = str(value) - try: - return value.encode("latin-1").decode("utf-8") - except (UnicodeDecodeError, UnicodeEncodeError): - return value - - -def send_dpr_email( - email_id, - plan_name, - mws_reports, - mws_Ids, - resource_report, - resource_report_url, - dpr_s3_url, - state_name="", - district_name="", - tehsil_name="", -): - try: - mws_table_html = "" - if mws_reports and mws_Ids: - mws_rows = "".join( - f'{mws_id}' - f'' - f'View Report' - for mws_id, report_url in zip(mws_Ids, mws_reports) - ) - mws_table_html = f""" -

        -

        MWS Reports

        - - - - - - - - {mws_rows} -
        MWS IDReport
        -
        - """ - - email_body = f""" - - - - -
        -
        -
        -

        Detailed Project Report

        -

        {to_utf8(plan_name)}

        -

        {to_utf8(tehsil_name)} · {to_utf8(district_name)} · {to_utf8(state_name)}

        -
        -
        -

        - Hi,

        - Your Detailed Project Report for {to_utf8(plan_name)} is ready. -

        -
        -

        Download DPR Report

        - Download DPR → -
        - {mws_table_html} -
        -

        Resource Report

        - View Report → -
        -
        -
        -

        - Thanks and Regards,
        - CoRE Stack Team -

        -
        -
        -

        - This is an automated email from CoRE Stack. -

        -
        - - - """ - - backend = EmailBackend( - host=EMAIL_HOST, - port=EMAIL_PORT, - username=EMAIL_HOST_USER, - password=EMAIL_HOST_PASSWORD, - use_ssl=EMAIL_USE_SSL, - timeout=EMAIL_TIMEOUT, - ssl_context=ssl.create_default_context(), - ) - - email = EmailMessage( - subject=f"DPR of plan: {plan_name}", - body=email_body, - from_email=EMAIL_HOST_USER, - to=[email_id], - connection=backend, - ) - - email.content_subtype = "html" - - if resource_report is not None: - email.attach( - f"Resource Report_{plan_name}.pdf", resource_report, "application/pdf" - ) - - logger.info("Sending DPR email to %s", email_id) - email.send(fail_silently=False) - logger.info("DPR email sent.") - backend.close() - - except socket.error as e: - logger.error(f"Socket error: {e}") - except ssl.SSLError as e: - logger.error(f"SSL error: {e}") - except Exception as e: - logger.error(f"Failed to send email: {e}") - - -def upload_dpr_to_s3(doc, plan_id, plan_name): - doc_bytes = BytesIO() - doc.save(doc_bytes) - doc_bytes.seek(0) - - safe_plan_name = transform_name(plan_name) - s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}.docx" - - s3_client = boto3.client( - "s3", - aws_access_key_id=DPR_S3_ACCESS_KEY, - aws_secret_access_key=DPR_S3_SECRET_KEY, - region_name=DPR_S3_REGION, - ) - - s3_client.upload_fileobj( - doc_bytes, - DPR_S3_BUCKET, - s3_key, - ExtraArgs={ - "ContentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.docx"', - "CacheControl": "no-cache, no-store, must-revalidate", - }, - ) - - ts = int(time.time()) - s3_url = f"https://{DPR_S3_BUCKET}.s3.{DPR_S3_REGION}.amazonaws.com/{s3_key}?v={ts}" - logger.info(f"DPR uploaded to S3: {s3_url}") - return s3_url - - -def _extract_s3_key(s3_url): - - parsed = urlparse(s3_url) - return parsed.path.lstrip("/") - - -def check_dpr_exists_on_s3(s3_url): - if not s3_url: - return False - - try: - s3_key = _extract_s3_key(s3_url) - except (IndexError, AttributeError): - return False - - s3_client = boto3.client( - "s3", - aws_access_key_id=DPR_S3_ACCESS_KEY, - aws_secret_access_key=DPR_S3_SECRET_KEY, - region_name=DPR_S3_REGION, - ) - - try: - s3_client.head_object(Bucket=DPR_S3_BUCKET, Key=s3_key) - return True - except ClientError: - logger.warning(f"DPR not found on S3: {s3_url}") - return False - - -def download_dpr_from_s3(s3_url): - s3_key = _extract_s3_key(s3_url) - - s3_client = boto3.client( - "s3", - aws_access_key_id=DPR_S3_ACCESS_KEY, - aws_secret_access_key=DPR_S3_SECRET_KEY, - region_name=DPR_S3_REGION, - ) - - doc_bytes = BytesIO() - s3_client.download_fileobj(DPR_S3_BUCKET, s3_key, doc_bytes) - doc_bytes.seek(0) - - doc = Document(doc_bytes) - logger.info(f"DPR downloaded from S3: {s3_url}") - return doc +import json +import re +import ssl +import socket +from io import BytesIO +import warnings +import time +from urllib.parse import urlparse +import pytz +import requests +from django.db.models import Max +from django.utils import timezone +from django.core.mail import EmailMessage +from django.core.mail.backends.smtp import EmailBackend +from docx import Document + +from nrm_app.settings import ( + EMAIL_HOST, + EMAIL_HOST_PASSWORD, + EMAIL_HOST_USER, + EMAIL_PORT, + EMAIL_TIMEOUT, + EMAIL_USE_SSL, + ODK_PASSWORD, + ODK_USERNAME, +) +from utilities.constants import ( + ODK_URL_AGRI_MAINTENANCE, + ODK_URL_GW_MAINTENANCE, + ODK_URL_RS_WATERBODY_MAINTENANCE, + ODK_URL_WATERBODY_MAINTENANCE, + ODK_URL_agri, + ODK_URL_crop, + ODK_URL_gw, + ODK_URL_livelihood, + ODK_URL_settlement, + ODK_URL_waterbody, + ODK_URL_well, +) +from utilities.logger import setup_logger + +from .models import ( + Agri_maintenance, + GW_maintenance, + ODK_agri, + ODK_crop, + ODK_groundwater, + ODK_livelihood, + ODK_settlement, + ODK_waterbody, + ODK_well, + SWB_maintenance, + SWB_RS_maintenance, +) + +import boto3 +from nrm_app.settings import ( + DPR_S3_ACCESS_KEY, + DPR_S3_SECRET_KEY, + DPR_S3_REGION, + DPR_S3_BUCKET, + DPR_S3_FOLDER, +) +from botocore.exceptions import ClientError + +warnings.filterwarnings("ignore") + +logger = setup_logger(__name__) + + +def get_url(geoserver_url, 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 get_vector_layer_geoserver(geoserver_url, workspace, layer_name): + """Fetch vector layer data from GeoServer and return as GeoJSON.""" + url = get_url(geoserver_url, workspace, layer_name) + try: + response = requests.get(url) + response.raise_for_status() + + # Check if the response content is not empty and is valid JSON + if response.content: + return response.json() + else: + print(f"Empty response for layer '{layer_name}'.") + return None + + except requests.exceptions.RequestException as e: + print(f"Failed to fetch the vector layer '{layer_name}' from GeoServer: {e}") + print(f"Request URL: {url}") + if response is not None: + print(f"Response status code: {response.status_code}") + # print(f"Response content: {response.text}") + return None + + +def determine_caste_fields(record): + """ + Determine caste group whether it's a Single Caste Group or Mixed Caste Group + """ + count_sc = record.get("count_sc") + count_st = record.get("count_st") + count_obc = record.get("count_obc") + count_general = record.get("count_general") + + caste_counts_mapping = { + "SC": count_sc, + "ST": count_st, + "OBC": count_obc, + "GENERAL": count_general, + } + + valid_castes = [] + for caste, count in caste_counts_mapping.items(): + if count is not None and count != "": + try: + count_value = float(count) if isinstance(count, str) else count + if count_value > 0: + valid_castes.append(caste) + except (ValueError, TypeError): + if count: + valid_castes.append(caste) + + if not valid_castes: + return None, None, None, True + + if len(valid_castes) == 1: + largest_caste = "Single Caste Group" + smallest_caste = valid_castes[0] + settlement_status = "NA" + return largest_caste, smallest_caste, settlement_status, False + + else: + largest_caste = "Mixed Caste Group" + smallest_caste = "NA" + settlement_status = ", ".join(sorted(valid_castes)) + return largest_caste, smallest_caste, settlement_status, False + + +def validate_email(emailid): + email_regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" + if not re.match(email_regex, emailid): + return False + else: + return True + + +def check_submission_time(record, model): + submission_date = timezone.datetime.strptime( + record.get("__system", {}).get("submissionDate", ""), "%Y-%m-%dT%H:%M:%S.%fZ" + ) + submission_date = timezone.make_aware(submission_date, pytz.UTC) + latest_submission_time = model.objects.aggregate(Max("submission_time"))[ + "submission_time__max" + ] + + if latest_submission_time and submission_date <= latest_submission_time: + return False, True + return True, False + + +def extract_coordinates(record): + try: + gps_point = record.get("GPS_point", {}) + if not gps_point: + return None, None + + # Check for both possible key names + maps_appearance = gps_point.get("point_mapsappearance") or gps_point.get( + "point_mapappearance" + ) + if not maps_appearance: + return None, None + + coordinates = maps_appearance.get("coordinates", []) + if len(coordinates) < 2: + return None, None + + return coordinates[1], coordinates[0] # latitude, longitude + except (AttributeError, IndexError, TypeError): + return None, None + + +def format_text_demands(text): + """ + Helps in converting demands in proper text + """ + if not text: + return "" + + items = text.split() + formatted_items = [] + + for item in items: + item_with_spaces = item.replace("_", " ") + formatted_item = " ".join( + word.capitalize() for word in item_with_spaces.split() + ) + formatted_items.append(formatted_item) + + formatted_text = "\n".join(formatted_items) + return formatted_text + + +def ensure_str(value): + """Normalize a value that may be a list (from Kobo multi-select fields) into a string.""" + if isinstance(value, list): + return " ".join(str(v) for v in value) + return value if value is not None else "" + + +def format_text(text): + """ + Converts text with underscores to properly formatted text. + Example: 'Delayed_payments_for_works' -> 'Delayed Payments For Works' + """ + if not text: + return "" + + text = ensure_str(text) + formatted_text = text.replace("_", " ") + return formatted_text.capitalize() + "\n\n" + + +def get_waterbody_repair_activities(data_waterbody, water_structure_type): + """ + Extract repair activities based on water structure type from data_waterbody. + Handles 'other' cases where the specific repair activity is in a separate field. + + Args: + data_waterbody (dict): The nested waterbody data dictionary + water_structure_type (str): The type of water structure + + Returns: + str: The repair activities or "NA" if none found + """ + if not data_waterbody or not water_structure_type: + return "NA" + + structure_type_mapping = { + "canal": "Repair_of_canal", + "bunding": "Repair_of_bunding", + "check dam": "Repair_of_check_dam", + "farm bund": "Repair_of_farm_bund", + "farm pond": "Repair_of_farm_ponds", + "soakage pits": "Repair_of_soakage_pits", + "recharge pits": "Repair_of_recharge_pits", + "rock fill dam": "Repair_of_rock_fill_dam", + "stone bunding": "Repair_of_stone_bunding", + "community pond": "Repair_of_community_pond", + "diversion drains": "Repair_of_diversion_drains", + "large water body": "Repair_of_large_water_body", + "model5 structure": "Repair_of_model5_structure", + "percolation tank": "Repair_of_percolation_tank", + "earthen gully plug": "Repair_of_earthen_gully_plug", + "30-40 model structure": "Repair_of_30_40_model_structure", + "loose boulder structure": "Repair_of_loose_boulder_structure", + "trench cum bund network": "Repair_of_trench_cum_bund_network", + "water absorption trenches": "Repair_of_Water_absorption_trenches", + "drainage soakage channels": "Repair_of_drainage_soakage_channels", + "staggered contour trenches": "Repair_of_Staggered_contour_trenches", + "continuous contour trenches": "Repair_of_Continuous_contour_trenches", + } + + structure_type_lower = water_structure_type.lower().strip() + if structure_type_lower.startswith("other:"): + repair_fields = [ + key for key in data_waterbody.keys() if key.startswith("Repair_of_") + ] + for field in repair_fields: + if data_waterbody.get(field): + repair_value = ensure_str(data_waterbody.get(field)) + other_field = field + "_other" + if ( + repair_value + and repair_value.lower() == "other" + and data_waterbody.get(other_field) + ): + return f"Other: {data_waterbody.get(other_field)}" + # elif repair_value: + # return repair_value.replace("_", " ").title() + return "NA" + + repair_field = structure_type_mapping.get(structure_type_lower) + + if not repair_field: + return "NA" + + repair_activity = ensure_str(data_waterbody.get(repair_field)) + + if not repair_activity: + return "NA" + + if repair_activity.lower() == "other": + other_field = repair_field + "_other" + other_value = data_waterbody.get(other_field) + if other_value: + return f"Other: {other_value}" + else: + return "Other" + + return repair_activity + + +def sort_key(settlement): + return (settlement == "NA", settlement.lower() if settlement != "NA" else "") + + +def transform_name(name): + if not name: + return name + + name = re.sub(r"[()]", "", name) + name = re.sub(r"[-\s]+", "_", name) + name = re.sub(r"_+", "_", name) + name = re.sub(r"^_|_$", "", name) + return name.lower() + + +def to_utf8(value): + """Ensure value is a properly encoded UTF-8 string for Word document. + + Handles cases where UTF-8 text was incorrectly decoded as Latin-1, + resulting in garbled characters like 'ಪಾà²...' for Kannada/Hindi text. + """ + if value is None: + return "NA" + if isinstance(value, list): + value = " ".join(str(v) for v in value) + if isinstance(value, bytes): + try: + return value.decode("utf-8") + except UnicodeDecodeError: + return value.decode("latin-1") + if not isinstance(value, str): + value = str(value) + try: + return value.encode("latin-1").decode("utf-8") + except (UnicodeDecodeError, UnicodeEncodeError): + return value + + +def send_dpr_email( + email_id, + plan_name, + mws_reports, + mws_Ids, + resource_report, + resource_report_url, + dpr_s3_url, + state_name="", + district_name="", + tehsil_name="", +): + try: + mws_table_html = "" + if mws_reports and mws_Ids: + mws_rows = "".join( + f'{mws_id}' + f'' + f'View Report' + for mws_id, report_url in zip(mws_Ids, mws_reports) + ) + mws_table_html = f""" +
        +

        MWS Reports

        + + + + + + + + {mws_rows} +
        MWS IDReport
        +
        + """ + + email_body = f""" + + + + +
        +
        +
        +

        Detailed Project Report

        +

        {to_utf8(plan_name)}

        +

        {to_utf8(tehsil_name)} · {to_utf8(district_name)} · {to_utf8(state_name)}

        +
        +
        +

        + Hi,

        + Your Detailed Project Report for {to_utf8(plan_name)} is ready. +

        +
        +

        Download DPR Report

        + Download DPR → +
        + {mws_table_html} +
        +

        Resource Report

        + View Report → +
        +
        +
        +

        + Thanks and Regards,
        + CoRE Stack Team +

        +
        +
        +

        + This is an automated email from CoRE Stack. +

        +
        + + + """ + + backend = EmailBackend( + host=EMAIL_HOST, + port=EMAIL_PORT, + username=EMAIL_HOST_USER, + password=EMAIL_HOST_PASSWORD, + use_ssl=EMAIL_USE_SSL, + timeout=EMAIL_TIMEOUT, + ssl_context=ssl.create_default_context(), + ) + + email = EmailMessage( + subject=f"DPR of plan: {plan_name}", + body=email_body, + from_email=EMAIL_HOST_USER, + to=[email_id], + connection=backend, + ) + + email.content_subtype = "html" + + if resource_report is not None: + email.attach( + f"Resource Report_{plan_name}.pdf", resource_report, "application/pdf" + ) + + logger.info("Sending DPR email to %s", email_id) + email.send(fail_silently=False) + logger.info("DPR email sent.") + backend.close() + + except socket.error as e: + logger.error(f"Socket error: {e}") + except ssl.SSLError as e: + logger.error(f"SSL error: {e}") + except Exception as e: + logger.error(f"Failed to send email: {e}") + + +def upload_dpr_to_s3(doc, plan_id, plan_name): + doc_bytes = BytesIO() + doc.save(doc_bytes) + doc_bytes.seek(0) + + safe_plan_name = transform_name(plan_name) + s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}.docx" + + s3_client = boto3.client( + "s3", + aws_access_key_id=DPR_S3_ACCESS_KEY, + aws_secret_access_key=DPR_S3_SECRET_KEY, + region_name=DPR_S3_REGION, + ) + + s3_client.upload_fileobj( + doc_bytes, + DPR_S3_BUCKET, + s3_key, + ExtraArgs={ + "ContentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.docx"', + "CacheControl": "no-cache, no-store, must-revalidate", + }, + ) + + ts = int(time.time()) + s3_url = f"https://{DPR_S3_BUCKET}.s3.{DPR_S3_REGION}.amazonaws.com/{s3_key}?v={ts}" + logger.info(f"DPR uploaded to S3: {s3_url}") + return s3_url + + +def _extract_s3_key(s3_url): + + parsed = urlparse(s3_url) + return parsed.path.lstrip("/") + + +def check_dpr_exists_on_s3(s3_url): + if not s3_url: + return False + + try: + s3_key = _extract_s3_key(s3_url) + except (IndexError, AttributeError): + return False + + s3_client = boto3.client( + "s3", + aws_access_key_id=DPR_S3_ACCESS_KEY, + aws_secret_access_key=DPR_S3_SECRET_KEY, + region_name=DPR_S3_REGION, + ) + + try: + s3_client.head_object(Bucket=DPR_S3_BUCKET, Key=s3_key) + return True + except ClientError: + logger.warning(f"DPR not found on S3: {s3_url}") + return False + + +def download_dpr_from_s3(s3_url): + s3_key = _extract_s3_key(s3_url) + + s3_client = boto3.client( + "s3", + aws_access_key_id=DPR_S3_ACCESS_KEY, + aws_secret_access_key=DPR_S3_SECRET_KEY, + region_name=DPR_S3_REGION, + ) + + doc_bytes = BytesIO() + s3_client.download_fileobj(DPR_S3_BUCKET, s3_key, doc_bytes) + doc_bytes.seek(0) + + doc = Document(doc_bytes) + logger.info(f"DPR downloaded from S3: {s3_url}") + return doc diff --git a/dpr/views.py b/dpr/views.py index 53a22a08..08a663ff 100644 --- a/dpr/views.py +++ b/dpr/views.py @@ -1,64 +1,64 @@ -from datetime import date -from django.template.loader import render_to_string -from dpr.service.translation_service import load_translations - -# from weasyprint import HTML -from .gen_dpr import get_settlement_count_for_plan -from .utils import get_vector_layer_geoserver, transform_name -from nrm_app.settings import GEOSERVER_URL -from .get_dpr_sectionwise_data import ( - get_section_b_data, - get_section_c_data, - get_section_d_data, - get_section_e_data, - get_section_f_data, - get_section_g_data, -) -from .service.form_download_service import sync_odk_forms - - -def generate_dpr_html(plan, language="en"): - translations = load_translations(language) - total_settlements = get_settlement_count_for_plan(plan.id) - mws_fortnight = get_vector_layer_geoserver( - geoserver_url=GEOSERVER_URL, - workspace="mws_layers", - layer_name="deltaG_fortnight_" - + transform_name(str(plan.district_soi.district_name)) - + "_" - + transform_name(str(plan.tehsil_soi.tehsil_name)), - ) - section_b_data, settlement_mws_ids, mws_gdf = get_section_b_data( - plan, total_settlements, mws_fortnight - ) - section_c_data = get_section_c_data(plan, language) - section_d_data = get_section_d_data(plan, settlement_mws_ids, mws_gdf, language) - section_e_data = get_section_e_data(plan, language) - section_f_data = get_section_f_data(plan, language) - section_g_data = get_section_g_data(plan, language) - html = render_to_string( - "dpr/base.html", - { - "t": translations, - "current_date": date.today().strftime("%B %d, %Y"), - "section_a": plan, - "section_b": section_b_data, - "section_c": section_c_data, - "section_d": section_d_data, - "section_e": section_e_data, - "section_f": section_f_data, - "section_g": section_g_data, - "footnote": f"DPR supported by {plan.organization.name} in {date.today().year}", - }, - ) - - return html - - -# def generate_dpr_pdf(plan, language="en"): -# sync_odk_forms() -# html = generate_dpr_html(plan, language) -# -# pdf = HTML(string=html).write_pdf() -# -# return pdf +from datetime import date +from django.template.loader import render_to_string +from dpr.service.translation_service import load_translations + +# from weasyprint import HTML +from .gen_dpr import get_settlement_count_for_plan +from .utils import get_vector_layer_geoserver, transform_name +from nrm_app.settings import GEOSERVER_URL +from .get_dpr_sectionwise_data import ( + get_section_b_data, + get_section_c_data, + get_section_d_data, + get_section_e_data, + get_section_f_data, + get_section_g_data, +) +from .service.form_download_service import sync_odk_forms + + +def generate_dpr_html(plan, language="en"): + translations = load_translations(language) + total_settlements = get_settlement_count_for_plan(plan.id) + mws_fortnight = get_vector_layer_geoserver( + geoserver_url=GEOSERVER_URL, + workspace="mws_layers", + layer_name="deltaG_fortnight_" + + transform_name(str(plan.district_soi.district_name)) + + "_" + + transform_name(str(plan.tehsil_soi.tehsil_name)), + ) + section_b_data, settlement_mws_ids, mws_gdf = get_section_b_data( + plan, total_settlements, mws_fortnight + ) + section_c_data = get_section_c_data(plan, language) + section_d_data = get_section_d_data(plan, settlement_mws_ids, mws_gdf, language) + section_e_data = get_section_e_data(plan, language) + section_f_data = get_section_f_data(plan, language) + section_g_data = get_section_g_data(plan, language) + html = render_to_string( + "dpr/base.html", + { + "t": translations, + "current_date": date.today().strftime("%B %d, %Y"), + "section_a": plan, + "section_b": section_b_data, + "section_c": section_c_data, + "section_d": section_d_data, + "section_e": section_e_data, + "section_f": section_f_data, + "section_g": section_g_data, + "footnote": f"DPR supported by {plan.organization.name} in {date.today().year}", + }, + ) + + return html + + +# def generate_dpr_pdf(plan, language="en"): +# sync_odk_forms() +# html = generate_dpr_html(plan, language) +# +# pdf = HTML(string=html).write_pdf() +# +# return pdf diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 725c6040..f08fe9d6 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -1,436 +1,436 @@ -""" -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") + +# 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/plans/api.py b/plans/api.py index 58bd4fc1..dbd8eca2 100644 --- a/plans/api.py +++ b/plans/api.py @@ -1,789 +1,789 @@ -import logging -import os -import time -import uuid -from typing import Any, Dict, List, Optional, Tuple - -import requests -from django.views.decorators.csrf import csrf_exempt -from rest_framework import status -from rest_framework.decorators import api_view, schema -from rest_framework.response import Response - -from dpr.utils import transform_name -from moderation.utils.update_csdb import sync_form_type -from nrm_app.settings import ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC, TMP_LOCATION -from utilities.auth_check_decorator import api_security_check -from utilities.auth_utils import auth_free -from utilities.constants import ( - ODK_SYNC_URL_AGRI_FEEDBACK, - ODK_SYNC_URL_AGRI_MAINTENANCE, - ODK_SYNC_URL_AGROHORTICULTURE, - ODK_SYNC_URL_CROP, - ODK_SYNC_URL_GW_FEEDBACK, - ODK_SYNC_URL_GW_MAINTENANCE, - ODK_SYNC_URL_IRRIGATION_STRUCTURE, - ODK_SYNC_URL_LIVELIHOOD, - ODK_SYNC_URL_RECHARGE_STRUCTURE, - ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, - ODK_SYNC_URL_SETTLEMENT, - ODK_SYNC_URL_SWB_FEEDBACK, - ODK_SYNC_URL_WATER_STRUCTURES, - ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, - ODK_SYNC_URL_WELL, -) - -logger = logging.getLogger(__name__) - -from .build_layer import build_layer -from .models import ODKSyncLog, PlanApp, Plan -from .serializers import PlanAppSerializer -from .utils import fetch_bearer_token, fetch_db_data -from geoadmin.models import GramPanchayat -from django.db.models import Q - -_COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( - "layer_name", - "plan_id", - "plan_name", - "district_name", - "block_name", -) - -_LAYER_KIND_CONFIG: Dict[str, Dict[str, str]] = { - "resources": {"type_field": "resource_type", "singular": "resource"}, - "works": {"type_field": "work_type", "singular": "work"}, -} - - -# MARK: Get Plans API -@api_security_check(auth_type="Auth_free") -@schema(None) -def get_plans(request): - """ - Get Plans API - - Args: - block_id (str, optional): Block ID. Defaults to None. - - Returns: - Response: JSON response containing a list of plans of a block or all the plans - """ - try: - block_id = request.query_params.get("block_id", None) - if block_id is not None: - plans = Plan.objects.filter(block=block_id) - else: - plans = Plan.objects.all() - serializer = PlanAppSerializer(plans, many=True) - response = {"plans": serializer.data} - - return Response(response, status=status.HTTP_200_OK) - except Exception as e: - print("Exception in get_plans api :: ", e) - return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@api_view(["POST"]) -@auth_free -@schema(None) -def add_plan(request): - if request.method == "POST": - serializer = PlanAppSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() # Save the new Plan instance if validation passes - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {"error": "Method not allowed"}, status=status.HTTP_405_METHOD_NOT_ALLOWED - ) - - -# MARK: Build Layer Helpers (shared by /add_resources and /add_works) -def _extract_payload(request, kind: str) -> Tuple[Optional[Dict[str, Any]], List[str]]: - """Pull and normalize the request payload. Returns (payload, missing_fields).""" - type_field = _LAYER_KIND_CONFIG[kind]["type_field"] - required = (*_COMMON_REQUIRED_FIELDS, type_field) - - missing = [f for f in required if not request.data.get(f)] - if missing: - return None, missing - - def _lower(value: Any) -> Any: - return value.lower() if isinstance(value, str) else value - - return { - "layer_name": _lower(request.data.get("layer_name")), - "item_type": _lower(request.data.get(type_field)), - "plan_id": request.data.get("plan_id"), - "plan_name": _lower(request.data.get("plan_name")), - "district": _lower(request.data.get("district_name")), - "block": _lower(request.data.get("block_name")), - }, [] - - -def _expected_layer_store_name( - item_type: str, plan_id: Any, district: str, block: str -) -> str: - """Mirror the naming convention used by build_layer.build_layer for transparency.""" - return f"{item_type}_{plan_id}_{district}_{transform_name(name=block)}" - - -def _safe_unlink(csv_path: str, request_id: str, kind: str) -> None: - try: - if os.path.exists(csv_path): - os.remove(csv_path) - logger.info( - f"[{request_id}] {kind}.build: cleaned up temp CSV at {csv_path}" - ) - except OSError as exc: - logger.warning( - f"[{request_id}] {kind}.build: failed to remove temp CSV at " - f"{csv_path}: {exc}" - ) - - -def _error_response( - request_id: str, - code: str, - message: str, - http_status: int, - extra: Optional[Dict[str, Any]] = None, -) -> Response: - data: Dict[str, Any] = {"request_id": request_id} - if extra: - data.update(extra) - return Response( - { - "status": "error", - "code": code, - "error": message, - "data": data, - }, - status=http_status, - ) - - -def _build_layer_for_kind(request, kind: str) -> Response: - """ - Shared workflow for /add_resources and /add_works: - 1. validate payload - 2. trigger incremental ODK -> DB sync (best-effort) - 3. fetch source records from DB and stage a CSV - 4. publish the layer to GeoServer - 5. clean up the temp CSV and return a structured response - """ - request_id = uuid.uuid4().hex[:12] - type_field = _LAYER_KIND_CONFIG[kind]["type_field"] - singular = _LAYER_KIND_CONFIG[kind]["singular"] - started_at = time.perf_counter() - - logger.info( - f"[{request_id}] {kind}.build: request received " - f"(content_type={request.content_type}, keys={list(request.data.keys())})" - ) - - payload, missing = _extract_payload(request, kind) - if missing: - logger.warning( - f"[{request_id}] {kind}.build: rejecting request — " - f"missing/empty fields: {missing}" - ) - return _error_response( - request_id, - code="missing_fields", - message=f"Missing required field(s): {', '.join(missing)}.", - http_status=status.HTTP_400_BAD_REQUEST, - extra={"missing_fields": missing}, - ) - - item_type = payload["item_type"] - plan_id = payload["plan_id"] - plan_name = payload["plan_name"] - district = payload["district"] - block = payload["block"] - layer_name = payload["layer_name"] - - context = { - type_field: item_type, - "plan_id": plan_id, - "plan_name": plan_name, - "district": district, - "block": block, - "layer_name": layer_name, - } - logger.info(f"[{request_id}] {kind}.build: payload normalized — {context}") - - csv_path = os.path.join(TMP_LOCATION, f"{item_type}_{plan_id}_{block}.csv") - logger.info(f"[{request_id}] {kind}.build: temp CSV path resolved to {csv_path}") - - sync_started = time.perf_counter() - logger.info( - f"[{request_id}] {kind}.build: triggering incremental ODK sync for " - f"{type_field}={item_type}" - ) - sync_ok = sync_form_type(item_type) - sync_ms = int((time.perf_counter() - sync_started) * 1000) - if sync_ok: - logger.info( - f"[{request_id}] {kind}.build: ODK sync completed for " - f"{type_field}={item_type} in {sync_ms}ms" - ) - else: - logger.warning( - f"[{request_id}] {kind}.build: ODK sync FAILED for " - f"{type_field}={item_type} in {sync_ms}ms; proceeding with existing DB data" - ) - - fetch_started = time.perf_counter() - logger.info( - f"[{request_id}] {kind}.build: fetching DB data for {type_field}={item_type}, " - f"plan_id={plan_id}, block={block}" - ) - try: - record_count = fetch_db_data(csv_path, item_type, block, plan_id) - except Exception as exc: - fetch_ms = int((time.perf_counter() - fetch_started) * 1000) - logger.exception( - f"[{request_id}] {kind}.build: unexpected error during fetch_db_data " - f"for {type_field}={item_type}, plan_id={plan_id} " - f"(fetch_ms={fetch_ms}): {exc}" - ) - _safe_unlink(csv_path, request_id, kind) - return _error_response( - request_id, - code="db_fetch_failed", - message="Failed to fetch source data from the database.", - http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, - extra={ - **context, - "details": str(exc), - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - }, - ) - fetch_ms = int((time.perf_counter() - fetch_started) * 1000) - - if not record_count: - total_ms = int((time.perf_counter() - started_at) * 1000) - logger.warning( - f"[{request_id}] {kind}.build: no DB data found for " - f"{type_field}={item_type}, plan_id={plan_id}, block={block} " - f"(sync_ok={sync_ok}, fetch_ms={fetch_ms}, total_ms={total_ms})" - ) - return _error_response( - request_id, - code="no_data_found", - message=( - f"No records found for {type_field}='{item_type}', " - f"plan_id='{plan_id}', block='{block}'." - ), - http_status=status.HTTP_404_NOT_FOUND, - extra={ - **context, - "record_count": 0, - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "total_duration_ms": total_ms, - }, - ) - logger.info( - f"[{request_id}] {kind}.build: DB fetch staged {record_count} row(s) " - f"in {fetch_ms}ms" - ) - - layer_store_name = _expected_layer_store_name(item_type, plan_id, district, block) - build_started = time.perf_counter() - logger.info( - f"[{request_id}] {kind}.build: publishing GeoServer layer " - f"workspace='{kind}', store='{layer_store_name}'" - ) - try: - success = build_layer( - layer_type=kind, - item_type=item_type, - plan_id=plan_id, - district=district, - block=block, - csv_path=csv_path, - ) - except Exception as exc: - build_ms = int((time.perf_counter() - build_started) * 1000) - total_ms = int((time.perf_counter() - started_at) * 1000) - logger.exception( - f"[{request_id}] {kind}.build: unexpected error during build_layer for " - f"{type_field}={item_type}, plan_id={plan_id} " - f"(build_ms={build_ms}, total_ms={total_ms}): {exc}" - ) - _safe_unlink(csv_path, request_id, kind) - return _error_response( - request_id, - code="internal_error", - message="An unexpected error occurred while building the layer.", - http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, - extra={ - **context, - "layer_store_name": layer_store_name, - "details": str(exc), - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "build_duration_ms": build_ms, - "total_duration_ms": total_ms, - }, - ) - finally: - _safe_unlink(csv_path, request_id, kind) - - build_ms = int((time.perf_counter() - build_started) * 1000) - total_ms = int((time.perf_counter() - started_at) * 1000) - - if not success: - logger.error( - f"[{request_id}] {kind}.build: build_layer returned False for " - f"{type_field}={item_type}, plan_id={plan_id} " - f"(build_ms={build_ms}, total_ms={total_ms})" - ) - return _error_response( - request_id, - code="layer_build_failed", - message=( - f"Failed to publish GeoServer layer '{layer_store_name}'. " - "See server logs for details." - ), - http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, - extra={ - **context, - "layer_store_name": layer_store_name, - "record_count": record_count, - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "build_duration_ms": build_ms, - "total_duration_ms": total_ms, - }, - ) - - logger.info( - f"[{request_id}] {kind}.build: SUCCESS — published layer " - f"'{layer_store_name}' ({record_count} row(s)) in workspace='{kind}' " - f"(sync={sync_ms}ms, fetch={fetch_ms}ms, build={build_ms}ms, total={total_ms}ms)" - ) - return Response( - { - "status": "success", - "code": "layer_published", - "message": ( - f"Successfully published {singular} layer " - f"'{layer_store_name}' to GeoServer with {record_count} record(s)." - ), - "data": { - "request_id": request_id, - "layer_type": kind, - "workspace": kind, - "layer_store_name": layer_store_name, - "record_count": record_count, - **context, - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "build_duration_ms": build_ms, - "total_duration_ms": total_ms, - }, - }, - status=status.HTTP_201_CREATED, - ) - - -# api's for add settlement, add well, add waterbody | add work [new, maintenance] -@api_view(["POST"]) -@auth_free -@schema(None) -def add_resources(request): - """ - Build and publish a GeoServer 'resources' layer for the given plan/block. - - Supported resource_type values: settlement, well, waterbody, cropping. - Layer naming convention: ___. - """ - return _build_layer_for_kind(request, kind="resources") - - -@api_view(["POST"]) -@auth_free -@schema(None) -def add_works(request): - """ - Build and publish a GeoServer 'works' layer for the given plan/block. - - Supported work_type values: - plan_gw — new recharge structures (groundwater) - main_gw — maintenance of recharge structures - plan_agri — new irrigation structures - main_agri — maintenance of irrigation structures - main_swb — surface water body maintenance - main_swb_rs — remote-sensed surface water body maintenance - livelihood — livelihood - agrohorticulture — agrohorticulture - - Layer naming convention: ___. - """ - return _build_layer_for_kind(request, kind="works") - - -# MARK: SYNC OFFLINE DATA HELPER FUNCTIONS -def _get_resource_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping for different resource types.""" - return { - "settlement": { - "url": ODK_SYNC_URL_SETTLEMENT, - "success_message": "Settlement data synced successfully", - }, - "well": { - "url": ODK_SYNC_URL_WELL, - "success_message": "Well data synced successfully", - }, - "water_structures": { - "url": ODK_SYNC_URL_WATER_STRUCTURES, - "success_message": "Water structures data synced successfully", - }, - "cropping_pattern": { - "url": ODK_SYNC_URL_CROP, - "success_message": "Cropping pattern data synced successfully", - }, - } - - -def _get_work_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping for different work types.""" - return { - "recharge_st": { - "url": ODK_SYNC_URL_RECHARGE_STRUCTURE, - "success_message": "Recharge structure data synced successfully", - }, - "irrigation_st": { - "url": ODK_SYNC_URL_IRRIGATION_STRUCTURE, - "success_message": "Irrigation structure data synced successfully", - }, - "propose_maintenance_recharge_st": { - "url": ODK_SYNC_URL_GW_MAINTENANCE, - "success_message": "Recharge structure maintenance data synced successfully", - }, - "propose_maintenance_rs_swb": { - "url": ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, - "success_message": "Surface water body maintenance data synced successfully", - }, - "propose_maintenance_ws_swb": { - "url": ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, - "success_message": "Water structures maintenance data synced successfully", - }, - "propose_maintenance_irrigation_st": { - "url": ODK_SYNC_URL_AGRI_MAINTENANCE, - "success_message": "Irrigation structure maintenance data synced successfully", - }, - "livelihood": { - "url": ODK_SYNC_URL_LIVELIHOOD, - "success_message": "Livelihood data synced successfully", - }, - "agrohorticulture": { - "url": ODK_SYNC_URL_AGROHORTICULTURE, - "success_message": "Agrohorticulture data synced successfully", - }, - } - - -def _get_feedback_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping of different feedback types""" - return { - "gw_feedback": { - "url": ODK_SYNC_URL_GW_FEEDBACK, - "success_message": "Groundwater feedback data synced successfully", - }, - "swb_feedback": { - "url": ODK_SYNC_URL_SWB_FEEDBACK, - "success_message": "Surface water body feedback data synced successfully", - }, - "agri_feedback": { - "url": ODK_SYNC_URL_AGRI_FEEDBACK, - "success_message": "Agriculture feedback data synced successfully", - }, - } - - -def _validate_sync_request( - request, resource_type: str = None, work_type: str = None, feedback_type: str = None -) -> Optional[Response]: - """Validate the sync request parameters and content type.""" - - if not resource_type and not work_type and not feedback_type: - return Response( - { - "error": "Must specify either resource_type or work_type or feedback_type" - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - if resource_type: - valid_resources = ["settlement", "well", "water_structures", "cropping_pattern"] - if resource_type not in valid_resources: - return Response( - {"error": f"Invalid resource type. Must be one of {valid_resources}"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if work_type: - valid_work_types = [ - "recharge_st", - "irrigation_st", - "propose_maintenance_recharge_st", - "propose_maintenance_rs_swb", - "propose_maintenance_ws_swb", - "propose_maintenance_irrigation_st", - "livelihood", - "agrohorticulture", - ] - if work_type not in valid_work_types: - return Response( - {"error": f"Invalid work type. Must be one of {valid_work_types}"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if feedback_type: - valid_feedback_types = ["gw_feedback", "swb_feedback", "agri_feedback"] - if feedback_type not in valid_feedback_types: - return Response( - { - "error": f"Invalid feedback type. Must be one of {valid_feedback_types}" - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - if request.content_type != "application/xml": - return Response( - {"error": "Content-Type must be application/xml"}, - status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, - ) - - return None - - -def _sync_to_odk( - xml_string: str, - config: Dict[str, Any], - bearer_token: str, - category: str, - sync_type: str, -) -> Response: - """Handle the actual sync to ODK for a specific resource or work type.""" - sync_log = ODKSyncLog.objects.create( - category=category, - sync_type=sync_type, - xml_content=xml_string, - odk_url=config["url"], - status=ODKSyncLog.SyncStatus.PENDING, - ) - - try: - response = requests.post( - config["url"], - headers={ - "Content-Type": "application/xml", - "Authorization": f"Bearer {bearer_token}", - }, - data=xml_string, - ) - response.raise_for_status() - - odk_response = response.json() if response.content else None - sync_log.status = ODKSyncLog.SyncStatus.SUCCESS - sync_log.odk_response = odk_response - sync_log.save(update_fields=["status", "odk_response"]) - - return Response( - { - "sync_status": True, - "message": config["success_message"], - "odk_response": odk_response, - }, - status=status.HTTP_201_CREATED, - ) - - except requests.exceptions.RequestException as e: - item_name = config["success_message"].split()[0].lower() - print(f"Error syncing {item_name} data to ODK: {str(e)}") - - sync_log.status = ODKSyncLog.SyncStatus.FAILED - sync_log.error_details = str(e) - sync_log.save(update_fields=["status", "error_details"]) - - return Response( - { - "sync_status": False, - "error": f"Failed to sync {item_name} data to ODK", - "details": str(e), - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - -# MARK: SYNC OFFLINE DATA -# API to sync offline data coming from CC app -@api_view(["POST"]) -@csrf_exempt -@auth_free -@schema(None) -def sync_offline_data(request, resource_type=None, work_type=None, feedback_type=None): - """ - Sync data to ODK based on resource type or work type - Resource types: settlement, well, water_structures, cropping_pattern - Work types: "recharge_st", "irrigation_st", "propose_maintenance_recharge_st", "propose_maintenance_rs_swb", - "propose_maintenance_ws_swb", "propose_maintenance_irrigation_st", "livelihood", - Feedback types: "gw_feedback", "swb_feedback", "agri_feedback" - - fetch Bearer Token from ODK - - send xmlString to ODK - """ - print( - f"Inside sync_offline_data API for resource type: {resource_type}, work type: {work_type}, feedback type: {feedback_type}" - ) - - # Validate request - validation_error = _validate_sync_request( - request, resource_type, work_type, feedback_type - ) - if validation_error: - return validation_error - - if resource_type: - configs = _get_resource_config() - config = configs[resource_type] - category = ODKSyncLog.SyncCategory.RESOURCE - item_type = resource_type - elif work_type: - configs = _get_work_config() - config = configs[work_type] - category = ODKSyncLog.SyncCategory.WORK - item_type = work_type - elif feedback_type: - configs = _get_feedback_config() - config = configs[feedback_type] - category = ODKSyncLog.SyncCategory.FEEDBACK - item_type = feedback_type - else: - return Response( - {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST - ) - - xml_string = request.body.decode("utf-8") - print(f"Sync Category: {category}, Type: {item_type}") - - try: - bearer_token = fetch_bearer_token(ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC) - print("Bearer Token: ", bearer_token) - - return _sync_to_odk(xml_string, config, bearer_token, category, item_type) - - except Exception as e: - print("Exception in sync_offline_data api :: ", e) - return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -# map plan to gp api -@api_view(["PATCh"]) -@schema(None) -def map_plan_to_gp(request): - - plan_id = request.data.get("plan_id") - gp_id = request.data.get("gp_id") - - if not plan_id or not gp_id: - return Response( - { - "success": False, - "message": "plan_id and gp_id are required", - }, - status=400, - ) - - try: - plan = PlanApp.objects.get(id=plan_id) - - except PlanApp.DoesNotExist: - return Response( - { - "success": False, - "message": "Plan not found", - }, - status=404, - ) - - try: - gp = GramPanchayat.objects.get(gram_panchayat_code=gp_id) - - except GramPanchayat.DoesNotExist: - return Response( - { - "success": False, - "message": "Gram Panchayat not found", - }, - status=404, - ) - - # GP should belong to same tehsil - - if plan.tehsil_soi_id != gp.tehsil_id: - return Response( - { - "success": False, - "message": "Selected GP does not belong to plan tehsil", - }, - status=400, - ) - - plan.gp = gp - plan.updated_by = request.user - - plan.save(update_fields=["gp", "updated_by", "updated_at"]) - - return Response( - { - "success": True, - "message": "Plan mapped with GP successfully", - "data": { - "plan_id": plan.id, - "gp_id": gp.gram_panchayat_code, - "gp_name": gp.gram_panchayat_name, - }, - } - ) - - -@api_view(["GET"]) -@schema(None) -def plan_count(request): - """ - gives plan count on the basis of org_id or project_id and filter - """ - org_id = request.query_params.get("org_id") - project_id = request.query_params.get("project_id") - is_completed = request.query_params.get("is_completed") - - queryset = PlanApp.objects.filter(enabled=True).exclude( - Q(plan__icontains="test") | Q(plan__icontains="demo") - ) - - if is_completed: - queryset = queryset.filter(is_completed=True).exclude( - Q(plan__icontains="test") | Q(plan__icontains="demo") - ) - - if org_id: - plan_count = queryset.filter(organization=org_id).count() - elif project_id: - plan_count = queryset.filter(project=project_id).count() - else: - plan_count = 0 - - return Response({"plan_count": plan_count}) +import logging +import os +import time +import uuid +from typing import Any, Dict, List, Optional, Tuple + +import requests +from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.decorators import api_view, schema +from rest_framework.response import Response + +from dpr.utils import transform_name +from moderation.utils.update_csdb import sync_form_type +from nrm_app.settings import ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC, TMP_LOCATION +from utilities.auth_check_decorator import api_security_check +from utilities.auth_utils import auth_free +from utilities.constants import ( + ODK_SYNC_URL_AGRI_FEEDBACK, + ODK_SYNC_URL_AGRI_MAINTENANCE, + ODK_SYNC_URL_AGROHORTICULTURE, + ODK_SYNC_URL_CROP, + ODK_SYNC_URL_GW_FEEDBACK, + ODK_SYNC_URL_GW_MAINTENANCE, + ODK_SYNC_URL_IRRIGATION_STRUCTURE, + ODK_SYNC_URL_LIVELIHOOD, + ODK_SYNC_URL_RECHARGE_STRUCTURE, + ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, + ODK_SYNC_URL_SETTLEMENT, + ODK_SYNC_URL_SWB_FEEDBACK, + ODK_SYNC_URL_WATER_STRUCTURES, + ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, + ODK_SYNC_URL_WELL, +) + +logger = logging.getLogger(__name__) + +from .build_layer import build_layer +from .models import ODKSyncLog, PlanApp, Plan +from .serializers import PlanAppSerializer +from .utils import fetch_bearer_token, fetch_db_data +from geoadmin.models import GramPanchayat +from django.db.models import Q + +_COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( + "layer_name", + "plan_id", + "plan_name", + "district_name", + "block_name", +) + +_LAYER_KIND_CONFIG: Dict[str, Dict[str, str]] = { + "resources": {"type_field": "resource_type", "singular": "resource"}, + "works": {"type_field": "work_type", "singular": "work"}, +} + + +# MARK: Get Plans API +@api_security_check(auth_type="Auth_free") +@schema(None) +def get_plans(request): + """ + Get Plans API + + Args: + block_id (str, optional): Block ID. Defaults to None. + + Returns: + Response: JSON response containing a list of plans of a block or all the plans + """ + try: + block_id = request.query_params.get("block_id", None) + if block_id is not None: + plans = Plan.objects.filter(block=block_id) + else: + plans = Plan.objects.all() + serializer = PlanAppSerializer(plans, many=True) + response = {"plans": serializer.data} + + return Response(response, status=status.HTTP_200_OK) + except Exception as e: + print("Exception in get_plans api :: ", e) + return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +@api_view(["POST"]) +@auth_free +@schema(None) +def add_plan(request): + if request.method == "POST": + serializer = PlanAppSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() # Save the new Plan instance if validation passes + return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Method not allowed"}, status=status.HTTP_405_METHOD_NOT_ALLOWED + ) + + +# MARK: Build Layer Helpers (shared by /add_resources and /add_works) +def _extract_payload(request, kind: str) -> Tuple[Optional[Dict[str, Any]], List[str]]: + """Pull and normalize the request payload. Returns (payload, missing_fields).""" + type_field = _LAYER_KIND_CONFIG[kind]["type_field"] + required = (*_COMMON_REQUIRED_FIELDS, type_field) + + missing = [f for f in required if not request.data.get(f)] + if missing: + return None, missing + + def _lower(value: Any) -> Any: + return value.lower() if isinstance(value, str) else value + + return { + "layer_name": _lower(request.data.get("layer_name")), + "item_type": _lower(request.data.get(type_field)), + "plan_id": request.data.get("plan_id"), + "plan_name": _lower(request.data.get("plan_name")), + "district": _lower(request.data.get("district_name")), + "block": _lower(request.data.get("block_name")), + }, [] + + +def _expected_layer_store_name( + item_type: str, plan_id: Any, district: str, block: str +) -> str: + """Mirror the naming convention used by build_layer.build_layer for transparency.""" + return f"{item_type}_{plan_id}_{district}_{transform_name(name=block)}" + + +def _safe_unlink(csv_path: str, request_id: str, kind: str) -> None: + try: + if os.path.exists(csv_path): + os.remove(csv_path) + logger.info( + f"[{request_id}] {kind}.build: cleaned up temp CSV at {csv_path}" + ) + except OSError as exc: + logger.warning( + f"[{request_id}] {kind}.build: failed to remove temp CSV at " + f"{csv_path}: {exc}" + ) + + +def _error_response( + request_id: str, + code: str, + message: str, + http_status: int, + extra: Optional[Dict[str, Any]] = None, +) -> Response: + data: Dict[str, Any] = {"request_id": request_id} + if extra: + data.update(extra) + return Response( + { + "status": "error", + "code": code, + "error": message, + "data": data, + }, + status=http_status, + ) + + +def _build_layer_for_kind(request, kind: str) -> Response: + """ + Shared workflow for /add_resources and /add_works: + 1. validate payload + 2. trigger incremental ODK -> DB sync (best-effort) + 3. fetch source records from DB and stage a CSV + 4. publish the layer to GeoServer + 5. clean up the temp CSV and return a structured response + """ + request_id = uuid.uuid4().hex[:12] + type_field = _LAYER_KIND_CONFIG[kind]["type_field"] + singular = _LAYER_KIND_CONFIG[kind]["singular"] + started_at = time.perf_counter() + + logger.info( + f"[{request_id}] {kind}.build: request received " + f"(content_type={request.content_type}, keys={list(request.data.keys())})" + ) + + payload, missing = _extract_payload(request, kind) + if missing: + logger.warning( + f"[{request_id}] {kind}.build: rejecting request — " + f"missing/empty fields: {missing}" + ) + return _error_response( + request_id, + code="missing_fields", + message=f"Missing required field(s): {', '.join(missing)}.", + http_status=status.HTTP_400_BAD_REQUEST, + extra={"missing_fields": missing}, + ) + + item_type = payload["item_type"] + plan_id = payload["plan_id"] + plan_name = payload["plan_name"] + district = payload["district"] + block = payload["block"] + layer_name = payload["layer_name"] + + context = { + type_field: item_type, + "plan_id": plan_id, + "plan_name": plan_name, + "district": district, + "block": block, + "layer_name": layer_name, + } + logger.info(f"[{request_id}] {kind}.build: payload normalized — {context}") + + csv_path = os.path.join(TMP_LOCATION, f"{item_type}_{plan_id}_{block}.csv") + logger.info(f"[{request_id}] {kind}.build: temp CSV path resolved to {csv_path}") + + sync_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: triggering incremental ODK sync for " + f"{type_field}={item_type}" + ) + sync_ok = sync_form_type(item_type) + sync_ms = int((time.perf_counter() - sync_started) * 1000) + if sync_ok: + logger.info( + f"[{request_id}] {kind}.build: ODK sync completed for " + f"{type_field}={item_type} in {sync_ms}ms" + ) + else: + logger.warning( + f"[{request_id}] {kind}.build: ODK sync FAILED for " + f"{type_field}={item_type} in {sync_ms}ms; proceeding with existing DB data" + ) + + fetch_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: fetching DB data for {type_field}={item_type}, " + f"plan_id={plan_id}, block={block}" + ) + try: + record_count = fetch_db_data(csv_path, item_type, block, plan_id) + except Exception as exc: + fetch_ms = int((time.perf_counter() - fetch_started) * 1000) + logger.exception( + f"[{request_id}] {kind}.build: unexpected error during fetch_db_data " + f"for {type_field}={item_type}, plan_id={plan_id} " + f"(fetch_ms={fetch_ms}): {exc}" + ) + _safe_unlink(csv_path, request_id, kind) + return _error_response( + request_id, + code="db_fetch_failed", + message="Failed to fetch source data from the database.", + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "details": str(exc), + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + }, + ) + fetch_ms = int((time.perf_counter() - fetch_started) * 1000) + + if not record_count: + total_ms = int((time.perf_counter() - started_at) * 1000) + logger.warning( + f"[{request_id}] {kind}.build: no DB data found for " + f"{type_field}={item_type}, plan_id={plan_id}, block={block} " + f"(sync_ok={sync_ok}, fetch_ms={fetch_ms}, total_ms={total_ms})" + ) + return _error_response( + request_id, + code="no_data_found", + message=( + f"No records found for {type_field}='{item_type}', " + f"plan_id='{plan_id}', block='{block}'." + ), + http_status=status.HTTP_404_NOT_FOUND, + extra={ + **context, + "record_count": 0, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "total_duration_ms": total_ms, + }, + ) + logger.info( + f"[{request_id}] {kind}.build: DB fetch staged {record_count} row(s) " + f"in {fetch_ms}ms" + ) + + layer_store_name = _expected_layer_store_name(item_type, plan_id, district, block) + build_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: publishing GeoServer layer " + f"workspace='{kind}', store='{layer_store_name}'" + ) + try: + success = build_layer( + layer_type=kind, + item_type=item_type, + plan_id=plan_id, + district=district, + block=block, + csv_path=csv_path, + ) + except Exception as exc: + build_ms = int((time.perf_counter() - build_started) * 1000) + total_ms = int((time.perf_counter() - started_at) * 1000) + logger.exception( + f"[{request_id}] {kind}.build: unexpected error during build_layer for " + f"{type_field}={item_type}, plan_id={plan_id} " + f"(build_ms={build_ms}, total_ms={total_ms}): {exc}" + ) + _safe_unlink(csv_path, request_id, kind) + return _error_response( + request_id, + code="internal_error", + message="An unexpected error occurred while building the layer.", + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "layer_store_name": layer_store_name, + "details": str(exc), + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + ) + finally: + _safe_unlink(csv_path, request_id, kind) + + build_ms = int((time.perf_counter() - build_started) * 1000) + total_ms = int((time.perf_counter() - started_at) * 1000) + + if not success: + logger.error( + f"[{request_id}] {kind}.build: build_layer returned False for " + f"{type_field}={item_type}, plan_id={plan_id} " + f"(build_ms={build_ms}, total_ms={total_ms})" + ) + return _error_response( + request_id, + code="layer_build_failed", + message=( + f"Failed to publish GeoServer layer '{layer_store_name}'. " + "See server logs for details." + ), + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "layer_store_name": layer_store_name, + "record_count": record_count, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + ) + + logger.info( + f"[{request_id}] {kind}.build: SUCCESS — published layer " + f"'{layer_store_name}' ({record_count} row(s)) in workspace='{kind}' " + f"(sync={sync_ms}ms, fetch={fetch_ms}ms, build={build_ms}ms, total={total_ms}ms)" + ) + return Response( + { + "status": "success", + "code": "layer_published", + "message": ( + f"Successfully published {singular} layer " + f"'{layer_store_name}' to GeoServer with {record_count} record(s)." + ), + "data": { + "request_id": request_id, + "layer_type": kind, + "workspace": kind, + "layer_store_name": layer_store_name, + "record_count": record_count, + **context, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + }, + status=status.HTTP_201_CREATED, + ) + + +# api's for add settlement, add well, add waterbody | add work [new, maintenance] +@api_view(["POST"]) +@auth_free +@schema(None) +def add_resources(request): + """ + Build and publish a GeoServer 'resources' layer for the given plan/block. + + Supported resource_type values: settlement, well, waterbody, cropping. + Layer naming convention: ___. + """ + return _build_layer_for_kind(request, kind="resources") + + +@api_view(["POST"]) +@auth_free +@schema(None) +def add_works(request): + """ + Build and publish a GeoServer 'works' layer for the given plan/block. + + Supported work_type values: + plan_gw — new recharge structures (groundwater) + main_gw — maintenance of recharge structures + plan_agri — new irrigation structures + main_agri — maintenance of irrigation structures + main_swb — surface water body maintenance + main_swb_rs — remote-sensed surface water body maintenance + livelihood — livelihood + agrohorticulture — agrohorticulture + + Layer naming convention: ___. + """ + return _build_layer_for_kind(request, kind="works") + + +# MARK: SYNC OFFLINE DATA HELPER FUNCTIONS +def _get_resource_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping for different resource types.""" + return { + "settlement": { + "url": ODK_SYNC_URL_SETTLEMENT, + "success_message": "Settlement data synced successfully", + }, + "well": { + "url": ODK_SYNC_URL_WELL, + "success_message": "Well data synced successfully", + }, + "water_structures": { + "url": ODK_SYNC_URL_WATER_STRUCTURES, + "success_message": "Water structures data synced successfully", + }, + "cropping_pattern": { + "url": ODK_SYNC_URL_CROP, + "success_message": "Cropping pattern data synced successfully", + }, + } + + +def _get_work_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping for different work types.""" + return { + "recharge_st": { + "url": ODK_SYNC_URL_RECHARGE_STRUCTURE, + "success_message": "Recharge structure data synced successfully", + }, + "irrigation_st": { + "url": ODK_SYNC_URL_IRRIGATION_STRUCTURE, + "success_message": "Irrigation structure data synced successfully", + }, + "propose_maintenance_recharge_st": { + "url": ODK_SYNC_URL_GW_MAINTENANCE, + "success_message": "Recharge structure maintenance data synced successfully", + }, + "propose_maintenance_rs_swb": { + "url": ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, + "success_message": "Surface water body maintenance data synced successfully", + }, + "propose_maintenance_ws_swb": { + "url": ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, + "success_message": "Water structures maintenance data synced successfully", + }, + "propose_maintenance_irrigation_st": { + "url": ODK_SYNC_URL_AGRI_MAINTENANCE, + "success_message": "Irrigation structure maintenance data synced successfully", + }, + "livelihood": { + "url": ODK_SYNC_URL_LIVELIHOOD, + "success_message": "Livelihood data synced successfully", + }, + "agrohorticulture": { + "url": ODK_SYNC_URL_AGROHORTICULTURE, + "success_message": "Agrohorticulture data synced successfully", + }, + } + + +def _get_feedback_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping of different feedback types""" + return { + "gw_feedback": { + "url": ODK_SYNC_URL_GW_FEEDBACK, + "success_message": "Groundwater feedback data synced successfully", + }, + "swb_feedback": { + "url": ODK_SYNC_URL_SWB_FEEDBACK, + "success_message": "Surface water body feedback data synced successfully", + }, + "agri_feedback": { + "url": ODK_SYNC_URL_AGRI_FEEDBACK, + "success_message": "Agriculture feedback data synced successfully", + }, + } + + +def _validate_sync_request( + request, resource_type: str = None, work_type: str = None, feedback_type: str = None +) -> Optional[Response]: + """Validate the sync request parameters and content type.""" + + if not resource_type and not work_type and not feedback_type: + return Response( + { + "error": "Must specify either resource_type or work_type or feedback_type" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + if resource_type: + valid_resources = ["settlement", "well", "water_structures", "cropping_pattern"] + if resource_type not in valid_resources: + return Response( + {"error": f"Invalid resource type. Must be one of {valid_resources}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if work_type: + valid_work_types = [ + "recharge_st", + "irrigation_st", + "propose_maintenance_recharge_st", + "propose_maintenance_rs_swb", + "propose_maintenance_ws_swb", + "propose_maintenance_irrigation_st", + "livelihood", + "agrohorticulture", + ] + if work_type not in valid_work_types: + return Response( + {"error": f"Invalid work type. Must be one of {valid_work_types}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if feedback_type: + valid_feedback_types = ["gw_feedback", "swb_feedback", "agri_feedback"] + if feedback_type not in valid_feedback_types: + return Response( + { + "error": f"Invalid feedback type. Must be one of {valid_feedback_types}" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + if request.content_type != "application/xml": + return Response( + {"error": "Content-Type must be application/xml"}, + status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, + ) + + return None + + +def _sync_to_odk( + xml_string: str, + config: Dict[str, Any], + bearer_token: str, + category: str, + sync_type: str, +) -> Response: + """Handle the actual sync to ODK for a specific resource or work type.""" + sync_log = ODKSyncLog.objects.create( + category=category, + sync_type=sync_type, + xml_content=xml_string, + odk_url=config["url"], + status=ODKSyncLog.SyncStatus.PENDING, + ) + + try: + response = requests.post( + config["url"], + headers={ + "Content-Type": "application/xml", + "Authorization": f"Bearer {bearer_token}", + }, + data=xml_string, + ) + response.raise_for_status() + + odk_response = response.json() if response.content else None + sync_log.status = ODKSyncLog.SyncStatus.SUCCESS + sync_log.odk_response = odk_response + sync_log.save(update_fields=["status", "odk_response"]) + + return Response( + { + "sync_status": True, + "message": config["success_message"], + "odk_response": odk_response, + }, + status=status.HTTP_201_CREATED, + ) + + except requests.exceptions.RequestException as e: + item_name = config["success_message"].split()[0].lower() + print(f"Error syncing {item_name} data to ODK: {str(e)}") + + sync_log.status = ODKSyncLog.SyncStatus.FAILED + sync_log.error_details = str(e) + sync_log.save(update_fields=["status", "error_details"]) + + return Response( + { + "sync_status": False, + "error": f"Failed to sync {item_name} data to ODK", + "details": str(e), + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + +# MARK: SYNC OFFLINE DATA +# API to sync offline data coming from CC app +@api_view(["POST"]) +@csrf_exempt +@auth_free +@schema(None) +def sync_offline_data(request, resource_type=None, work_type=None, feedback_type=None): + """ + Sync data to ODK based on resource type or work type + Resource types: settlement, well, water_structures, cropping_pattern + Work types: "recharge_st", "irrigation_st", "propose_maintenance_recharge_st", "propose_maintenance_rs_swb", + "propose_maintenance_ws_swb", "propose_maintenance_irrigation_st", "livelihood", + Feedback types: "gw_feedback", "swb_feedback", "agri_feedback" + - fetch Bearer Token from ODK + - send xmlString to ODK + """ + print( + f"Inside sync_offline_data API for resource type: {resource_type}, work type: {work_type}, feedback type: {feedback_type}" + ) + + # Validate request + validation_error = _validate_sync_request( + request, resource_type, work_type, feedback_type + ) + if validation_error: + return validation_error + + if resource_type: + configs = _get_resource_config() + config = configs[resource_type] + category = ODKSyncLog.SyncCategory.RESOURCE + item_type = resource_type + elif work_type: + configs = _get_work_config() + config = configs[work_type] + category = ODKSyncLog.SyncCategory.WORK + item_type = work_type + elif feedback_type: + configs = _get_feedback_config() + config = configs[feedback_type] + category = ODKSyncLog.SyncCategory.FEEDBACK + item_type = feedback_type + else: + return Response( + {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + ) + + xml_string = request.body.decode("utf-8") + print(f"Sync Category: {category}, Type: {item_type}") + + try: + bearer_token = fetch_bearer_token(ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC) + print("Bearer Token: ", bearer_token) + + return _sync_to_odk(xml_string, config, bearer_token, category, item_type) + + except Exception as e: + print("Exception in sync_offline_data api :: ", e) + return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +# map plan to gp api +@api_view(["PATCh"]) +@schema(None) +def map_plan_to_gp(request): + + plan_id = request.data.get("plan_id") + gp_id = request.data.get("gp_id") + + if not plan_id or not gp_id: + return Response( + { + "success": False, + "message": "plan_id and gp_id are required", + }, + status=400, + ) + + try: + plan = PlanApp.objects.get(id=plan_id) + + except PlanApp.DoesNotExist: + return Response( + { + "success": False, + "message": "Plan not found", + }, + status=404, + ) + + try: + gp = GramPanchayat.objects.get(gram_panchayat_code=gp_id) + + except GramPanchayat.DoesNotExist: + return Response( + { + "success": False, + "message": "Gram Panchayat not found", + }, + status=404, + ) + + # GP should belong to same tehsil + + if plan.tehsil_soi_id != gp.tehsil_id: + return Response( + { + "success": False, + "message": "Selected GP does not belong to plan tehsil", + }, + status=400, + ) + + plan.gp = gp + plan.updated_by = request.user + + plan.save(update_fields=["gp", "updated_by", "updated_at"]) + + return Response( + { + "success": True, + "message": "Plan mapped with GP successfully", + "data": { + "plan_id": plan.id, + "gp_id": gp.gram_panchayat_code, + "gp_name": gp.gram_panchayat_name, + }, + } + ) + + +@api_view(["GET"]) +@schema(None) +def plan_count(request): + """ + gives plan count on the basis of org_id or project_id and filter + """ + org_id = request.query_params.get("org_id") + project_id = request.query_params.get("project_id") + is_completed = request.query_params.get("is_completed") + + queryset = PlanApp.objects.filter(enabled=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if is_completed: + queryset = queryset.filter(is_completed=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if org_id: + plan_count = queryset.filter(organization=org_id).count() + elif project_id: + plan_count = queryset.filter(project=project_id).count() + else: + plan_count = 0 + + return Response({"plan_count": plan_count}) diff --git a/plans/tests.py b/plans/tests.py index bb68993b..71fe76ea 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -1,556 +1,556 @@ -# plans/tests.py -import csv -import os -import tempfile -from datetime import datetime, timezone -from unittest.mock import patch - -from django.test import TestCase -from django.urls import reverse -from rest_framework.test import APITestCase, APIClient -from rest_framework import status - -from dpr.models import ODK_settlement -from .models import Plan -from .utils import fetch_db_data -from projects.models import Project, AppType -from organization.models import Organization -from users.models import User, UserProjectGroup -from django.contrib.auth.models import Group, Permission - - -class PlanModelTest(TestCase): - def setUp(self): - # Create organization - self.organization = Organization.objects.create(name="Test Organization") - - # Create project with app_type - self.project = Project.objects.create( - name="Test Project", - organization=self.organization, - app_type=AppType.WATERSHED, - enabled=True, - ) - - # Create user - self.user = User.objects.create_user( - username="testuser", - email="test@example.com", - password="password123", - organization=self.organization, - ) - - def test_plan_creation(self): - plan = Plan.objects.create( - name="Test Watershed Plan", - project=self.project, - organization=self.organization, - state="Test State", - district="Test District", - block="Test Block", - village="Test Village", - gram_panchayat="Test GP", - created_by=self.user, - ) - - # Check model attributes - self.assertEqual(plan.name, "Test Watershed Plan") - self.assertEqual(plan.project, self.project) - self.assertEqual(plan.organization, self.organization) - self.assertEqual(plan.state, "Test State") - self.assertEqual(plan.district, "Test District") - self.assertEqual(plan.created_by, self.user) - - -class PlanAPITest(APITestCase): - def setUp(self): - self.client = APIClient() - - # Create organization - self.organization = Organization.objects.create(name="Test Organization") - - # Create project with app_type - self.project = Project.objects.create( - name="Test Project", - organization=self.organization, - app_type=AppType.WATERSHED, - enabled=True, - ) - - # Create admin user - self.admin_user = User.objects.create_user( - username="admin", - email="admin@example.com", - password="password123", - organization=self.organization, - is_superadmin=True, - ) - - # Create edit user - self.edit_user = User.objects.create_user( - username="editor", - email="editor@example.com", - password="password123", - organization=self.organization, - ) - - # Create view user - self.view_user = User.objects.create_user( - username="viewer", - email="viewer@example.com", - password="password123", - organization=self.organization, - ) - - # Create groups and permissions - self.admin_group = Group.objects.create(name="Project Admin") - self.editor_group = Group.objects.create(name="Project Editor") - self.viewer_group = Group.objects.create(name="Project Viewer") - - # Create permissions - Permission.objects.get_or_create( - codename="view_watershed", - name="Can view watershed planning data", - content_type_id=1, # This would typically be correct content type ID - ) - - Permission.objects.get_or_create( - codename="add_watershed", - name="Can add watershed planning data", - content_type_id=1, - ) - - Permission.objects.get_or_create( - codename="change_watershed", - name="Can change watershed planning data", - content_type_id=1, - ) - - Permission.objects.get_or_create( - codename="delete_watershed", - name="Can delete watershed planning data", - content_type_id=1, - ) - - # Assign permissions to groups - view_perm = Permission.objects.get(codename="view_watershed") - add_perm = Permission.objects.get(codename="add_watershed") - change_perm = Permission.objects.get(codename="change_watershed") - delete_perm = Permission.objects.get(codename="delete_watershed") - - self.admin_group.permissions.add(view_perm, add_perm, change_perm, delete_perm) - self.editor_group.permissions.add(view_perm, add_perm, change_perm) - self.viewer_group.permissions.add(view_perm) - - # Assign users to project roles - UserProjectGroup.objects.create( - user=self.edit_user, project=self.project, group=self.editor_group - ) - - UserProjectGroup.objects.create( - user=self.view_user, project=self.project, group=self.viewer_group - ) - - # Create a test plan - self.plan = Plan.objects.create( - name="Test Watershed Plan", - project=self.project, - organization=self.organization, - state="Test State", - district="Test District", - block="Test Block", - village="Test Village", - gram_panchayat="Test GP", - created_by=self.admin_user, - ) - - # URLs - self.plans_list_url = reverse( - "project-plan-list", kwargs={"project_pk": self.project.pk} - ) - self.plan_detail_url = reverse( - "project-plan-detail", - kwargs={"project_pk": self.project.pk, "pk": self.plan.pk}, - ) - - def test_list_plans_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - response = self.client.get(self.plans_list_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - def test_list_plans_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - response = self.client.get(self.plans_list_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - def test_list_plans_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - response = self.client.get(self.plans_list_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - def test_create_plan_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - data = { - "name": "New Watershed Plan", - "state": "New State", - "district": "New District", - "block": "New Block", - "village": "New Village", - "gram_panchayat": "New GP", - } - - response = self.client.post(self.plans_list_url, data) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data["name"], "New Watershed Plan") - self.assertEqual(response.data["state"], "New State") - - # Check plan was created in database - self.assertEqual(Plan.objects.count(), 2) - - def test_create_plan_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - data = { - "name": "Editor Plan", - "state": "Editor State", - "district": "Editor District", - "block": "Editor Block", - "village": "Editor Village", - "gram_panchayat": "Editor GP", - } - - response = self.client.post(self.plans_list_url, data) - - # Editor should be able to create plans - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data["name"], "Editor Plan") - - def test_create_plan_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - data = { - "name": "Viewer Plan", - "state": "Viewer State", - "district": "Viewer District", - "block": "Viewer Block", - "village": "Viewer Village", - "gram_panchayat": "Viewer GP", - } - - response = self.client.post(self.plans_list_url, data) - - # Viewer should not be able to create plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_update_plan_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - data = { - "name": "Updated Plan", - "state": "Updated State", - "district": "Updated District", - "block": "Updated Block", - "village": "Updated Village", - "gram_panchayat": "Updated GP", - } - - response = self.client.put(self.plan_detail_url, data) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["name"], "Updated Plan") - - # Verify database was updated - updated_plan = Plan.objects.get(pk=self.plan.pk) - self.assertEqual(updated_plan.name, "Updated Plan") - - def test_update_plan_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - data = { - "name": "Editor Updated", - "state": self.plan.state, - "district": self.plan.district, - "block": self.plan.block, - "village": self.plan.village, - "gram_panchayat": self.plan.gram_panchayat, - } - - response = self.client.patch(self.plan_detail_url, {"name": "Editor Updated"}) - - # Editor should be able to update plans - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["name"], "Editor Updated") - - def test_update_plan_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - data = {"name": "Viewer Updated"} - - response = self.client.patch(self.plan_detail_url, data) - - # Viewer should not be able to update plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_delete_plan_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - response = self.client.delete(self.plan_detail_url) - - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - # Verify plan was deleted - self.assertEqual(Plan.objects.count(), 0) - - def test_delete_plan_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - response = self.client.delete(self.plan_detail_url) - - # Editor should not be able to delete plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Verify plan was not deleted - self.assertEqual(Plan.objects.count(), 1) - - def test_delete_plan_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - response = self.client.delete(self.plan_detail_url) - - # Viewer should not be able to delete plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Verify plan was not deleted - self.assertEqual(Plan.objects.count(), 1) - - -# Minimal valid ODK settlement JSON (same structure as ODK submissions stored in data_settlement) -def _make_settlement_json(plan_id, block_name, settlement_id="SETT001", review_state="hasIssues"): - return { - "__id": f"uuid:{settlement_id}", - "__system": {"reviewState": review_state, "submissionDate": "2024-01-01T00:00:00Z"}, - "block_name": block_name, - "plan_id": str(plan_id), - "GPS_point": { - "point_mapsappearance": { - "coordinates": [78.5, 20.5] - } - }, - "Settlements_id": settlement_id, - "Settlements_name": "Test Settlement", - "MNREGA_INFORMATION": { - "NREGA_aware": 10, - "NREGA_applied": 5, - "NREGA_job_card": 3, - "total_household": 2, - "NREGA_work_days": 100, - "q1": "yes", - "select_one_Y_N": "yes", - "select_one_demands": "wages", - "select_multiple_issues": "delayed_payment", - "select_one_contributions": "labour", - }, - } - - -def _create_settlement(plan_id, block_name, settlement_id="SETT001", - is_deleted=False, is_moderated=False, review_state="hasIssues", - data_override=None): - data = data_override or _make_settlement_json(plan_id, block_name, settlement_id, review_state) - return ODK_settlement.objects.create( - settlement_id=settlement_id, - settlement_name="Test Settlement", - submission_time=datetime(2024, 1, 1, tzinfo=timezone.utc), - submitted_by="test_user", - status_re=review_state, - latitude=20.5, - longitude=78.5, - block_name=block_name, - number_of_households=10, - largest_caste="General", - smallest_caste="SC", - settlement_status="active", - plan_id=str(plan_id), - plan_name="Test Plan", - uuid=f"uuid:{settlement_id}", - farmer_family={}, - livestock_census={}, - nrega_job_aware=10, - nrega_job_applied=5, - nrega_past_work="yes", - nrega_raise_demand="yes", - nrega_demand="wages", - nrega_issues="delayed_payment", - nrega_community="labour", - data_settlement=data, - is_deleted=is_deleted, - is_moderated=is_moderated, - ) - - -class FetchDbDataTest(TestCase): - - def setUp(self): - self.tmp_dir = tempfile.mkdtemp() - - def _csv_path(self, name="test.csv"): - return os.path.join(self.tmp_dir, name) - - def test_returns_true_and_writes_csv_for_valid_settlement(self): - _create_settlement(plan_id="42", block_name="test block") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - self.assertTrue(os.path.exists(csv_path)) - with open(csv_path) as f: - reader = csv.DictReader(f) - rows = list(reader) - self.assertEqual(len(rows), 1) - self.assertIn("latitude", rows[0]) - self.assertIn("longitude", rows[0]) - self.assertEqual(rows[0]["sett_id"], "SETT001") - self.assertEqual(rows[0]["sett_name"], "Test Settlement") - - def test_moderated_record_uses_moderated_json(self): - moderated_data = _make_settlement_json("42", "test block") - moderated_data["Settlements_name"] = "Moderated Settlement Name" - _create_settlement( - plan_id="42", - block_name="test block", - settlement_id="SETT002", - is_moderated=True, - data_override=moderated_data, - ) - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_name"], "Moderated Settlement Name") - - def test_deleted_records_excluded(self): - _create_settlement(plan_id="42", block_name="test block", is_deleted=True) - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertFalse(result) - self.assertFalse(os.path.exists(csv_path)) - - def test_rejected_submissions_excluded_by_transform(self): - _create_settlement( - plan_id="42", block_name="test block", - settlement_id="SETT003", review_state="rejected" - ) - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertFalse(result) - - def test_returns_false_for_no_matching_records(self): - csv_path = self._csv_path() - result = fetch_db_data(csv_path, "settlement", "nonexistent_block", "99") - self.assertFalse(result) - - def test_returns_false_for_unknown_resource_type(self): - csv_path = self._csv_path() - result = fetch_db_data(csv_path, "unknown_type", "test_block", "42") - self.assertFalse(result) - - def test_block_name_with_spaces_matches_underscore_block_param(self): - _create_settlement(plan_id="42", block_name="test block") - csv_path = self._csv_path() - - # block param uses underscore; DB has spaces — should still match - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - - def test_only_matching_plan_id_returned(self): - _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") - _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_id"], "S1") - - -class AddResourcesAPITest(APITestCase): - - def setUp(self): - self.client = APIClient() - self.url = reverse("add_resources") - - @patch("plans.api.build_layer", return_value=True) - @patch("plans.api.sync_form_type", return_value=True) - def test_returns_201_when_db_data_exists(self, mock_sync, mock_build): - _create_settlement(plan_id="42", block_name="test block") - - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "42", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "test block", - }) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - mock_sync.assert_called_once_with("settlement") - mock_build.assert_called_once() - - @patch("plans.api.sync_form_type", return_value=True) - def test_returns_404_when_no_db_data(self, mock_sync): - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "99", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "no block", - }) - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - @patch("plans.api.build_layer", return_value=True) - @patch("plans.api.sync_form_type", return_value=False) - def test_proceeds_with_db_data_even_when_sync_fails(self, mock_sync, mock_build): - _create_settlement(plan_id="42", block_name="test block") - - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "42", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "test block", - }) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - mock_build.assert_called_once() - - @patch("plans.api.build_layer", return_value=False) - @patch("plans.api.sync_form_type", return_value=True) - def test_returns_500_when_build_layer_fails(self, mock_sync, mock_build): - _create_settlement(plan_id="42", block_name="test block") - - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "42", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "test block", - }) - - self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) +# plans/tests.py +import csv +import os +import tempfile +from datetime import datetime, timezone +from unittest.mock import patch + +from django.test import TestCase +from django.urls import reverse +from rest_framework.test import APITestCase, APIClient +from rest_framework import status + +from dpr.models import ODK_settlement +from .models import Plan +from .utils import fetch_db_data +from projects.models import Project, AppType +from organization.models import Organization +from users.models import User, UserProjectGroup +from django.contrib.auth.models import Group, Permission + + +class PlanModelTest(TestCase): + def setUp(self): + # Create organization + self.organization = Organization.objects.create(name="Test Organization") + + # Create project with app_type + self.project = Project.objects.create( + name="Test Project", + organization=self.organization, + app_type=AppType.WATERSHED, + enabled=True, + ) + + # Create user + self.user = User.objects.create_user( + username="testuser", + email="test@example.com", + password="password123", + organization=self.organization, + ) + + def test_plan_creation(self): + plan = Plan.objects.create( + name="Test Watershed Plan", + project=self.project, + organization=self.organization, + state="Test State", + district="Test District", + block="Test Block", + village="Test Village", + gram_panchayat="Test GP", + created_by=self.user, + ) + + # Check model attributes + self.assertEqual(plan.name, "Test Watershed Plan") + self.assertEqual(plan.project, self.project) + self.assertEqual(plan.organization, self.organization) + self.assertEqual(plan.state, "Test State") + self.assertEqual(plan.district, "Test District") + self.assertEqual(plan.created_by, self.user) + + +class PlanAPITest(APITestCase): + def setUp(self): + self.client = APIClient() + + # Create organization + self.organization = Organization.objects.create(name="Test Organization") + + # Create project with app_type + self.project = Project.objects.create( + name="Test Project", + organization=self.organization, + app_type=AppType.WATERSHED, + enabled=True, + ) + + # Create admin user + self.admin_user = User.objects.create_user( + username="admin", + email="admin@example.com", + password="password123", + organization=self.organization, + is_superadmin=True, + ) + + # Create edit user + self.edit_user = User.objects.create_user( + username="editor", + email="editor@example.com", + password="password123", + organization=self.organization, + ) + + # Create view user + self.view_user = User.objects.create_user( + username="viewer", + email="viewer@example.com", + password="password123", + organization=self.organization, + ) + + # Create groups and permissions + self.admin_group = Group.objects.create(name="Project Admin") + self.editor_group = Group.objects.create(name="Project Editor") + self.viewer_group = Group.objects.create(name="Project Viewer") + + # Create permissions + Permission.objects.get_or_create( + codename="view_watershed", + name="Can view watershed planning data", + content_type_id=1, # This would typically be correct content type ID + ) + + Permission.objects.get_or_create( + codename="add_watershed", + name="Can add watershed planning data", + content_type_id=1, + ) + + Permission.objects.get_or_create( + codename="change_watershed", + name="Can change watershed planning data", + content_type_id=1, + ) + + Permission.objects.get_or_create( + codename="delete_watershed", + name="Can delete watershed planning data", + content_type_id=1, + ) + + # Assign permissions to groups + view_perm = Permission.objects.get(codename="view_watershed") + add_perm = Permission.objects.get(codename="add_watershed") + change_perm = Permission.objects.get(codename="change_watershed") + delete_perm = Permission.objects.get(codename="delete_watershed") + + self.admin_group.permissions.add(view_perm, add_perm, change_perm, delete_perm) + self.editor_group.permissions.add(view_perm, add_perm, change_perm) + self.viewer_group.permissions.add(view_perm) + + # Assign users to project roles + UserProjectGroup.objects.create( + user=self.edit_user, project=self.project, group=self.editor_group + ) + + UserProjectGroup.objects.create( + user=self.view_user, project=self.project, group=self.viewer_group + ) + + # Create a test plan + self.plan = Plan.objects.create( + name="Test Watershed Plan", + project=self.project, + organization=self.organization, + state="Test State", + district="Test District", + block="Test Block", + village="Test Village", + gram_panchayat="Test GP", + created_by=self.admin_user, + ) + + # URLs + self.plans_list_url = reverse( + "project-plan-list", kwargs={"project_pk": self.project.pk} + ) + self.plan_detail_url = reverse( + "project-plan-detail", + kwargs={"project_pk": self.project.pk, "pk": self.plan.pk}, + ) + + def test_list_plans_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + response = self.client.get(self.plans_list_url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_list_plans_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + response = self.client.get(self.plans_list_url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_list_plans_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + response = self.client.get(self.plans_list_url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_create_plan_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + data = { + "name": "New Watershed Plan", + "state": "New State", + "district": "New District", + "block": "New Block", + "village": "New Village", + "gram_panchayat": "New GP", + } + + response = self.client.post(self.plans_list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["name"], "New Watershed Plan") + self.assertEqual(response.data["state"], "New State") + + # Check plan was created in database + self.assertEqual(Plan.objects.count(), 2) + + def test_create_plan_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + data = { + "name": "Editor Plan", + "state": "Editor State", + "district": "Editor District", + "block": "Editor Block", + "village": "Editor Village", + "gram_panchayat": "Editor GP", + } + + response = self.client.post(self.plans_list_url, data) + + # Editor should be able to create plans + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["name"], "Editor Plan") + + def test_create_plan_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + data = { + "name": "Viewer Plan", + "state": "Viewer State", + "district": "Viewer District", + "block": "Viewer Block", + "village": "Viewer Village", + "gram_panchayat": "Viewer GP", + } + + response = self.client.post(self.plans_list_url, data) + + # Viewer should not be able to create plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_update_plan_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + data = { + "name": "Updated Plan", + "state": "Updated State", + "district": "Updated District", + "block": "Updated Block", + "village": "Updated Village", + "gram_panchayat": "Updated GP", + } + + response = self.client.put(self.plan_detail_url, data) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["name"], "Updated Plan") + + # Verify database was updated + updated_plan = Plan.objects.get(pk=self.plan.pk) + self.assertEqual(updated_plan.name, "Updated Plan") + + def test_update_plan_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + data = { + "name": "Editor Updated", + "state": self.plan.state, + "district": self.plan.district, + "block": self.plan.block, + "village": self.plan.village, + "gram_panchayat": self.plan.gram_panchayat, + } + + response = self.client.patch(self.plan_detail_url, {"name": "Editor Updated"}) + + # Editor should be able to update plans + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["name"], "Editor Updated") + + def test_update_plan_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + data = {"name": "Viewer Updated"} + + response = self.client.patch(self.plan_detail_url, data) + + # Viewer should not be able to update plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_plan_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + response = self.client.delete(self.plan_detail_url) + + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + # Verify plan was deleted + self.assertEqual(Plan.objects.count(), 0) + + def test_delete_plan_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + response = self.client.delete(self.plan_detail_url) + + # Editor should not be able to delete plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Verify plan was not deleted + self.assertEqual(Plan.objects.count(), 1) + + def test_delete_plan_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + response = self.client.delete(self.plan_detail_url) + + # Viewer should not be able to delete plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Verify plan was not deleted + self.assertEqual(Plan.objects.count(), 1) + + +# Minimal valid ODK settlement JSON (same structure as ODK submissions stored in data_settlement) +def _make_settlement_json(plan_id, block_name, settlement_id="SETT001", review_state="hasIssues"): + return { + "__id": f"uuid:{settlement_id}", + "__system": {"reviewState": review_state, "submissionDate": "2024-01-01T00:00:00Z"}, + "block_name": block_name, + "plan_id": str(plan_id), + "GPS_point": { + "point_mapsappearance": { + "coordinates": [78.5, 20.5] + } + }, + "Settlements_id": settlement_id, + "Settlements_name": "Test Settlement", + "MNREGA_INFORMATION": { + "NREGA_aware": 10, + "NREGA_applied": 5, + "NREGA_job_card": 3, + "total_household": 2, + "NREGA_work_days": 100, + "q1": "yes", + "select_one_Y_N": "yes", + "select_one_demands": "wages", + "select_multiple_issues": "delayed_payment", + "select_one_contributions": "labour", + }, + } + + +def _create_settlement(plan_id, block_name, settlement_id="SETT001", + is_deleted=False, is_moderated=False, review_state="hasIssues", + data_override=None): + data = data_override or _make_settlement_json(plan_id, block_name, settlement_id, review_state) + return ODK_settlement.objects.create( + settlement_id=settlement_id, + settlement_name="Test Settlement", + submission_time=datetime(2024, 1, 1, tzinfo=timezone.utc), + submitted_by="test_user", + status_re=review_state, + latitude=20.5, + longitude=78.5, + block_name=block_name, + number_of_households=10, + largest_caste="General", + smallest_caste="SC", + settlement_status="active", + plan_id=str(plan_id), + plan_name="Test Plan", + uuid=f"uuid:{settlement_id}", + farmer_family={}, + livestock_census={}, + nrega_job_aware=10, + nrega_job_applied=5, + nrega_past_work="yes", + nrega_raise_demand="yes", + nrega_demand="wages", + nrega_issues="delayed_payment", + nrega_community="labour", + data_settlement=data, + is_deleted=is_deleted, + is_moderated=is_moderated, + ) + + +class FetchDbDataTest(TestCase): + + def setUp(self): + self.tmp_dir = tempfile.mkdtemp() + + def _csv_path(self, name="test.csv"): + return os.path.join(self.tmp_dir, name) + + def test_returns_true_and_writes_csv_for_valid_settlement(self): + _create_settlement(plan_id="42", block_name="test block") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + self.assertTrue(os.path.exists(csv_path)) + with open(csv_path) as f: + reader = csv.DictReader(f) + rows = list(reader) + self.assertEqual(len(rows), 1) + self.assertIn("latitude", rows[0]) + self.assertIn("longitude", rows[0]) + self.assertEqual(rows[0]["sett_id"], "SETT001") + self.assertEqual(rows[0]["sett_name"], "Test Settlement") + + def test_moderated_record_uses_moderated_json(self): + moderated_data = _make_settlement_json("42", "test block") + moderated_data["Settlements_name"] = "Moderated Settlement Name" + _create_settlement( + plan_id="42", + block_name="test block", + settlement_id="SETT002", + is_moderated=True, + data_override=moderated_data, + ) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_name"], "Moderated Settlement Name") + + def test_deleted_records_excluded(self): + _create_settlement(plan_id="42", block_name="test block", is_deleted=True) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertFalse(result) + self.assertFalse(os.path.exists(csv_path)) + + def test_rejected_submissions_excluded_by_transform(self): + _create_settlement( + plan_id="42", block_name="test block", + settlement_id="SETT003", review_state="rejected" + ) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertFalse(result) + + def test_returns_false_for_no_matching_records(self): + csv_path = self._csv_path() + result = fetch_db_data(csv_path, "settlement", "nonexistent_block", "99") + self.assertFalse(result) + + def test_returns_false_for_unknown_resource_type(self): + csv_path = self._csv_path() + result = fetch_db_data(csv_path, "unknown_type", "test_block", "42") + self.assertFalse(result) + + def test_block_name_with_spaces_matches_underscore_block_param(self): + _create_settlement(plan_id="42", block_name="test block") + csv_path = self._csv_path() + + # block param uses underscore; DB has spaces — should still match + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + + def test_only_matching_plan_id_returned(self): + _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") + _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_id"], "S1") + + +class AddResourcesAPITest(APITestCase): + + def setUp(self): + self.client = APIClient() + self.url = reverse("add_resources") + + @patch("plans.api.build_layer", return_value=True) + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_201_when_db_data_exists(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + mock_sync.assert_called_once_with("settlement") + mock_build.assert_called_once() + + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_404_when_no_db_data(self, mock_sync): + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "99", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "no block", + }) + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + @patch("plans.api.build_layer", return_value=True) + @patch("plans.api.sync_form_type", return_value=False) + def test_proceeds_with_db_data_even_when_sync_fails(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + mock_build.assert_called_once() + + @patch("plans.api.build_layer", return_value=False) + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_500_when_build_layer_fails(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/plans/utils.py b/plans/utils.py index 7e96cfcf..f4e0151f 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -1,739 +1,739 @@ -import csv -import logging -import re -from datetime import datetime, timezone - -import dateutil.parser -import requests - -from dpr.models import ( - ODK_settlement, ODK_well, ODK_waterbody, - ODK_groundwater, ODK_agri, ODK_livelihood, ODK_crop, - SWB_maintenance, SWB_RS_maintenance, GW_maintenance, Agri_maintenance, - ODK_agrohorticulture, -) -from moderation.utils.utils import ( - MODEL_FIELD_EXTRACTORS as _MODERATION_EXTRACTORS, - extract_lat_lon_from_gps, -) -from utilities.constants import ODK_URL_SESSION - -logger = logging.getLogger(__name__) - -_token_cache = { - "token": None, - "expires_at": None, -} - -# MARK: Helper -def normalize_name(name): - """ - Normalize names for comparison by: - - Converting to lowercase - - Replacing spaces with underscores - - Removing extra whitespace - """ - if not name: - return "" - return name.lower().replace(" ", "_").strip() - -_RESOURCE_TYPES_FLAT_HEADER = frozenset({ - "settlement", "well", "waterbody", "cropping", -}) -_RESOURCE_TYPES_UNION_HEADER = frozenset({ - "plan_gw", "plan_agri", "main_swb", "main_gw", "main_swb_rs", "main_agri", - "livelihood", "agrohorticulture", -}) - - -def _write_csv(resource_type, modified_response_list, all_keys, csv_path): - if resource_type in _RESOURCE_TYPES_FLAT_HEADER: - header_keys = modified_response_list[0].keys() - with open(csv_path, "w", encoding="utf-8") as output_file: - dict_writer = csv.DictWriter( - output_file, fieldnames=header_keys, extrasaction="ignore" - ) - dict_writer.writeheader() - dict_writer.writerows(modified_response_list) - elif resource_type in _RESOURCE_TYPES_UNION_HEADER: - with open(csv_path, "w", newline="", encoding="utf-8") as csvfile: - dict_writer = csv.DictWriter(csvfile, fieldnames=list(all_keys)) - dict_writer.writeheader() - for item in modified_response_list: - dict_writer.writerow(flatten_dict(item)) - logger.info(f"CSV generated for '{resource_type}' at {csv_path}") - - -# MARK: Modify ODK Settlement Data -def modify_response_list_settlement(res, block, plan_id): - res_list = [] - logger.info( - "modify_response_list_settlement: block=%s plan_id=%s input_records=%d", - block, plan_id, len(res) if res else 0, - ) - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["sett_id"] = result["Settlements_id"] - result["sett_name"] = result["Settlements_name"] - try: - mgnrega_info = result.get("MNREGA_INFORMATION", {}) - except Exception: - logger.exception("modify_response_list_settlement: failed reading MNREGA_INFORMATION") - continue - if mgnrega_info: - result["job_aware"] = mgnrega_info.get("NREGA_aware", "") or 0 - result["job_applied"] = mgnrega_info.get("NREGA_applied", "") or 0 - result["job_card"] = mgnrega_info.get("NREGA_job_card", "") or 0 - result["without_jc"] = mgnrega_info.get("total_household", "") or 0 - result["work_days"] = mgnrega_info.get("NREGA_work_days", "") or 0 - result["past_work"] = mgnrega_info.get("q1", "") or "0" - result["raise_demand"] = mgnrega_info.get("select_one_Y_N", "") or "0" - result["demand"] = mgnrega_info.get("select_one_demands", "") or "0" - result["issues"] = mgnrega_info.get("select_multiple_issues", "") or "0" - result["community"] = ( - mgnrega_info.get("select_one_contributions", "") or "0" - ) - res_list.append(result) - return res_list - - -# MARK: Modify ODK Well Data -def modify_response_list_well(res, block, plan_id): - res_list = [] - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["well_id"] = result["well_id"] - - well_usage_section = result.get("Well_usage", {}) - try: - result["ben_settlement"] = result.get("beneficiary_settlement", "") or "NA" - result["owner"] = result.get("select_one_owns", "") or "NA" - result["hh_benefitted"] = result.get("households_benefited", "") or "NA" - result["caste"] = result.get("select_multiple_caste_use", "") or "NA" - result["functional"] = ( - well_usage_section.get("select_one_Functional_Non_functional", "") - or "NA" - ) - result["need_maintenance"] = ( - well_usage_section.get("select_one_maintenance", "") or "NA" - ) - repair_value = well_usage_section.get("select_one_repairs_well") - if repair_value: - repair_value = str(repair_value).lower() - if repair_value == "other": - result["repair"] = ( - well_usage_section.get("select_one_repairs_well_other", "") - or "NA" - ) - else: - result["repair"] = repair_value - else: - result["repair"] = "NA" - except Exception: - logger.exception("modify_response_list_well: failed enriching record") - continue - res_list.append(result) - - return res_list - - -# MARK: Modify ODK Waterbody Data -def modify_response_list_waterbody(res, block, plan_id): - res_list = [] - logger.info( - "modify_response_list_waterbody: block=%s plan_id=%s input_records=%d", - block, plan_id, len(res) if res else 0, - ) - for result in res: - if result is None: - continue - if result.get("__system", {}).get("reviewState") == "rejected": - continue - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["wb_id"] = result["waterbodies_id"] - - # type_of_water_st = result["select_one_water_structure"] - # if type_of_water_st: - # type_of_water_st = str(type_of_water_st).lower() - # if type_of_water_st == "other": - # result["wbs_type"] = result.get("select_one_water_structure_other", "") or "" - # else: - # result["wbs_type"] = result.get("select_one_water_structure", "") or "" - # else: - # result["wbs_type"] = "0" - - result["wbs_type"] = result.get("select_one_water_structure", "") or "0" - - try: - manager = result["select_one_manages"] - if manager: - manager = str(manager).lower() - if manager == "other": - result["who_manages"] = result.get("text_one_manages", "") or "" - else: - result["who_manages"] = result.get("select_one_manages", "") or "" - else: - result["who_manages"] = "0" - - who_owns = result["select_one_owns"] - if who_owns: - who_owns = str(who_owns).lower() - if who_owns == "other" or who_owns == "any other": - result["owner"] = result.get("text_one_owns", "") or "" - else: - result["owner"] = result.get("select_one_owns", "") or "" - else: - result["owner"] = "0" - result["caste"] = result.get("select_multiple_caste_use", "") or "0" - result["hh_benefitted"] = result.get("households_benefited", "") or 0 - result["identified"] = result.get("select_one_identified", "") or "0" - result["need_maintenance"] = result.get("select_one_maintenance") or "0" - - # Handle the dynamic water structure dimensions - # water_structure_type = result.get("select_one_water_structure", "").lower().replace("_", " ") - # water_structure_dimension = {} - # for key, value in result.items(): - # if isinstance(value, dict): - # structure_type = key.lower().replace("_", " ") - # if structure_type == water_structure_type: - # water_structure_dimension = { - # "length": next((v for k, v in value.items() if k.startswith("Length")), None), - # "breadth": next((v for k, v in value.items() if k.startswith("Breadth")), None), - # "width": next((v for k, v in value.items() if k.startswith("Width")), None), - # "depth": next((v for k, v in value.items() if k.startswith("Depth")), None), - # "height": next((v for k, v in value.items() if k.startswith("Height")), None), - # } - # break - - # Add the dimensions to the result dictionary - # result.update(water_structure_dimension) - except Exception: - logger.exception("modify_response_list_waterbody: failed enriching record") - continue - res_list.append(result) - return res_list - - -# MARK: Modify ODK Cropping Data -def modify_reponse_list_cropping(res, block, plan_id): - res_list = [] - logger.info( - "modify_reponse_list_cropping: block=%s plan_id=%s input_records=%d", - block, plan_id, len(res) if res else 0, - ) - - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - - if str(result.get("plan_id")) != str(plan_id): - continue - - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["crop_id"] = result["__id"] - - # Settlement and land basic information (5 keys) - result["sett_name"] = result.get("beneficiary_settlement", "") or "" - result["uncropp_br"] = result.get("Uncropped_barren_land", "") or "" - result["irrigatn"] = result.get("select_multiple_widgets", "") or "" - result["land_cls"] = result.get("select_one_classified", "") or "" - result["crop_seas"] = result.get("select_one_practice", "") or "" - - # Kharif season information (3 keys) - result["crop_khrf"] = result.get("select_multiple_cropping_kharif", "") or "" - result["crop_kh_o"] = result.get("select_multiple_cropping_kharif_other", "") or "" - result["area_khrf"] = result.get("total_area_cultivation_kharif", "") or "" - - # Rabi season information (3 keys) - result["crops_rabi"] = result.get("select_multiple_cropping_Rabi", "") or "" - result["crop_rb_o"] = result.get("select_multiple_cropping_Rabi_other", "") or "" - result["area_rabi"] = result.get("total_area_cultivation_Rabi", "") or "" - - # Zaid season information (3 keys) - result["crops_zaid"] = result.get("select_multiple_cropping_Zaid", "") or "" - result["crop_zd_o"] = result.get("select_multiple_cropping_Zaid_other", "") or "" - result["area_zaid"] = result.get("total_area_cultivation_Zaid", "") or "" - - # Soil and productivity information (4 keys) - result["productiv"] = result.get("select_one_productivity", "") or "" - result["soil_deg"] = result.get("soil_degraded", "") or "" - result["deg_reas"] = result.get("select_one_reason_degradation", "") or "" - result["deg_reas2"] = result.get("select_one_reason_degradation_1", "") or "" - - res_list.append(result) - - return res_list - - -# MARK: Modify ODK Plan Data -def modify_response_list_plan(res, block, plan_id): - res_list = [] - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["work_id"] = result["work_id"] - - work_type = None - selected_work = None - - if "TYPE_OF_WORK" in result: - work_type = result["TYPE_OF_WORK"] - elif "TYPE_OF_WORK_ID" in result: - work_type = result["TYPE_OF_WORK_ID"] - - if work_type: - result["work_type"] = work_type - - work_type_key = re.sub(r"[^a-zA-Z0-9]+", "_", work_type) - - if work_type_key in result: - selected_work = result[work_type_key] - if selected_work: - result["selected_work"] = selected_work - else: - result["selected_work"] = work_type_key - elif work_type in result: - selected_work = result[work_type] - if selected_work: - result["selected_work"] = selected_work - else: - result["selected_work"] = work_type - else: - result["selected_work"] = work_type - - result["ben_settlement"] = result["beneficiary_settlement"] - result["ben_name"] = result["Beneficiary_Name"] - result["ben_contact"] = result["Beneficiary_Contact_Number"] - res_list.append(result) - - return res_list - - -# MARK: Modify ODK Livelihood Data -def modify_response_list_livelihood(res, block, plan_id): - res_list = [] - for result in res: - # if result["__system"]["reviewState"] != "rejected": - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - res_list.append(result) - return res_list - - -# MARK: Modify ODK Maintenance / Agrohorticulture (Generic) -def modify_response_list_work(res, block, plan_id): - """ - Robust transform for maintenance and agrohorticulture submissions. - Unlike `modify_response_list_plan`, this avoids bracket-access on keys - that maintenance/agrohorticulture blobs may not carry (e.g. work_id, - Beneficiary_Name). Block filter is best-effort: applied only when the - blob actually has block_name (these models have no block_name DB column). - """ - res_list = [] - for result in res: - if result is None: - continue - if not isinstance(result, dict): - continue - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - blob_block = result.get("block_name") - if blob_block: - try: - if normalize_name(str(blob_block).lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - lat, lon = extract_lat_lon_from_gps(result.get("GPS_point")) - if lat is not None and lon is not None: - result["latitude"] = lat - result["longitude"] = lon - - sys_info = result.get("__system") or {} - if isinstance(sys_info, dict): - review_state = sys_info.get("reviewState") - if review_state: - result["status_re"] = review_state - - res_list.append(result) - return res_list - - -# Layer-build source-of-truth registry. Key = resource_type / work_type the -# `/add_resources` and `/add_works` endpoints accept. Tuple = (Model, JSON -# blob field, model has block_name column for DB-level pre-filtering). -# -# Resources (workspace=resources): settlement, well, waterbody, cropping -# Works (workspace=works): -# plan_gw — new recharge structures (groundwater) -# main_gw — maintenance of recharge structures -# plan_agri — new irrigation structures -# main_agri — maintenance of irrigation structures -# main_swb — surface water body maintenance (water-structure form) -# main_swb_rs — remote-sensed surface water body maintenance -# livelihood — livelihood -# agrohorticulture — agrohorticulture -_DB_CONFIG = { - "settlement": (ODK_settlement, "data_settlement", True), - "well": (ODK_well, "data_well", True), - "waterbody": (ODK_waterbody, "data_waterbody", True), - "cropping": (ODK_crop, "data_crop", False), - "plan_gw": (ODK_groundwater, "data_groundwater", True), - "plan_agri": (ODK_agri, "data_agri", True), - "livelihood": (ODK_livelihood, "data_livelihood", True), - "main_swb": (SWB_maintenance, "data_swb_maintenance", False), - "main_gw": (GW_maintenance, "data_gw_maintenance", False), - "main_swb_rs": (SWB_RS_maintenance, "data_swb_rs_maintenance", False), - "main_agri": (Agri_maintenance, "data_agri_maintenance", False), - # `data_agohorticulture` is the actual model field name — there is a - # spelling typo in the schema. Respecting it here to avoid a migration. - "agrohorticulture": (ODK_agrohorticulture, "data_agohorticulture", False), -} - -# Fields never useful to project alongside the JSON blob: the blob itself, -# soft-delete metadata, and relational fields (FKs serialise poorly via values()). -_PROJECTION_EXCLUDED_FIELDS = frozenset({ - "data_before_moderation", - "is_deleted", - "deleted_at", - "deleted_by", - "moderated_by", -}) - - -def _scalar_projection_fields(model, json_blob_field: str) -> list: - """ - Concrete, non-relational fields on `model` safe to project via `.values()` - alongside the raw ODK JSON blob. Captures everything moderation can edit - (settlement_name, block_name, nrega_*, lat/lon, status_re, ...) so the - generated layer reflects the latest moderated values, not just the - original ODK submission stored in `data_`. - """ - skip = {json_blob_field, *_PROJECTION_EXCLUDED_FIELDS} - fields = [] - for f in model._meta.get_fields(): - if not getattr(f, "concrete", False): - continue - if f.many_to_one or f.one_to_one or f.many_to_many or f.one_to_many: - continue - if f.name in skip: - continue - fields.append(f.name) - return fields - - -def _merge_moderated(blob: dict, friendly: dict, friendly_canonical: bool) -> dict: - """ - Merge friendly DB column values into the raw ODK blob so the layer carries - both the original ODK keys (Settlements_name, GPS_point, ...) and the - moderated friendly columns (settlement_name, block_name, ...). - - `friendly_canonical=True` (model has a moderation extractor): friendly - columns are kept in sync with every moderation edit, so they win on - collision. `False` (no extractor, e.g. SWB_maintenance): moderation only - touches the blob, so the blob wins on collision. - - GeoPackage/SQLite (the downstream layer format) is case-insensitive on - column names, so a blob key like "GPS_point" and a friendly column - "gps_point" would collide on write. We dedupe case-insensitively in - favour of the canonical side; non-colliding keys (e.g. "Settlements_name" - vs "settlement_name") are both preserved. - """ - if friendly_canonical: - winner, loser = friendly, blob - else: - winner, loser = blob, friendly - - winner_lower = {k.lower() for k in winner} - loser_filtered = { - k: v for k, v in loser.items() - if k.lower() not in winner_lower - } - return {**loser_filtered, **winner} - - -# MARK: Fetch DB Data -def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: - """ - Build the CSV of records for the given (resource_type, plan_id, block) - by reading from our DB (post-moderation source of truth). - - Returns the number of rows actually written to the CSV; 0 means no - usable data was found and the caller should treat it as a soft 404. - """ - logger.info( - f"fetch_db_data: starting — resource_type={resource_type}, " - f"plan_id={plan_id}, block={block}, csv_path={csv_path}" - ) - - entry = _DB_CONFIG.get(resource_type) - if not entry: - logger.warning(f"fetch_db_data: unknown resource_type '{resource_type}'") - return 0 - - model, data_field, has_block_col = entry - projection_fields = _scalar_projection_fields(model, data_field) - friendly_canonical = model in _MODERATION_EXTRACTORS - logger.info( - f"fetch_db_data: querying {model.__name__}.{data_field} " - f"with plan_id={plan_id}, is_deleted=False" - + ( - f", block_name icontains '{block}'" - if has_block_col - else " (no block_name column, skipping DB block filter)" - ) - ) - logger.info( - f"fetch_db_data: projecting blob '{data_field}' + " - f"{len(projection_fields)} moderated column(s) " - f"(friendly_canonical={friendly_canonical}): {projection_fields}" - ) - - qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) - if has_block_col: - qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) - - raw_rows = list(qs.values(data_field, *projection_fields)) - logger.info( - f"fetch_db_data: DB returned {len(raw_rows)} record(s) for " - f"resource_type={resource_type}, plan_id={plan_id}" - ) - - response_list = [] - empty_blob_count = 0 - for row in raw_rows: - blob = row.get(data_field) or {} - if not blob: - empty_blob_count += 1 - continue - friendly = {k: v for k, v in row.items() if k != data_field} - response_list.append(_merge_moderated(blob, friendly, friendly_canonical)) - - if empty_blob_count: - logger.warning( - f"fetch_db_data: skipped {empty_blob_count} record(s) with empty " - f"{data_field}" - ) - - if not response_list: - logger.warning( - f"fetch_db_data: no usable records for resource_type={resource_type}, " - f"plan_id={plan_id}, block={block}" - ) - return 0 - - logger.info( - f"fetch_db_data: running transform for resource_type={resource_type} " - f"on {len(response_list)} record(s) (each enriched with " - f"{len(projection_fields)} moderated column(s))" - ) - - all_keys = set() - if resource_type == "settlement": - rows = modify_response_list_settlement(response_list, block, plan_id) - elif resource_type == "well": - rows = modify_response_list_well(response_list, block, plan_id) - elif resource_type == "waterbody": - rows = modify_response_list_waterbody(response_list, block, plan_id) - elif resource_type == "cropping": - rows = modify_reponse_list_cropping(response_list, block, plan_id) - elif resource_type in ["plan_gw", "main_swb", "plan_agri"]: - rows = modify_response_list_plan(response_list, block, plan_id) - for item in rows: - all_keys.update(extract_keys(item)) - elif resource_type == "livelihood": - rows = modify_response_list_livelihood(response_list, block, plan_id) - for item in rows: - all_keys.update(extract_keys(item)) - elif resource_type in [ - "main_gw", "main_swb_rs", "main_agri", "agrohorticulture", - ]: - rows = modify_response_list_work(response_list, block, plan_id) - for item in rows: - all_keys.update(extract_keys(item)) - else: - logger.warning( - f"fetch_db_data: no transform defined for resource_type='{resource_type}'" - ) - return 0 - - logger.info( - f"fetch_db_data: transform produced {len(rows)} row(s) " - f"(filtered from {len(response_list)}) for resource_type={resource_type}" - ) - - if not rows: - logger.warning( - f"fetch_db_data: transform returned empty list for " - f"resource_type={resource_type}, plan_id={plan_id}, block={block}" - ) - return 0 - - logger.info(f"fetch_db_data: writing CSV to {csv_path}") - _write_csv(resource_type, rows, all_keys, csv_path) - logger.info( - f"fetch_db_data: done — {len(rows)} row(s) written to {csv_path} " - f"(columns include {len(projection_fields)} moderated friendly field(s))" - ) - return len(rows) - - -def flatten_dict(d, parent_key="", sep="_"): - items = [] - for k, v in d.items(): - new_key = f"{parent_key}{sep}{k}" if parent_key else k - if isinstance(v, dict): - items.extend(flatten_dict(v, new_key, sep=sep).items()) - else: - items.append((new_key, v)) - return dict(items) - - -def extract_keys(d, parent_key="", sep="_"): - keys = [] - for k, v in d.items(): - new_key = f"{parent_key}{sep}{k}" if parent_key else k - keys.append(new_key) - if isinstance(v, dict): - keys.extend(extract_keys(v, new_key, sep=sep)) - return keys - - -# MARK: Bearer Token -def fetch_bearer_token(email: str, password: str) -> str: - try: - if _token_cache["token"] and _token_cache["expires_at"]: - now = datetime.now(timezone.utc) - if now < _token_cache["expires_at"]: - return _token_cache["token"] - - response = requests.post( - ODK_URL_SESSION, json={"email": email, "password": password} - ) - print("Response: ", response) - if response.status_code == 200: - response_data = response.json() - _token_cache["token"] = response_data.get("token") - _token_cache["expires_at"] = dateutil.parser.parse( - response_data.get("expiresAt") - ) - return _token_cache["token"] - else: - raise Exception( - f"Failed to fetch bearer token. Status code: {response.status_code}" - ) - except Exception as e: - print(f"An error occurred while fetching the bearer token: {str(e)}") - raise +import csv +import logging +import re +from datetime import datetime, timezone + +import dateutil.parser +import requests + +from dpr.models import ( + ODK_settlement, ODK_well, ODK_waterbody, + ODK_groundwater, ODK_agri, ODK_livelihood, ODK_crop, + SWB_maintenance, SWB_RS_maintenance, GW_maintenance, Agri_maintenance, + ODK_agrohorticulture, +) +from moderation.utils.utils import ( + MODEL_FIELD_EXTRACTORS as _MODERATION_EXTRACTORS, + extract_lat_lon_from_gps, +) +from utilities.constants import ODK_URL_SESSION + +logger = logging.getLogger(__name__) + +_token_cache = { + "token": None, + "expires_at": None, +} + +# MARK: Helper +def normalize_name(name): + """ + Normalize names for comparison by: + - Converting to lowercase + - Replacing spaces with underscores + - Removing extra whitespace + """ + if not name: + return "" + return name.lower().replace(" ", "_").strip() + +_RESOURCE_TYPES_FLAT_HEADER = frozenset({ + "settlement", "well", "waterbody", "cropping", +}) +_RESOURCE_TYPES_UNION_HEADER = frozenset({ + "plan_gw", "plan_agri", "main_swb", "main_gw", "main_swb_rs", "main_agri", + "livelihood", "agrohorticulture", +}) + + +def _write_csv(resource_type, modified_response_list, all_keys, csv_path): + if resource_type in _RESOURCE_TYPES_FLAT_HEADER: + header_keys = modified_response_list[0].keys() + with open(csv_path, "w", encoding="utf-8") as output_file: + dict_writer = csv.DictWriter( + output_file, fieldnames=header_keys, extrasaction="ignore" + ) + dict_writer.writeheader() + dict_writer.writerows(modified_response_list) + elif resource_type in _RESOURCE_TYPES_UNION_HEADER: + with open(csv_path, "w", newline="", encoding="utf-8") as csvfile: + dict_writer = csv.DictWriter(csvfile, fieldnames=list(all_keys)) + dict_writer.writeheader() + for item in modified_response_list: + dict_writer.writerow(flatten_dict(item)) + logger.info(f"CSV generated for '{resource_type}' at {csv_path}") + + +# MARK: Modify ODK Settlement Data +def modify_response_list_settlement(res, block, plan_id): + res_list = [] + logger.info( + "modify_response_list_settlement: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["sett_id"] = result["Settlements_id"] + result["sett_name"] = result["Settlements_name"] + try: + mgnrega_info = result.get("MNREGA_INFORMATION", {}) + except Exception: + logger.exception("modify_response_list_settlement: failed reading MNREGA_INFORMATION") + continue + if mgnrega_info: + result["job_aware"] = mgnrega_info.get("NREGA_aware", "") or 0 + result["job_applied"] = mgnrega_info.get("NREGA_applied", "") or 0 + result["job_card"] = mgnrega_info.get("NREGA_job_card", "") or 0 + result["without_jc"] = mgnrega_info.get("total_household", "") or 0 + result["work_days"] = mgnrega_info.get("NREGA_work_days", "") or 0 + result["past_work"] = mgnrega_info.get("q1", "") or "0" + result["raise_demand"] = mgnrega_info.get("select_one_Y_N", "") or "0" + result["demand"] = mgnrega_info.get("select_one_demands", "") or "0" + result["issues"] = mgnrega_info.get("select_multiple_issues", "") or "0" + result["community"] = ( + mgnrega_info.get("select_one_contributions", "") or "0" + ) + res_list.append(result) + return res_list + + +# MARK: Modify ODK Well Data +def modify_response_list_well(res, block, plan_id): + res_list = [] + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["well_id"] = result["well_id"] + + well_usage_section = result.get("Well_usage", {}) + try: + result["ben_settlement"] = result.get("beneficiary_settlement", "") or "NA" + result["owner"] = result.get("select_one_owns", "") or "NA" + result["hh_benefitted"] = result.get("households_benefited", "") or "NA" + result["caste"] = result.get("select_multiple_caste_use", "") or "NA" + result["functional"] = ( + well_usage_section.get("select_one_Functional_Non_functional", "") + or "NA" + ) + result["need_maintenance"] = ( + well_usage_section.get("select_one_maintenance", "") or "NA" + ) + repair_value = well_usage_section.get("select_one_repairs_well") + if repair_value: + repair_value = str(repair_value).lower() + if repair_value == "other": + result["repair"] = ( + well_usage_section.get("select_one_repairs_well_other", "") + or "NA" + ) + else: + result["repair"] = repair_value + else: + result["repair"] = "NA" + except Exception: + logger.exception("modify_response_list_well: failed enriching record") + continue + res_list.append(result) + + return res_list + + +# MARK: Modify ODK Waterbody Data +def modify_response_list_waterbody(res, block, plan_id): + res_list = [] + logger.info( + "modify_response_list_waterbody: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) + for result in res: + if result is None: + continue + if result.get("__system", {}).get("reviewState") == "rejected": + continue + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["wb_id"] = result["waterbodies_id"] + + # type_of_water_st = result["select_one_water_structure"] + # if type_of_water_st: + # type_of_water_st = str(type_of_water_st).lower() + # if type_of_water_st == "other": + # result["wbs_type"] = result.get("select_one_water_structure_other", "") or "" + # else: + # result["wbs_type"] = result.get("select_one_water_structure", "") or "" + # else: + # result["wbs_type"] = "0" + + result["wbs_type"] = result.get("select_one_water_structure", "") or "0" + + try: + manager = result["select_one_manages"] + if manager: + manager = str(manager).lower() + if manager == "other": + result["who_manages"] = result.get("text_one_manages", "") or "" + else: + result["who_manages"] = result.get("select_one_manages", "") or "" + else: + result["who_manages"] = "0" + + who_owns = result["select_one_owns"] + if who_owns: + who_owns = str(who_owns).lower() + if who_owns == "other" or who_owns == "any other": + result["owner"] = result.get("text_one_owns", "") or "" + else: + result["owner"] = result.get("select_one_owns", "") or "" + else: + result["owner"] = "0" + result["caste"] = result.get("select_multiple_caste_use", "") or "0" + result["hh_benefitted"] = result.get("households_benefited", "") or 0 + result["identified"] = result.get("select_one_identified", "") or "0" + result["need_maintenance"] = result.get("select_one_maintenance") or "0" + + # Handle the dynamic water structure dimensions + # water_structure_type = result.get("select_one_water_structure", "").lower().replace("_", " ") + # water_structure_dimension = {} + # for key, value in result.items(): + # if isinstance(value, dict): + # structure_type = key.lower().replace("_", " ") + # if structure_type == water_structure_type: + # water_structure_dimension = { + # "length": next((v for k, v in value.items() if k.startswith("Length")), None), + # "breadth": next((v for k, v in value.items() if k.startswith("Breadth")), None), + # "width": next((v for k, v in value.items() if k.startswith("Width")), None), + # "depth": next((v for k, v in value.items() if k.startswith("Depth")), None), + # "height": next((v for k, v in value.items() if k.startswith("Height")), None), + # } + # break + + # Add the dimensions to the result dictionary + # result.update(water_structure_dimension) + except Exception: + logger.exception("modify_response_list_waterbody: failed enriching record") + continue + res_list.append(result) + return res_list + + +# MARK: Modify ODK Cropping Data +def modify_reponse_list_cropping(res, block, plan_id): + res_list = [] + logger.info( + "modify_reponse_list_cropping: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) + + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + + if str(result.get("plan_id")) != str(plan_id): + continue + + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["crop_id"] = result["__id"] + + # Settlement and land basic information (5 keys) + result["sett_name"] = result.get("beneficiary_settlement", "") or "" + result["uncropp_br"] = result.get("Uncropped_barren_land", "") or "" + result["irrigatn"] = result.get("select_multiple_widgets", "") or "" + result["land_cls"] = result.get("select_one_classified", "") or "" + result["crop_seas"] = result.get("select_one_practice", "") or "" + + # Kharif season information (3 keys) + result["crop_khrf"] = result.get("select_multiple_cropping_kharif", "") or "" + result["crop_kh_o"] = result.get("select_multiple_cropping_kharif_other", "") or "" + result["area_khrf"] = result.get("total_area_cultivation_kharif", "") or "" + + # Rabi season information (3 keys) + result["crops_rabi"] = result.get("select_multiple_cropping_Rabi", "") or "" + result["crop_rb_o"] = result.get("select_multiple_cropping_Rabi_other", "") or "" + result["area_rabi"] = result.get("total_area_cultivation_Rabi", "") or "" + + # Zaid season information (3 keys) + result["crops_zaid"] = result.get("select_multiple_cropping_Zaid", "") or "" + result["crop_zd_o"] = result.get("select_multiple_cropping_Zaid_other", "") or "" + result["area_zaid"] = result.get("total_area_cultivation_Zaid", "") or "" + + # Soil and productivity information (4 keys) + result["productiv"] = result.get("select_one_productivity", "") or "" + result["soil_deg"] = result.get("soil_degraded", "") or "" + result["deg_reas"] = result.get("select_one_reason_degradation", "") or "" + result["deg_reas2"] = result.get("select_one_reason_degradation_1", "") or "" + + res_list.append(result) + + return res_list + + +# MARK: Modify ODK Plan Data +def modify_response_list_plan(res, block, plan_id): + res_list = [] + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["work_id"] = result["work_id"] + + work_type = None + selected_work = None + + if "TYPE_OF_WORK" in result: + work_type = result["TYPE_OF_WORK"] + elif "TYPE_OF_WORK_ID" in result: + work_type = result["TYPE_OF_WORK_ID"] + + if work_type: + result["work_type"] = work_type + + work_type_key = re.sub(r"[^a-zA-Z0-9]+", "_", work_type) + + if work_type_key in result: + selected_work = result[work_type_key] + if selected_work: + result["selected_work"] = selected_work + else: + result["selected_work"] = work_type_key + elif work_type in result: + selected_work = result[work_type] + if selected_work: + result["selected_work"] = selected_work + else: + result["selected_work"] = work_type + else: + result["selected_work"] = work_type + + result["ben_settlement"] = result["beneficiary_settlement"] + result["ben_name"] = result["Beneficiary_Name"] + result["ben_contact"] = result["Beneficiary_Contact_Number"] + res_list.append(result) + + return res_list + + +# MARK: Modify ODK Livelihood Data +def modify_response_list_livelihood(res, block, plan_id): + res_list = [] + for result in res: + # if result["__system"]["reviewState"] != "rejected": + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + res_list.append(result) + return res_list + + +# MARK: Modify ODK Maintenance / Agrohorticulture (Generic) +def modify_response_list_work(res, block, plan_id): + """ + Robust transform for maintenance and agrohorticulture submissions. + Unlike `modify_response_list_plan`, this avoids bracket-access on keys + that maintenance/agrohorticulture blobs may not carry (e.g. work_id, + Beneficiary_Name). Block filter is best-effort: applied only when the + blob actually has block_name (these models have no block_name DB column). + """ + res_list = [] + for result in res: + if result is None: + continue + if not isinstance(result, dict): + continue + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + blob_block = result.get("block_name") + if blob_block: + try: + if normalize_name(str(blob_block).lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + lat, lon = extract_lat_lon_from_gps(result.get("GPS_point")) + if lat is not None and lon is not None: + result["latitude"] = lat + result["longitude"] = lon + + sys_info = result.get("__system") or {} + if isinstance(sys_info, dict): + review_state = sys_info.get("reviewState") + if review_state: + result["status_re"] = review_state + + res_list.append(result) + return res_list + + +# Layer-build source-of-truth registry. Key = resource_type / work_type the +# `/add_resources` and `/add_works` endpoints accept. Tuple = (Model, JSON +# blob field, model has block_name column for DB-level pre-filtering). +# +# Resources (workspace=resources): settlement, well, waterbody, cropping +# Works (workspace=works): +# plan_gw — new recharge structures (groundwater) +# main_gw — maintenance of recharge structures +# plan_agri — new irrigation structures +# main_agri — maintenance of irrigation structures +# main_swb — surface water body maintenance (water-structure form) +# main_swb_rs — remote-sensed surface water body maintenance +# livelihood — livelihood +# agrohorticulture — agrohorticulture +_DB_CONFIG = { + "settlement": (ODK_settlement, "data_settlement", True), + "well": (ODK_well, "data_well", True), + "waterbody": (ODK_waterbody, "data_waterbody", True), + "cropping": (ODK_crop, "data_crop", False), + "plan_gw": (ODK_groundwater, "data_groundwater", True), + "plan_agri": (ODK_agri, "data_agri", True), + "livelihood": (ODK_livelihood, "data_livelihood", True), + "main_swb": (SWB_maintenance, "data_swb_maintenance", False), + "main_gw": (GW_maintenance, "data_gw_maintenance", False), + "main_swb_rs": (SWB_RS_maintenance, "data_swb_rs_maintenance", False), + "main_agri": (Agri_maintenance, "data_agri_maintenance", False), + # `data_agohorticulture` is the actual model field name — there is a + # spelling typo in the schema. Respecting it here to avoid a migration. + "agrohorticulture": (ODK_agrohorticulture, "data_agohorticulture", False), +} + +# Fields never useful to project alongside the JSON blob: the blob itself, +# soft-delete metadata, and relational fields (FKs serialise poorly via values()). +_PROJECTION_EXCLUDED_FIELDS = frozenset({ + "data_before_moderation", + "is_deleted", + "deleted_at", + "deleted_by", + "moderated_by", +}) + + +def _scalar_projection_fields(model, json_blob_field: str) -> list: + """ + Concrete, non-relational fields on `model` safe to project via `.values()` + alongside the raw ODK JSON blob. Captures everything moderation can edit + (settlement_name, block_name, nrega_*, lat/lon, status_re, ...) so the + generated layer reflects the latest moderated values, not just the + original ODK submission stored in `data_`. + """ + skip = {json_blob_field, *_PROJECTION_EXCLUDED_FIELDS} + fields = [] + for f in model._meta.get_fields(): + if not getattr(f, "concrete", False): + continue + if f.many_to_one or f.one_to_one or f.many_to_many or f.one_to_many: + continue + if f.name in skip: + continue + fields.append(f.name) + return fields + + +def _merge_moderated(blob: dict, friendly: dict, friendly_canonical: bool) -> dict: + """ + Merge friendly DB column values into the raw ODK blob so the layer carries + both the original ODK keys (Settlements_name, GPS_point, ...) and the + moderated friendly columns (settlement_name, block_name, ...). + + `friendly_canonical=True` (model has a moderation extractor): friendly + columns are kept in sync with every moderation edit, so they win on + collision. `False` (no extractor, e.g. SWB_maintenance): moderation only + touches the blob, so the blob wins on collision. + + GeoPackage/SQLite (the downstream layer format) is case-insensitive on + column names, so a blob key like "GPS_point" and a friendly column + "gps_point" would collide on write. We dedupe case-insensitively in + favour of the canonical side; non-colliding keys (e.g. "Settlements_name" + vs "settlement_name") are both preserved. + """ + if friendly_canonical: + winner, loser = friendly, blob + else: + winner, loser = blob, friendly + + winner_lower = {k.lower() for k in winner} + loser_filtered = { + k: v for k, v in loser.items() + if k.lower() not in winner_lower + } + return {**loser_filtered, **winner} + + +# MARK: Fetch DB Data +def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: + """ + Build the CSV of records for the given (resource_type, plan_id, block) + by reading from our DB (post-moderation source of truth). + + Returns the number of rows actually written to the CSV; 0 means no + usable data was found and the caller should treat it as a soft 404. + """ + logger.info( + f"fetch_db_data: starting — resource_type={resource_type}, " + f"plan_id={plan_id}, block={block}, csv_path={csv_path}" + ) + + entry = _DB_CONFIG.get(resource_type) + if not entry: + logger.warning(f"fetch_db_data: unknown resource_type '{resource_type}'") + return 0 + + model, data_field, has_block_col = entry + projection_fields = _scalar_projection_fields(model, data_field) + friendly_canonical = model in _MODERATION_EXTRACTORS + logger.info( + f"fetch_db_data: querying {model.__name__}.{data_field} " + f"with plan_id={plan_id}, is_deleted=False" + + ( + f", block_name icontains '{block}'" + if has_block_col + else " (no block_name column, skipping DB block filter)" + ) + ) + logger.info( + f"fetch_db_data: projecting blob '{data_field}' + " + f"{len(projection_fields)} moderated column(s) " + f"(friendly_canonical={friendly_canonical}): {projection_fields}" + ) + + qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) + if has_block_col: + qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) + + raw_rows = list(qs.values(data_field, *projection_fields)) + logger.info( + f"fetch_db_data: DB returned {len(raw_rows)} record(s) for " + f"resource_type={resource_type}, plan_id={plan_id}" + ) + + response_list = [] + empty_blob_count = 0 + for row in raw_rows: + blob = row.get(data_field) or {} + if not blob: + empty_blob_count += 1 + continue + friendly = {k: v for k, v in row.items() if k != data_field} + response_list.append(_merge_moderated(blob, friendly, friendly_canonical)) + + if empty_blob_count: + logger.warning( + f"fetch_db_data: skipped {empty_blob_count} record(s) with empty " + f"{data_field}" + ) + + if not response_list: + logger.warning( + f"fetch_db_data: no usable records for resource_type={resource_type}, " + f"plan_id={plan_id}, block={block}" + ) + return 0 + + logger.info( + f"fetch_db_data: running transform for resource_type={resource_type} " + f"on {len(response_list)} record(s) (each enriched with " + f"{len(projection_fields)} moderated column(s))" + ) + + all_keys = set() + if resource_type == "settlement": + rows = modify_response_list_settlement(response_list, block, plan_id) + elif resource_type == "well": + rows = modify_response_list_well(response_list, block, plan_id) + elif resource_type == "waterbody": + rows = modify_response_list_waterbody(response_list, block, plan_id) + elif resource_type == "cropping": + rows = modify_reponse_list_cropping(response_list, block, plan_id) + elif resource_type in ["plan_gw", "main_swb", "plan_agri"]: + rows = modify_response_list_plan(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + elif resource_type == "livelihood": + rows = modify_response_list_livelihood(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + elif resource_type in [ + "main_gw", "main_swb_rs", "main_agri", "agrohorticulture", + ]: + rows = modify_response_list_work(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + else: + logger.warning( + f"fetch_db_data: no transform defined for resource_type='{resource_type}'" + ) + return 0 + + logger.info( + f"fetch_db_data: transform produced {len(rows)} row(s) " + f"(filtered from {len(response_list)}) for resource_type={resource_type}" + ) + + if not rows: + logger.warning( + f"fetch_db_data: transform returned empty list for " + f"resource_type={resource_type}, plan_id={plan_id}, block={block}" + ) + return 0 + + logger.info(f"fetch_db_data: writing CSV to {csv_path}") + _write_csv(resource_type, rows, all_keys, csv_path) + logger.info( + f"fetch_db_data: done — {len(rows)} row(s) written to {csv_path} " + f"(columns include {len(projection_fields)} moderated friendly field(s))" + ) + return len(rows) + + +def flatten_dict(d, parent_key="", sep="_"): + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + + +def extract_keys(d, parent_key="", sep="_"): + keys = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + keys.append(new_key) + if isinstance(v, dict): + keys.extend(extract_keys(v, new_key, sep=sep)) + return keys + + +# MARK: Bearer Token +def fetch_bearer_token(email: str, password: str) -> str: + try: + if _token_cache["token"] and _token_cache["expires_at"]: + now = datetime.now(timezone.utc) + if now < _token_cache["expires_at"]: + return _token_cache["token"] + + response = requests.post( + ODK_URL_SESSION, json={"email": email, "password": password} + ) + print("Response: ", response) + if response.status_code == 200: + response_data = response.json() + _token_cache["token"] = response_data.get("token") + _token_cache["expires_at"] = dateutil.parser.parse( + response_data.get("expiresAt") + ) + return _token_cache["token"] + else: + raise Exception( + f"Failed to fetch bearer token. Status code: {response.status_code}" + ) + except Exception as e: + print(f"An error occurred while fetching the bearer token: {str(e)}") + raise diff --git a/stats_generator/mws_indicators.py b/stats_generator/mws_indicators.py index 49cedf07..e67d272c 100644 --- a/stats_generator/mws_indicators.py +++ b/stats_generator/mws_indicators.py @@ -1,1130 +1,1130 @@ -import os -import requests, json -import pandas as pd -import pymannkendall as mk -import numpy as np -import ast -from nrm_app.settings import EXCEL_PATH -from .utils import get_url -from rest_framework.response import Response -from rest_framework import status -from django.http import HttpResponse -from .models import LayerInfo - - -def create_geojson_for_all_mws(existing_geojson_path, df, new_geojson_path): - with open(existing_geojson_path) as f: - existing_data = json.load(f) - - features = [] - - for _, row in df.iterrows(): - uid = row["mws_id"] - geometry = None - - for feature in existing_data["features"]: - if feature["properties"].get("uid") == uid: - geometry = feature["geometry"] - break - - if geometry is None: - print( - f"No geometry found for uid: {uid}. Using default geometry (e.g., None)." - ) - geometry = {"type": "Point", "coordinates": [0, 0]} - - properties = row.to_dict() - - new_feature = { - "type": "Feature", - "geometry": geometry, - "properties": properties, - } - features.append(new_feature) - - new_feature_collection = {"type": "FeatureCollection", "features": features} - - with open(new_geojson_path, "w") as f: - json.dump(new_feature_collection, f) - - -def generate_mws_data_for_kyl_filters( - state, district, block, file_type, regenerate=None -): - state_folder = state.replace(" ", "_").upper() - district_folder = district.replace(" ", "_").upper() - file_xl_path = os.path.join( - EXCEL_PATH, - "data/stats_excel_files", - state_folder, - district_folder, - f"{district}_{block}", - ) - if regenerate: - file_path = None - else: - file_path = get_mws_KYL_filter_data(state, district, block, file_type) - if not file_path: - try: - sheets = { - "hydrological_annual": -1, - "terrain": -1, - "croppingIntensity_annual": -1, - "surfaceWaterBodies_annual": -1, - "croppingDrought_kharif": -1, - "nrega_annual": -1, - "mws_intersect_villages": -1, - "change_detection_degradation": -1, - "change_detection_afforestation": -1, - "change_detection_deforestation": -1, - "change_detection_urbanization": -1, - "change_detection_cropintensity": -1, - "terrain_lulc_slope": -1, - "terrain_lulc_plain": -1, - "restoration_vector": -1, - "aquifer_vector": -1, - "soge_vector": -1, - "lcw_conflict": -1, - "factory_csr": -1, - "mining": -1, - "green_credit": -1, - "mws_intersect_swb": -1, - "dem": -1, - "canal": -1, - "river": -1, - "lulc_vector": -1, - "drainage_density": -1, - } - - try: - with pd.ExcelFile(file_xl_path + ".xlsx") as xl: - available_sheets = xl.sheet_names # Get list of available sheets - - # Try to parse each sheet if it exists - for sheet_name in sheets.keys(): - if sheet_name in available_sheets: - try: - sheets[sheet_name] = xl.parse(sheet_name) - except Exception as e: - print(f"Error parsing sheet {sheet_name}: {e}") - sheets[sheet_name] = -1 - else: - print(f"Sheet {sheet_name} not found in Excel file") - sheets[sheet_name] = -1 - - except Exception as e: - print(f"Error reading Excel file: {e}") - # Return all sheets as -1 if the file can't be read - return {k: -1 for k in sheets.keys()} - - results = [] - df_hydrological_annual = sheets["hydrological_annual"] - - for specific_mws_id in df_hydrological_annual["UID"].unique(): - hydro_annual_mws_data = df_hydrological_annual[ - df_hydrological_annual["UID"] == specific_mws_id - ] - precipitation_columns = hydro_annual_mws_data.filter( - like="Precipitation" - ) # Avg_precipitation - total_percipitation_column = precipitation_columns.shape[1] - sum_precipitation = precipitation_columns.sum(axis=1).sum() - avg_percipitation = round( - sum_precipitation / total_percipitation_column, 4 - ) - - try: - terrain_vector_mws_data = sheets["terrain"][ - sheets["terrain"]["UID"] == specific_mws_id - ] - terrainCluster_ID = terrain_vector_mws_data.get( - "terrain_cluster_id", None - ).iloc[ - 0 - ] # terrain - except: - terrainCluster_ID = -9999 - - try: - df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ - sheets["croppingIntensity_annual"]["UID"] == specific_mws_id - ] - df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) - - crp_Intensity_columns = df_crp_intensity_mws_data.filter( - like="cropping_intensity_unit_less" - ) # cropping_intensity_avg - total_crp_Intensity_column = crp_Intensity_columns.shape[1] - sum_crp_Intensity = crp_Intensity_columns.sum(axis=1).sum() - cropping_intensity_avg = round( - ( - sum_crp_Intensity / total_crp_Intensity_column - if total_crp_Intensity_column > 0 - else 0 - ), - 4, - ) - - ######### Cropping Intensity Trend ################# - crp_intensity_T = df_crp_intensity_mws_data.filter( - like="cropping_intensity_unit_less" - ).dropna() # Drop rows with NaN for trend calculation - crp_intensity_T = crp_intensity_T.squeeze().tolist()[:-3] - result = mk.original_test(crp_intensity_T) - - def sens_slope(data): - slopes = [] - for i in range(len(data) - 1): - for j in range(i + 1, len(data)): - s = (data[j] - data[i]) / (j - i) - slopes.append(s) - return np.median(slopes) - - cropping_intensity_trend_value = sens_slope(crp_intensity_T) - cropping_intensity_trend = None - if result.trend == "no trend": - cropping_intensity_trend = "0" - elif result.trend == "increasing": - cropping_intensity_trend = "1" - else: - cropping_intensity_trend = "-1" - - # Total cropped area, replace NaN with 0 for these columns as well - total_cropped_area = df_crp_intensity_mws_data.iloc[0][ - "sum_area_in_ha" - ] - - # Handle single-cropped area calculation - single_crop_columns = df_crp_intensity_mws_data.filter( - like="single_cropped_area" - ) # avg_single_cropped - total_single_crop_column = single_crop_columns.shape[1] - sum_single_crop = single_crop_columns.sum(axis=1).sum() - percent_single_crop = ( - sum_single_crop * 100 / total_cropped_area - if total_cropped_area > 0 - else 0 - ) - avg_single_cropped = round( - ( - percent_single_crop / total_single_crop_column - if total_single_crop_column > 0 - else 0 - ), - 4, - ) - - # Handle doubly-cropped area calculation - double_crop_columns = df_crp_intensity_mws_data.filter( - like="doubly_cropped_area" - ) # avg_double_cropped - total_double_crop_column = double_crop_columns.shape[1] - sum_double_crop = double_crop_columns.sum(axis=1).sum() - percent_double_crop = ( - sum_double_crop * 100 / total_cropped_area - if total_cropped_area > 0 - else 0 - ) - avg_double_cropped = round( - ( - percent_double_crop / total_double_crop_column - if total_double_crop_column > 0 - else 0 - ), - 4, - ) - - # Handle triply-cropped area calculation - triply_crop_columns = df_crp_intensity_mws_data.filter( - like="triply_cropped_area" - ) # avg_triply_cropped - total_triply_crop_column = triply_crop_columns.shape[1] - sum_triply_crop = triply_crop_columns.sum(axis=1).sum() - percent_triply_crop = ( - sum_triply_crop * 100 / total_cropped_area - if total_cropped_area > 0 - else 0 - ) - avg_triply_cropped = round( - ( - percent_triply_crop / total_triply_crop_column - if total_triply_crop_column > 0 - else 0 - ), - 4, - ) - - except Exception as e: - # Handle exception and ensure all variables are set - cropping_intensity_avg = -9999 - cropping_intensity_trend = -9999 - avg_single_cropped = -9999 - avg_double_cropped = -9999 - avg_triply_cropped = -9999 - print(f"Error occurred: {e}") - - - ##################### SWB ##################### - try: - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ - sheets["croppingIntensity_annual"]["UID"] == specific_mws_id - ] - - df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) - df_swb_annual_mws_data = df_swb_annual_mws_data.fillna(0) - swb_area_kharif_columns = df_swb_annual_mws_data.filter( - like="kharif_area" - ) - single_kharif_crop_columns = df_crp_intensity_mws_data.filter( - like="single_kharif_cropped_area" - ) - double_crop_columns = df_crp_intensity_mws_data.filter( - like="doubly_cropped_area" - ) - triply_crop_columns = df_crp_intensity_mws_data.filter( - like="triply_cropped_area" - ) - - combined_columns_kharif = single_kharif_crop_columns.add( - double_crop_columns, fill_value=0 - ) - combined_columns_kharif = combined_columns_kharif.add( - triply_crop_columns, fill_value=0 - ) - total_cropped_area_kharif = combined_columns_kharif.sum( - axis=1 - ).sum() - total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] - sum_swb_area_kharif = swb_area_kharif_columns.sum(axis=1).sum() - - avg_wsr_ratio_kharif = ( - sum_swb_area_kharif / total_cropped_area_kharif - if total_cropped_area_kharif > 0 - else 0 - ) - avg_wsr_ratio_kharif = round( - avg_wsr_ratio_kharif * 100 / total_swb_area_kharif_column, 4 - ) - swb_area_rabi_columns = df_swb_annual_mws_data.filter( - like="rabi_area" - ) - single_non_kharif_crop_columns = df_crp_intensity_mws_data.filter( - like="single_non_kharif_cropped_area" - ) - - # Combine the cropping areas and calculate total cropped area for Rabi - combined_columns_rabi = single_non_kharif_crop_columns.add( - double_crop_columns, fill_value=0 - ) - combined_columns_rabi = combined_columns_rabi.add( - triply_crop_columns, fill_value=0 - ) - total_cropped_area_rabi = combined_columns_rabi.sum(axis=1).sum() - - total_swb_rabi_column = swb_area_rabi_columns.shape[1] - sum_swb_area_rabi = swb_area_rabi_columns.sum(axis=1).sum() - - # Average WSR ratio for Rabi - avg_wsr_ratio_rabi = ( - sum_swb_area_rabi / total_cropped_area_rabi - if total_cropped_area_rabi > 0 - else 0 - ) - avg_wsr_ratio_rabi = round( - avg_wsr_ratio_rabi * 100 / total_swb_rabi_column, 4 - ) - swb_area_zaid_columns = df_swb_annual_mws_data.filter( - like="zaid_area" - ) - total_cropped_area_zaid = triply_crop_columns.sum(axis=1).sum() - - total_swb_zaid_column = swb_area_zaid_columns.shape[1] - sum_swb_area_zaid = swb_area_zaid_columns.sum(axis=1).sum() - avg_wsr_ratio_zaid = ( - sum_swb_area_zaid / total_cropped_area_zaid - if total_cropped_area_zaid > 0 - else 0 - ) - avg_wsr_ratio_zaid = round( - avg_wsr_ratio_zaid * 100 / total_swb_zaid_column, 4 - ) - - except Exception as e: - avg_wsr_ratio_kharif = -9999 - avg_wsr_ratio_rabi = -9999 - avg_wsr_ratio_zaid = -9999 - print(f"Error occurred: {e}") - - ############ Swb_average - avg_kharif_surface_water_mws = -9999 - avg_rabi_surface_water_mws = -9999 - avg_zaid_surface_water_mws = -9999 - try: - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - if not df_swb_annual_mws_data.empty: - total_swb_area = df_swb_annual_mws_data.iloc[0][ - "total_swb_area_in_ha" - ] - - if total_swb_area != 0: # Check if total_swb_area is not zero - swb_area_kharif_columns = df_swb_annual_mws_data.filter( - like="kharif_area" - ) - total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] - sum_swb_area_kharif = ( - swb_area_kharif_columns.sum(axis=1).sum() / total_swb_area - ) - avg_kharif_surface_water_mws = round( - ( - sum_swb_area_kharif * 100 / total_swb_area_kharif_column - if total_swb_area_kharif_column > 0 - else 0 - ), - 4, - ) - - swb_rabi_area_columns = df_swb_annual_mws_data.filter( - like="rabi_area" - ) - total_swb_rabi_area_column = swb_rabi_area_columns.shape[1] - sum_swb_rabi_area = ( - swb_rabi_area_columns.sum(axis=1).sum() / total_swb_area - ) - avg_rabi_surface_water_mws = round( - ( - sum_swb_rabi_area * 100 / total_swb_rabi_area_column - if total_swb_rabi_area_column > 0 - else 0 - ), - 4, - ) - - swb_zaid_area_columns = df_swb_annual_mws_data.filter( - like="zaid_area" - ) - total_swb_zaid_area_column = swb_zaid_area_columns.shape[1] - sum_swb_zaid_area = ( - swb_zaid_area_columns.sum(axis=1).sum() / total_swb_area - ) - avg_zaid_surface_water_mws = round( - ( - sum_swb_zaid_area * 100 / total_swb_zaid_area_column - if total_swb_zaid_area_column > 0 - else 0 - ), - 4, - ) - else: - avg_perc_kharif_surface_water_mws = ( - avg_perc_rabi_surface_water_mws - ) = avg_perc_zaid_surface_water_mws = 0 - - except: - avg_perc_kharif_surface_water_mws = ( - avg_perc_rabi_surface_water_mws - ) = avg_perc_zaid_surface_water_mws = -9999 - - ################# SWB Trend ###################### - try: - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - swb_T = df_swb_annual_mws_data.filter( - like="total_area_in_ha" - ).dropna() # Drop rows with NaN for trend calculation - swb_T = swb_T.iloc[0].dropna().tolist() - result = mk.original_test(swb_T) - - trend_swb = None - if result.trend == "no trend": - trend_swb = "0" - elif result.trend == "increasing": - trend_swb = "1" - else: - trend_swb = "-1" - except: - trend_swb = -9999 - - ######### G Trend ################# - try: - G_Trend = ( - hydro_annual_mws_data.filter(like="G") - .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) - .dropna() - ) - G_Trend = G_Trend.squeeze().tolist() - result = mk.original_test(G_Trend) - - def sens_slope(data): - slopes = [] - for i in range(len(data) - 1): - for j in range(i + 1, len(data)): - s = (data[j] - data[i]) / (j - i) - slopes.append(s) - return np.median(slopes) - - trend_g_value = sens_slope(G_Trend) - trend_g = None - if result.trend == "no trend": - trend_g = "0" - elif result.trend == "increasing": - trend_g = "1" - else: - trend_g = "-1" - except: - trend_g = -9999 - - ######### drought_category ############## - try: - - layers = LayerInfo.objects.get( - layer_type="vector", workspace="drought" - ) - years = [ - str(year) - for year in range(layers.start_year, layers.end_year + 1) - ] - - df_crpDrought_mws_data = sheets["croppingDrought_kharif"][ - sheets["croppingDrought_kharif"]["UID"] == specific_mws_id - ] - - sum_moderate_severe = { - year: ( - 1 - if ( - df_crpDrought_mws_data.iloc[0][ - f"Moderate_in_weeks_{year}" - ] - + df_crpDrought_mws_data.iloc[0][ - f"Severe_in_weeks_{year}" - ] - ) - >= 5 - else 0 - ) - for year in years - } - sum_of_values = sum(sum_moderate_severe.values()) - drought_category = None - if sum_of_values >= 2: - drought_category = 2 - else: - drought_category = sum_of_values - - ######## avg_dry_spell_in_weeks - dryspell_columns = df_crpDrought_mws_data.filter( - like="drysp_unit_4_weeks" - ) # avg_dry_spell_in_weeks - total_dryspell_column = dryspell_columns.shape[1] - sum_dryspell = dryspell_columns.sum(axis=1).sum() - avg_dry_spell_in_weeks = round( - ( - sum_dryspell / total_dryspell_column - if total_dryspell_column > 0 - else 0 - ), - 4, - ) - except: - drought_category = -9999 - avg_dry_spell_in_weeks = -9999 - - ################# avg_runoff - runoff_columns = hydro_annual_mws_data.filter( - like="RunOff" - ) # avg_runoff - total_runoff_column = runoff_columns.shape[1] - sum_runoff = runoff_columns.sum(axis=1).sum() - avg_runoff = sum_runoff / total_runoff_column - - ############## Nrega Asset ########################## - try: - df_nrega_assets_mws_data = sheets["nrega_annual"][ - sheets["nrega_annual"]["mws_id"] == specific_mws_id - ] - nrega_assets_sum = ( - df_nrega_assets_mws_data.iloc[:, 1:] - .select_dtypes(include="number") - .sum() - .sum() - ) - except: - nrega_assets_sum = -9999 - - ############ MWS Intersect Villages ######################## - try: - df_mws_inters_villages_mws_data = sheets["mws_intersect_villages"][ - sheets["mws_intersect_villages"]["MWS UID"] == specific_mws_id - ] - mws_intersect_villages = df_mws_inters_villages_mws_data.get( - "Village IDs", None - ).iloc[0] - mws_intersect_villages = ast.literal_eval(mws_intersect_villages) - except: - mws_intersect_villages = [] - - ############ Change Detection Degradation ################### - try: - df_change_degr_detection_mws_data = sheets[ - "change_detection_degradation" - ][sheets["change_detection_degradation"]["UID"] == specific_mws_id] - degr_sum = ( - df_change_degr_detection_mws_data[ - [ - "farm_to_barren_area_in_ha", - "farm_to_scrub_land_area_in_ha", - ] - ] - .sum(axis=1) - .iloc[0] - ) - df_change_crp_detection_mws_data = sheets[ - "change_detection_cropintensity" - ][ - sheets["change_detection_cropintensity"]["UID"] - == specific_mws_id - ] - crp_sum = ( - df_change_crp_detection_mws_data[ - [ - "double_to_single_area_in_ha", - "triple_to_double_area_in_ha", - "triple_to_single_area_in_ha", - ] - ] - .sum(axis=1) - .iloc[0] - ) - degradation_land_area = degr_sum + crp_sum - change_in_cropping_intensity_area = ( - df_change_crp_detection_mws_data.get( - "total_change_crop_intensity_area_in_ha", None - ).iloc[0] - ) - - except: - degradation_land_area = -9999 - change_in_cropping_intensity_area = -9999 - - ############ Change Detection Afforestation ################### - try: - df_change_affo_detection_mws_data = sheets[ - "change_detection_afforestation" - ][ - sheets["change_detection_afforestation"]["UID"] - == specific_mws_id - ] - afforestation_column = [ - "barren_to_forest_area_in_ha", - "farm_to_forest_area_in_ha", - ] - afforestation_land_area = df_change_affo_detection_mws_data.get( - "total_afforestation_area_in_ha", None - ).iloc[0] - except: - afforestation_land_area = -9999 - - ############ Change Detection Deforestation ################### - try: - df_change_defo_detection_mws_data = sheets[ - "change_detection_deforestation" - ][ - sheets["change_detection_deforestation"]["UID"] - == specific_mws_id - ] - deforestation_land_area = df_change_defo_detection_mws_data.get( - "total_deforestation_area_in_ha", None - ).iloc[0] - except: - deforestation_land_area = -9999 - - ############ Change Detection Urbanization ################### - try: - df_change_urba_detection_mws_data = sheets[ - "change_detection_urbanization" - ][sheets["change_detection_urbanization"]["UID"] == specific_mws_id] - urbanization_land_area = df_change_urba_detection_mws_data.get( - "total_urbanization_area_in_ha", None - ).iloc[0] - except: - urbanization_land_area = -9999 - - ############# Terrain lulc slope / plain ##################### - try: - df_lulc_slope_mws_data = sheets["terrain_lulc_slope"][ - sheets["terrain_lulc_slope"]["UID"] == specific_mws_id - ] - lulc_slope_category = ( - df_lulc_slope_mws_data.get("cluster_name", pd.NA).iloc[0] - if not df_lulc_slope_mws_data.empty - else None - ) - - except: - lulc_slope_category = -9999 - - try: - df_lulc_plain_mws_data = sheets["terrain_lulc_plain"][ - sheets["terrain_lulc_plain"]["UID"] == specific_mws_id - ] - lulc_plain_category = ( - df_lulc_plain_mws_data.get("cluster_name", pd.NA).iloc[0] - if not df_lulc_plain_mws_data.empty - else None - ) - - except: - lulc_plain_category = -9999 - - ################# Restoration Vector ######################### - try: - df_restoration_vector_mws_data = sheets["restoration_vector"][ - sheets["restoration_vector"]["UID"] == specific_mws_id - ] - wide_scale_restoration = df_restoration_vector_mws_data.get( - "wide_scale_restoration_area_in_ha", None - ).iloc[0] - area_protection = df_restoration_vector_mws_data.get( - "protection_area_in_ha", None - ).iloc[0] - except: - wide_scale_restoration = -9999 - area_protection = -9999 - - ################# Aquifer Vector ######################### - aquifer_class_map = {0: "Hard Rock", 1: "Alluvial"} - - class_to_id = {v: k for k, v in aquifer_class_map.items()} - try: - df_aquifer_vector_mws_data = sheets["aquifer_vector"][ - sheets["aquifer_vector"]["UID"] == specific_mws_id - ] - aquifer_class_name = df_aquifer_vector_mws_data.get( - "aquifer_class", None - ).iloc[0] - if aquifer_class_name == "Alluvium": - aquifer_class_name = "Alluvial" - aquifer_class = int(class_to_id.get(aquifer_class_name, "")) - except Exception: - aquifer_class = -9999 - - ################# SOGE Vector ######################### - Soge_class = { - 0: "Safe", - 1: "Semi-Critical", - 2: "Critical", - 3: "Over Exploited", - 4: "Not Assessed", - } - - class_to_id = {v: k for k, v in Soge_class.items()} - try: - df_soge_vector_mws_data = sheets["soge_vector"][ - sheets["soge_vector"]["UID"] == specific_mws_id - ] - soge_class_name = df_soge_vector_mws_data.get( - "class_name", None - ).iloc[0] - soge_class = int( - class_to_id.get(soge_class_name, "") - ) # Returns None if not found - except Exception: - soge_class = -9999 - - ################## LCW Conflict ###################### - ## if count is 0 then Areas with no conflicts else Areas with conflicts - try: - lcw_conflict_count = sheets["lcw_conflict"][ - sheets["lcw_conflict"]["UID"] == specific_mws_id - ].shape[0] - if lcw_conflict_count == 0: - lcw_conflict = 0 - else: - lcw_conflict = 1 - except Exception as e: - lcw_conflict = -9999 - - ################## mining ###################### - ## if count is 0 then Areas with no mining else Areas with mining - try: - mining_count = sheets["mining"][ - sheets["mining"]["UID"] == specific_mws_id - ].shape[0] - if mining_count == 0: - mining = 0 - else: - mining = 1 - except Exception as e: - mining = -9999 - - ################## green credit ###################### - ## if count is 0 then Areas with no green credit else Areas with green credit - try: - green_credit_count = sheets["green_credit"][ - sheets["green_credit"]["UID"] == specific_mws_id - ].shape[0] - if green_credit_count == 0: - green_credit = 0 - else: - green_credit = 1 - except Exception as e: - green_credit = -9999 - - ################## factory csr ###################### - ## if count is 0 then Areas with no factory else Areas with factory - try: - factory_csr_count = sheets["factory_csr"][ - sheets["factory_csr"]["UID"] == specific_mws_id - ].shape[0] - if factory_csr_count == 0: - factory_csr = 0 - else: - factory_csr = 1 - except Exception as e: - factory_csr = -9999 - - ############ MWS Intersect Swb ######################## - try: - swb_df = sheets["mws_intersect_swb"] - - if swb_df is not -1 and not swb_df.empty: - mws_swb_data = swb_df[swb_df["UID"] == specific_mws_id] - - mws_intersect_swb = mws_swb_data.apply( - lambda row: { - "swbId": str(row["SWB_UID"]), - "swbName": ( - str(row["Waterbodies_name"]) - if pd.notna(row["Waterbodies_name"]) - else "" - ), - "latitude": ( - float(row["Latitude"]) - if pd.notna(row["Latitude"]) - else None - ), - "longitude": ( - float(row["Longitude"]) - if pd.notna(row["Longitude"]) - else None - ), - }, - axis=1, - ).tolist() - else: - mws_intersect_swb = [] - - except Exception as e: - print(f"Error in SWB funda: {e}") - mws_intersect_swb = [] - - ############ DEM (Digital Elevation Model) ######################## - try: - dem_df = sheets["dem"] - if dem_df is not -1 and not dem_df.empty: - mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] - - # Average of all UID mean elevations - overall_mean_elevation = dem_df["mean_elevation_in_m"].mean() - if not mws_dem_data.empty: - row = mws_dem_data.iloc[0] - relief = round( - row["max_elevation_in_m"] - row["min_elevation_in_m"], 2 - ) - mean_elevation = round(row["mean_elevation_in_m"], 2) - - # Relative mean elevation - if overall_mean_elevation != 0: - relative_mean_elevation = round( - (mean_elevation - overall_mean_elevation), 2 - ) - else: - relative_mean_elevation = 0 - - else: - relief = 0 - mean_elevation = 0 - relative_mean_elevation = 0 - - else: - relief = -9999 - mean_elevation = -9999 - relative_mean_elevation = -9999 - - except Exception as e: - print(f"Error in getting DEM data: {e}") - relief = -9999 - mean_elevation = -9999 - relative_mean_elevation = -9999 - - ############ Canal ######################## - try: - canal_df = sheets["canal"] - if canal_df is not -1 and not canal_df.empty: - mws_canal_data = canal_df[canal_df["UID"] == specific_mws_id] - if not mws_canal_data.empty: - canal_available = True - else: - canal_available = False - - else: - canal_available = False - - except Exception as e: - print(f"Error in getting canal data: {e}") - canal_available = -9999 - - ############ Canal ######################## - try: - river_df = sheets["river"] - if river_df is not -1 and not river_df.empty: - mws_river_data = river_df[river_df["UID"] == specific_mws_id] - if not mws_river_data.empty: - river_available = True - else: - river_available = False - - else: - river_available = False - - except Exception as e: - print(f"Error in getting canal data: {e}") - river_available = -9999 - - ############ lulc vector ######################## - try: - lulc_shrub_percent = -9999 - lulc_forest_percent = -9999 - lulc_crop_percent = -9999 - - lulc_df = sheets["lulc_vector"] - - if lulc_df is not -1 and not lulc_df.empty: - - mws_lulc_data = lulc_df[lulc_df["UID"] == specific_mws_id] - - if not mws_lulc_data.empty: - - row = mws_lulc_data.iloc[0] - - # Total area - area_in_ha = float(row.get("area_in_ha", 0)) - - # Shrub - shrub_cols = [ - col - for col in lulc_df.columns - if col.startswith("shrub_scrub_in_ha_") - ] - - lulc_shrub_area = round( - sum(row[col] for col in shrub_cols) / len(shrub_cols), 2 - ) - - # Forest - forest_cols = [ - col - for col in lulc_df.columns - if col.startswith("tree_forest_in_ha_") - ] - - lulc_forest_area = round( - sum(row[col] for col in forest_cols) / len(forest_cols), - 2, - ) - - if area_in_ha > 0: - lulc_shrub_percent = round( - (lulc_shrub_area / area_in_ha) * 100, 2 - ) - - lulc_forest_percent = round( - (lulc_forest_area / area_in_ha) * 100, 2 - ) - - df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ - sheets["croppingIntensity_annual"]["UID"] == specific_mws_id - ] - - crp_row = df_crp_intensity_mws_data.iloc[0] - area_in_ha = float(crp_row.get("area_in_ha", 0)) - cropped_area_in_ha = float(crp_row.get("sum_area_in_ha", 0)) - - lulc_crop_percent = round( - (cropped_area_in_ha / area_in_ha) * 100, 2 - ) - - else: - lulc_shrub_percent = -9999 - lulc_forest_percent = -9999 - lulc_crop_percent = -9999 - except Exception as e: - print(f"Error in LULC vector: {e}") - - lulc_shrub_percent = -9999 - lulc_forest_percent = -9999 - lulc_crop_percent = -9999 - - ############ Canal ######################## - try: - drainage_density_df = sheets["drainage_density"] - if drainage_density_df is not -1 and not drainage_density_df.empty: - mws_drainage_density_data = drainage_density_df[ - drainage_density_df["UID"] == specific_mws_id - ] - if not mws_drainage_density_data.empty: - row = mws_drainage_density_data.iloc[0] - drainage_density = round(row["drainage_density_std_in_km_per_km2"], 2) - else: - drainage_density = 0 - - else: - drainage_density = -9999 - - - except Exception as e: - print(f"Error in getting drainage_density data: {e}") - drainage_density = -9999 - - results.append( - { - "mws_id": specific_mws_id, - "terrainCluster_ID": terrainCluster_ID, - "avg_precipitation": avg_percipitation, - "cropping_intensity_trend": cropping_intensity_trend, - "cropping_intensity_avg": cropping_intensity_avg, - "avg_single_cropped": avg_single_cropped, - "avg_double_cropped": avg_double_cropped, - "avg_triple_cropped": avg_triply_cropped, - "avg_wsr_ratio_kharif": avg_wsr_ratio_kharif, - "avg_wsr_ratio_rabi": avg_wsr_ratio_rabi, - "avg_wsr_ratio_zaid": avg_wsr_ratio_zaid, - "avg_kharif_surface_water_mws": avg_kharif_surface_water_mws, - "avg_rabi_surface_water_mws": avg_rabi_surface_water_mws, - "avg_zaid_surface_water_mws": avg_zaid_surface_water_mws, - "trend_swb": trend_swb, - "trend_g": trend_g, - "drought_category": drought_category, - "avg_number_dry_spell": avg_dry_spell_in_weeks, - "avg_runoff": round(avg_runoff, 4), - "total_nrega_assets": nrega_assets_sum, - "mws_intersect_villages": mws_intersect_villages, - "degradation_land_area": round(degradation_land_area, 4), - "increase_in_tree_cover": round(afforestation_land_area, 4), - "decrease_in_tree_cover": round(deforestation_land_area, 4), - "degradation_cropping_intensity": round( - change_in_cropping_intensity_area, 4 - ), - "urbanization_area": round(urbanization_land_area, 4), - "lulc_slope_category": lulc_slope_category, - "lulc_plain_category": lulc_plain_category, - "area_wide_scale_restoration": round(wide_scale_restoration, 4), - "area_protection": round(area_protection, 4), - "aquifer_class": aquifer_class, - "soge_class": soge_class, - "lcw_conflict": lcw_conflict, - "mining": mining, - "green_credit": green_credit, - "factory_csr": factory_csr, - "mws_intersect_swb": mws_intersect_swb, - "relief": relief, - "mean_elevation": mean_elevation, - "relative_mean_elevation": relative_mean_elevation, - "canal_available": canal_available, - "river_available": river_available, - "lulc_shrub_percent": lulc_shrub_percent, - "lulc_forest_percent": lulc_forest_percent, - "lulc_crop_percent": lulc_crop_percent, - "drainage_density": drainage_density, - } - ) - - results_df = pd.DataFrame(results) - if file_type == "xlsx": - results_df.to_excel(file_xl_path + "_KYL_filter_data.xlsx", index=False) - elif file_type == "json": - results_list = results_df.to_dict(orient="records") - with open(file_xl_path + "_KYL_filter_data.json", "w") as json_file: - json.dump(results_list, json_file, indent=4) - elif file_type == "geojson": - layer_name = "deltaG_well_depth_" + district + "_" + block - mws_annual_geojson = get_url("mws_layers", layer_name) - response = requests.get(mws_annual_geojson) - response.raise_for_status() - - # Check if response has content - if response.content: - geojson_data = response.json() - deltaG_geojson = file_xl_path + "_deltaG_annual.geojson" - - with open(deltaG_geojson, "w") as f: - json.dump(geojson_data, f) - create_geojson_for_all_mws( - deltaG_geojson, - results_df, - file_xl_path + "_KYL_filter_data.geojson", - ) - file_path = get_mws_KYL_filter_data(state, district, block, file_type) - - except Exception as e: - return Response( - { - "status": "error", - "message": f"Error during file generation: {str(e)}", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - if file_path: - content_type_map = { - "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "json": "application/json", - "geojson": "application/geo+json", - } - content_type = content_type_map.get(file_type, "application/octet-stream") - - with open(file_path, "rb") as file: - response = HttpResponse( - file.read(), - content_type=content_type, - ) - response["Content-Disposition"] = ( - f"attachment; filename={district}_{block}_KYL_filter_data.{file_type}" - ) - return response - - else: - return Response( - {"status": "error", "message": "Failed to generate or download file"}, - status=status.HTTP_404_NOT_FOUND, - ) - - -def get_mws_KYL_filter_data(state, district, block, file_type): - state_folder = state.replace(" ", "_").upper() - district_folder = district.replace(" ", "_").upper() - file_xl_path = os.path.join( - EXCEL_PATH, - "data/stats_excel_files", - state_folder, - district_folder, - f"{district}_{block}", - ) - - file_path = None - if file_type == "xlsx": - file_path = os.path.join(file_xl_path + "_KYL_filter_data.xlsx") - elif file_type == "json": - file_path = os.path.join(file_xl_path + "_KYL_filter_data.json") - elif file_type == "geojson": - file_path = os.path.join(file_xl_path + "_KYL_filter_data.geojson") - - if os.path.exists(file_path): - return file_path - else: - return None +import os +import requests, json +import pandas as pd +import pymannkendall as mk +import numpy as np +import ast +from nrm_app.settings import EXCEL_PATH +from .utils import get_url +from rest_framework.response import Response +from rest_framework import status +from django.http import HttpResponse +from .models import LayerInfo + + +def create_geojson_for_all_mws(existing_geojson_path, df, new_geojson_path): + with open(existing_geojson_path) as f: + existing_data = json.load(f) + + features = [] + + for _, row in df.iterrows(): + uid = row["mws_id"] + geometry = None + + for feature in existing_data["features"]: + if feature["properties"].get("uid") == uid: + geometry = feature["geometry"] + break + + if geometry is None: + print( + f"No geometry found for uid: {uid}. Using default geometry (e.g., None)." + ) + geometry = {"type": "Point", "coordinates": [0, 0]} + + properties = row.to_dict() + + new_feature = { + "type": "Feature", + "geometry": geometry, + "properties": properties, + } + features.append(new_feature) + + new_feature_collection = {"type": "FeatureCollection", "features": features} + + with open(new_geojson_path, "w") as f: + json.dump(new_feature_collection, f) + + +def generate_mws_data_for_kyl_filters( + state, district, block, file_type, regenerate=None +): + state_folder = state.replace(" ", "_").upper() + district_folder = district.replace(" ", "_").upper() + file_xl_path = os.path.join( + EXCEL_PATH, + "data/stats_excel_files", + state_folder, + district_folder, + f"{district}_{block}", + ) + if regenerate: + file_path = None + else: + file_path = get_mws_KYL_filter_data(state, district, block, file_type) + if not file_path: + try: + sheets = { + "hydrological_annual": -1, + "terrain": -1, + "croppingIntensity_annual": -1, + "surfaceWaterBodies_annual": -1, + "croppingDrought_kharif": -1, + "nrega_annual": -1, + "mws_intersect_villages": -1, + "change_detection_degradation": -1, + "change_detection_afforestation": -1, + "change_detection_deforestation": -1, + "change_detection_urbanization": -1, + "change_detection_cropintensity": -1, + "terrain_lulc_slope": -1, + "terrain_lulc_plain": -1, + "restoration_vector": -1, + "aquifer_vector": -1, + "soge_vector": -1, + "lcw_conflict": -1, + "factory_csr": -1, + "mining": -1, + "green_credit": -1, + "mws_intersect_swb": -1, + "dem": -1, + "canal": -1, + "river": -1, + "lulc_vector": -1, + "drainage_density": -1, + } + + try: + with pd.ExcelFile(file_xl_path + ".xlsx") as xl: + available_sheets = xl.sheet_names # Get list of available sheets + + # Try to parse each sheet if it exists + for sheet_name in sheets.keys(): + if sheet_name in available_sheets: + try: + sheets[sheet_name] = xl.parse(sheet_name) + except Exception as e: + print(f"Error parsing sheet {sheet_name}: {e}") + sheets[sheet_name] = -1 + else: + print(f"Sheet {sheet_name} not found in Excel file") + sheets[sheet_name] = -1 + + except Exception as e: + print(f"Error reading Excel file: {e}") + # Return all sheets as -1 if the file can't be read + return {k: -1 for k in sheets.keys()} + + results = [] + df_hydrological_annual = sheets["hydrological_annual"] + + for specific_mws_id in df_hydrological_annual["UID"].unique(): + hydro_annual_mws_data = df_hydrological_annual[ + df_hydrological_annual["UID"] == specific_mws_id + ] + precipitation_columns = hydro_annual_mws_data.filter( + like="Precipitation" + ) # Avg_precipitation + total_percipitation_column = precipitation_columns.shape[1] + sum_precipitation = precipitation_columns.sum(axis=1).sum() + avg_percipitation = round( + sum_precipitation / total_percipitation_column, 4 + ) + + try: + terrain_vector_mws_data = sheets["terrain"][ + sheets["terrain"]["UID"] == specific_mws_id + ] + terrainCluster_ID = terrain_vector_mws_data.get( + "terrain_cluster_id", None + ).iloc[ + 0 + ] # terrain + except: + terrainCluster_ID = -9999 + + try: + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) + + crp_Intensity_columns = df_crp_intensity_mws_data.filter( + like="cropping_intensity_unit_less" + ) # cropping_intensity_avg + total_crp_Intensity_column = crp_Intensity_columns.shape[1] + sum_crp_Intensity = crp_Intensity_columns.sum(axis=1).sum() + cropping_intensity_avg = round( + ( + sum_crp_Intensity / total_crp_Intensity_column + if total_crp_Intensity_column > 0 + else 0 + ), + 4, + ) + + ######### Cropping Intensity Trend ################# + crp_intensity_T = df_crp_intensity_mws_data.filter( + like="cropping_intensity_unit_less" + ).dropna() # Drop rows with NaN for trend calculation + crp_intensity_T = crp_intensity_T.squeeze().tolist()[:-3] + result = mk.original_test(crp_intensity_T) + + def sens_slope(data): + slopes = [] + for i in range(len(data) - 1): + for j in range(i + 1, len(data)): + s = (data[j] - data[i]) / (j - i) + slopes.append(s) + return np.median(slopes) + + cropping_intensity_trend_value = sens_slope(crp_intensity_T) + cropping_intensity_trend = None + if result.trend == "no trend": + cropping_intensity_trend = "0" + elif result.trend == "increasing": + cropping_intensity_trend = "1" + else: + cropping_intensity_trend = "-1" + + # Total cropped area, replace NaN with 0 for these columns as well + total_cropped_area = df_crp_intensity_mws_data.iloc[0][ + "sum_area_in_ha" + ] + + # Handle single-cropped area calculation + single_crop_columns = df_crp_intensity_mws_data.filter( + like="single_cropped_area" + ) # avg_single_cropped + total_single_crop_column = single_crop_columns.shape[1] + sum_single_crop = single_crop_columns.sum(axis=1).sum() + percent_single_crop = ( + sum_single_crop * 100 / total_cropped_area + if total_cropped_area > 0 + else 0 + ) + avg_single_cropped = round( + ( + percent_single_crop / total_single_crop_column + if total_single_crop_column > 0 + else 0 + ), + 4, + ) + + # Handle doubly-cropped area calculation + double_crop_columns = df_crp_intensity_mws_data.filter( + like="doubly_cropped_area" + ) # avg_double_cropped + total_double_crop_column = double_crop_columns.shape[1] + sum_double_crop = double_crop_columns.sum(axis=1).sum() + percent_double_crop = ( + sum_double_crop * 100 / total_cropped_area + if total_cropped_area > 0 + else 0 + ) + avg_double_cropped = round( + ( + percent_double_crop / total_double_crop_column + if total_double_crop_column > 0 + else 0 + ), + 4, + ) + + # Handle triply-cropped area calculation + triply_crop_columns = df_crp_intensity_mws_data.filter( + like="triply_cropped_area" + ) # avg_triply_cropped + total_triply_crop_column = triply_crop_columns.shape[1] + sum_triply_crop = triply_crop_columns.sum(axis=1).sum() + percent_triply_crop = ( + sum_triply_crop * 100 / total_cropped_area + if total_cropped_area > 0 + else 0 + ) + avg_triply_cropped = round( + ( + percent_triply_crop / total_triply_crop_column + if total_triply_crop_column > 0 + else 0 + ), + 4, + ) + + except Exception as e: + # Handle exception and ensure all variables are set + cropping_intensity_avg = -9999 + cropping_intensity_trend = -9999 + avg_single_cropped = -9999 + avg_double_cropped = -9999 + avg_triply_cropped = -9999 + print(f"Error occurred: {e}") + + + ##################### SWB ##################### + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + + df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) + df_swb_annual_mws_data = df_swb_annual_mws_data.fillna(0) + swb_area_kharif_columns = df_swb_annual_mws_data.filter( + like="kharif_area" + ) + single_kharif_crop_columns = df_crp_intensity_mws_data.filter( + like="single_kharif_cropped_area" + ) + double_crop_columns = df_crp_intensity_mws_data.filter( + like="doubly_cropped_area" + ) + triply_crop_columns = df_crp_intensity_mws_data.filter( + like="triply_cropped_area" + ) + + combined_columns_kharif = single_kharif_crop_columns.add( + double_crop_columns, fill_value=0 + ) + combined_columns_kharif = combined_columns_kharif.add( + triply_crop_columns, fill_value=0 + ) + total_cropped_area_kharif = combined_columns_kharif.sum( + axis=1 + ).sum() + total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] + sum_swb_area_kharif = swb_area_kharif_columns.sum(axis=1).sum() + + avg_wsr_ratio_kharif = ( + sum_swb_area_kharif / total_cropped_area_kharif + if total_cropped_area_kharif > 0 + else 0 + ) + avg_wsr_ratio_kharif = round( + avg_wsr_ratio_kharif * 100 / total_swb_area_kharif_column, 4 + ) + swb_area_rabi_columns = df_swb_annual_mws_data.filter( + like="rabi_area" + ) + single_non_kharif_crop_columns = df_crp_intensity_mws_data.filter( + like="single_non_kharif_cropped_area" + ) + + # Combine the cropping areas and calculate total cropped area for Rabi + combined_columns_rabi = single_non_kharif_crop_columns.add( + double_crop_columns, fill_value=0 + ) + combined_columns_rabi = combined_columns_rabi.add( + triply_crop_columns, fill_value=0 + ) + total_cropped_area_rabi = combined_columns_rabi.sum(axis=1).sum() + + total_swb_rabi_column = swb_area_rabi_columns.shape[1] + sum_swb_area_rabi = swb_area_rabi_columns.sum(axis=1).sum() + + # Average WSR ratio for Rabi + avg_wsr_ratio_rabi = ( + sum_swb_area_rabi / total_cropped_area_rabi + if total_cropped_area_rabi > 0 + else 0 + ) + avg_wsr_ratio_rabi = round( + avg_wsr_ratio_rabi * 100 / total_swb_rabi_column, 4 + ) + swb_area_zaid_columns = df_swb_annual_mws_data.filter( + like="zaid_area" + ) + total_cropped_area_zaid = triply_crop_columns.sum(axis=1).sum() + + total_swb_zaid_column = swb_area_zaid_columns.shape[1] + sum_swb_area_zaid = swb_area_zaid_columns.sum(axis=1).sum() + avg_wsr_ratio_zaid = ( + sum_swb_area_zaid / total_cropped_area_zaid + if total_cropped_area_zaid > 0 + else 0 + ) + avg_wsr_ratio_zaid = round( + avg_wsr_ratio_zaid * 100 / total_swb_zaid_column, 4 + ) + + except Exception as e: + avg_wsr_ratio_kharif = -9999 + avg_wsr_ratio_rabi = -9999 + avg_wsr_ratio_zaid = -9999 + print(f"Error occurred: {e}") + + ############ Swb_average + avg_kharif_surface_water_mws = -9999 + avg_rabi_surface_water_mws = -9999 + avg_zaid_surface_water_mws = -9999 + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + if not df_swb_annual_mws_data.empty: + total_swb_area = df_swb_annual_mws_data.iloc[0][ + "total_swb_area_in_ha" + ] + + if total_swb_area != 0: # Check if total_swb_area is not zero + swb_area_kharif_columns = df_swb_annual_mws_data.filter( + like="kharif_area" + ) + total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] + sum_swb_area_kharif = ( + swb_area_kharif_columns.sum(axis=1).sum() / total_swb_area + ) + avg_kharif_surface_water_mws = round( + ( + sum_swb_area_kharif * 100 / total_swb_area_kharif_column + if total_swb_area_kharif_column > 0 + else 0 + ), + 4, + ) + + swb_rabi_area_columns = df_swb_annual_mws_data.filter( + like="rabi_area" + ) + total_swb_rabi_area_column = swb_rabi_area_columns.shape[1] + sum_swb_rabi_area = ( + swb_rabi_area_columns.sum(axis=1).sum() / total_swb_area + ) + avg_rabi_surface_water_mws = round( + ( + sum_swb_rabi_area * 100 / total_swb_rabi_area_column + if total_swb_rabi_area_column > 0 + else 0 + ), + 4, + ) + + swb_zaid_area_columns = df_swb_annual_mws_data.filter( + like="zaid_area" + ) + total_swb_zaid_area_column = swb_zaid_area_columns.shape[1] + sum_swb_zaid_area = ( + swb_zaid_area_columns.sum(axis=1).sum() / total_swb_area + ) + avg_zaid_surface_water_mws = round( + ( + sum_swb_zaid_area * 100 / total_swb_zaid_area_column + if total_swb_zaid_area_column > 0 + else 0 + ), + 4, + ) + else: + avg_perc_kharif_surface_water_mws = ( + avg_perc_rabi_surface_water_mws + ) = avg_perc_zaid_surface_water_mws = 0 + + except: + avg_perc_kharif_surface_water_mws = ( + avg_perc_rabi_surface_water_mws + ) = avg_perc_zaid_surface_water_mws = -9999 + + ################# SWB Trend ###################### + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + swb_T = df_swb_annual_mws_data.filter( + like="total_area_in_ha" + ).dropna() # Drop rows with NaN for trend calculation + swb_T = swb_T.iloc[0].dropna().tolist() + result = mk.original_test(swb_T) + + trend_swb = None + if result.trend == "no trend": + trend_swb = "0" + elif result.trend == "increasing": + trend_swb = "1" + else: + trend_swb = "-1" + except: + trend_swb = -9999 + + ######### G Trend ################# + try: + G_Trend = ( + hydro_annual_mws_data.filter(like="G") + .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) + .dropna() + ) + G_Trend = G_Trend.squeeze().tolist() + result = mk.original_test(G_Trend) + + def sens_slope(data): + slopes = [] + for i in range(len(data) - 1): + for j in range(i + 1, len(data)): + s = (data[j] - data[i]) / (j - i) + slopes.append(s) + return np.median(slopes) + + trend_g_value = sens_slope(G_Trend) + trend_g = None + if result.trend == "no trend": + trend_g = "0" + elif result.trend == "increasing": + trend_g = "1" + else: + trend_g = "-1" + except: + trend_g = -9999 + + ######### drought_category ############## + try: + + layers = LayerInfo.objects.get( + layer_type="vector", workspace="drought" + ) + years = [ + str(year) + for year in range(layers.start_year, layers.end_year + 1) + ] + + df_crpDrought_mws_data = sheets["croppingDrought_kharif"][ + sheets["croppingDrought_kharif"]["UID"] == specific_mws_id + ] + + sum_moderate_severe = { + year: ( + 1 + if ( + df_crpDrought_mws_data.iloc[0][ + f"Moderate_in_weeks_{year}" + ] + + df_crpDrought_mws_data.iloc[0][ + f"Severe_in_weeks_{year}" + ] + ) + >= 5 + else 0 + ) + for year in years + } + sum_of_values = sum(sum_moderate_severe.values()) + drought_category = None + if sum_of_values >= 2: + drought_category = 2 + else: + drought_category = sum_of_values + + ######## avg_dry_spell_in_weeks + dryspell_columns = df_crpDrought_mws_data.filter( + like="drysp_unit_4_weeks" + ) # avg_dry_spell_in_weeks + total_dryspell_column = dryspell_columns.shape[1] + sum_dryspell = dryspell_columns.sum(axis=1).sum() + avg_dry_spell_in_weeks = round( + ( + sum_dryspell / total_dryspell_column + if total_dryspell_column > 0 + else 0 + ), + 4, + ) + except: + drought_category = -9999 + avg_dry_spell_in_weeks = -9999 + + ################# avg_runoff + runoff_columns = hydro_annual_mws_data.filter( + like="RunOff" + ) # avg_runoff + total_runoff_column = runoff_columns.shape[1] + sum_runoff = runoff_columns.sum(axis=1).sum() + avg_runoff = sum_runoff / total_runoff_column + + ############## Nrega Asset ########################## + try: + df_nrega_assets_mws_data = sheets["nrega_annual"][ + sheets["nrega_annual"]["mws_id"] == specific_mws_id + ] + nrega_assets_sum = ( + df_nrega_assets_mws_data.iloc[:, 1:] + .select_dtypes(include="number") + .sum() + .sum() + ) + except: + nrega_assets_sum = -9999 + + ############ MWS Intersect Villages ######################## + try: + df_mws_inters_villages_mws_data = sheets["mws_intersect_villages"][ + sheets["mws_intersect_villages"]["MWS UID"] == specific_mws_id + ] + mws_intersect_villages = df_mws_inters_villages_mws_data.get( + "Village IDs", None + ).iloc[0] + mws_intersect_villages = ast.literal_eval(mws_intersect_villages) + except: + mws_intersect_villages = [] + + ############ Change Detection Degradation ################### + try: + df_change_degr_detection_mws_data = sheets[ + "change_detection_degradation" + ][sheets["change_detection_degradation"]["UID"] == specific_mws_id] + degr_sum = ( + df_change_degr_detection_mws_data[ + [ + "farm_to_barren_area_in_ha", + "farm_to_scrub_land_area_in_ha", + ] + ] + .sum(axis=1) + .iloc[0] + ) + df_change_crp_detection_mws_data = sheets[ + "change_detection_cropintensity" + ][ + sheets["change_detection_cropintensity"]["UID"] + == specific_mws_id + ] + crp_sum = ( + df_change_crp_detection_mws_data[ + [ + "double_to_single_area_in_ha", + "triple_to_double_area_in_ha", + "triple_to_single_area_in_ha", + ] + ] + .sum(axis=1) + .iloc[0] + ) + degradation_land_area = degr_sum + crp_sum + change_in_cropping_intensity_area = ( + df_change_crp_detection_mws_data.get( + "total_change_crop_intensity_area_in_ha", None + ).iloc[0] + ) + + except: + degradation_land_area = -9999 + change_in_cropping_intensity_area = -9999 + + ############ Change Detection Afforestation ################### + try: + df_change_affo_detection_mws_data = sheets[ + "change_detection_afforestation" + ][ + sheets["change_detection_afforestation"]["UID"] + == specific_mws_id + ] + afforestation_column = [ + "barren_to_forest_area_in_ha", + "farm_to_forest_area_in_ha", + ] + afforestation_land_area = df_change_affo_detection_mws_data.get( + "total_afforestation_area_in_ha", None + ).iloc[0] + except: + afforestation_land_area = -9999 + + ############ Change Detection Deforestation ################### + try: + df_change_defo_detection_mws_data = sheets[ + "change_detection_deforestation" + ][ + sheets["change_detection_deforestation"]["UID"] + == specific_mws_id + ] + deforestation_land_area = df_change_defo_detection_mws_data.get( + "total_deforestation_area_in_ha", None + ).iloc[0] + except: + deforestation_land_area = -9999 + + ############ Change Detection Urbanization ################### + try: + df_change_urba_detection_mws_data = sheets[ + "change_detection_urbanization" + ][sheets["change_detection_urbanization"]["UID"] == specific_mws_id] + urbanization_land_area = df_change_urba_detection_mws_data.get( + "total_urbanization_area_in_ha", None + ).iloc[0] + except: + urbanization_land_area = -9999 + + ############# Terrain lulc slope / plain ##################### + try: + df_lulc_slope_mws_data = sheets["terrain_lulc_slope"][ + sheets["terrain_lulc_slope"]["UID"] == specific_mws_id + ] + lulc_slope_category = ( + df_lulc_slope_mws_data.get("cluster_name", pd.NA).iloc[0] + if not df_lulc_slope_mws_data.empty + else None + ) + + except: + lulc_slope_category = -9999 + + try: + df_lulc_plain_mws_data = sheets["terrain_lulc_plain"][ + sheets["terrain_lulc_plain"]["UID"] == specific_mws_id + ] + lulc_plain_category = ( + df_lulc_plain_mws_data.get("cluster_name", pd.NA).iloc[0] + if not df_lulc_plain_mws_data.empty + else None + ) + + except: + lulc_plain_category = -9999 + + ################# Restoration Vector ######################### + try: + df_restoration_vector_mws_data = sheets["restoration_vector"][ + sheets["restoration_vector"]["UID"] == specific_mws_id + ] + wide_scale_restoration = df_restoration_vector_mws_data.get( + "wide_scale_restoration_area_in_ha", None + ).iloc[0] + area_protection = df_restoration_vector_mws_data.get( + "protection_area_in_ha", None + ).iloc[0] + except: + wide_scale_restoration = -9999 + area_protection = -9999 + + ################# Aquifer Vector ######################### + aquifer_class_map = {0: "Hard Rock", 1: "Alluvial"} + + class_to_id = {v: k for k, v in aquifer_class_map.items()} + try: + df_aquifer_vector_mws_data = sheets["aquifer_vector"][ + sheets["aquifer_vector"]["UID"] == specific_mws_id + ] + aquifer_class_name = df_aquifer_vector_mws_data.get( + "aquifer_class", None + ).iloc[0] + if aquifer_class_name == "Alluvium": + aquifer_class_name = "Alluvial" + aquifer_class = int(class_to_id.get(aquifer_class_name, "")) + except Exception: + aquifer_class = -9999 + + ################# SOGE Vector ######################### + Soge_class = { + 0: "Safe", + 1: "Semi-Critical", + 2: "Critical", + 3: "Over Exploited", + 4: "Not Assessed", + } + + class_to_id = {v: k for k, v in Soge_class.items()} + try: + df_soge_vector_mws_data = sheets["soge_vector"][ + sheets["soge_vector"]["UID"] == specific_mws_id + ] + soge_class_name = df_soge_vector_mws_data.get( + "class_name", None + ).iloc[0] + soge_class = int( + class_to_id.get(soge_class_name, "") + ) # Returns None if not found + except Exception: + soge_class = -9999 + + ################## LCW Conflict ###################### + ## if count is 0 then Areas with no conflicts else Areas with conflicts + try: + lcw_conflict_count = sheets["lcw_conflict"][ + sheets["lcw_conflict"]["UID"] == specific_mws_id + ].shape[0] + if lcw_conflict_count == 0: + lcw_conflict = 0 + else: + lcw_conflict = 1 + except Exception as e: + lcw_conflict = -9999 + + ################## mining ###################### + ## if count is 0 then Areas with no mining else Areas with mining + try: + mining_count = sheets["mining"][ + sheets["mining"]["UID"] == specific_mws_id + ].shape[0] + if mining_count == 0: + mining = 0 + else: + mining = 1 + except Exception as e: + mining = -9999 + + ################## green credit ###################### + ## if count is 0 then Areas with no green credit else Areas with green credit + try: + green_credit_count = sheets["green_credit"][ + sheets["green_credit"]["UID"] == specific_mws_id + ].shape[0] + if green_credit_count == 0: + green_credit = 0 + else: + green_credit = 1 + except Exception as e: + green_credit = -9999 + + ################## factory csr ###################### + ## if count is 0 then Areas with no factory else Areas with factory + try: + factory_csr_count = sheets["factory_csr"][ + sheets["factory_csr"]["UID"] == specific_mws_id + ].shape[0] + if factory_csr_count == 0: + factory_csr = 0 + else: + factory_csr = 1 + except Exception as e: + factory_csr = -9999 + + ############ MWS Intersect Swb ######################## + try: + swb_df = sheets["mws_intersect_swb"] + + if swb_df is not -1 and not swb_df.empty: + mws_swb_data = swb_df[swb_df["UID"] == specific_mws_id] + + mws_intersect_swb = mws_swb_data.apply( + lambda row: { + "swbId": str(row["SWB_UID"]), + "swbName": ( + str(row["Waterbodies_name"]) + if pd.notna(row["Waterbodies_name"]) + else "" + ), + "latitude": ( + float(row["Latitude"]) + if pd.notna(row["Latitude"]) + else None + ), + "longitude": ( + float(row["Longitude"]) + if pd.notna(row["Longitude"]) + else None + ), + }, + axis=1, + ).tolist() + else: + mws_intersect_swb = [] + + except Exception as e: + print(f"Error in SWB funda: {e}") + mws_intersect_swb = [] + + ############ DEM (Digital Elevation Model) ######################## + try: + dem_df = sheets["dem"] + if dem_df is not -1 and not dem_df.empty: + mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] + + # Average of all UID mean elevations + overall_mean_elevation = dem_df["mean_elevation_in_m"].mean() + if not mws_dem_data.empty: + row = mws_dem_data.iloc[0] + relief = round( + row["max_elevation_in_m"] - row["min_elevation_in_m"], 2 + ) + mean_elevation = round(row["mean_elevation_in_m"], 2) + + # Relative mean elevation + if overall_mean_elevation != 0: + relative_mean_elevation = round( + (mean_elevation - overall_mean_elevation), 2 + ) + else: + relative_mean_elevation = 0 + + else: + relief = 0 + mean_elevation = 0 + relative_mean_elevation = 0 + + else: + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 + + except Exception as e: + print(f"Error in getting DEM data: {e}") + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 + + ############ Canal ######################## + try: + canal_df = sheets["canal"] + if canal_df is not -1 and not canal_df.empty: + mws_canal_data = canal_df[canal_df["UID"] == specific_mws_id] + if not mws_canal_data.empty: + canal_available = True + else: + canal_available = False + + else: + canal_available = False + + except Exception as e: + print(f"Error in getting canal data: {e}") + canal_available = -9999 + + ############ Canal ######################## + try: + river_df = sheets["river"] + if river_df is not -1 and not river_df.empty: + mws_river_data = river_df[river_df["UID"] == specific_mws_id] + if not mws_river_data.empty: + river_available = True + else: + river_available = False + + else: + river_available = False + + except Exception as e: + print(f"Error in getting canal data: {e}") + river_available = -9999 + + ############ lulc vector ######################## + try: + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 + + lulc_df = sheets["lulc_vector"] + + if lulc_df is not -1 and not lulc_df.empty: + + mws_lulc_data = lulc_df[lulc_df["UID"] == specific_mws_id] + + if not mws_lulc_data.empty: + + row = mws_lulc_data.iloc[0] + + # Total area + area_in_ha = float(row.get("area_in_ha", 0)) + + # Shrub + shrub_cols = [ + col + for col in lulc_df.columns + if col.startswith("shrub_scrub_in_ha_") + ] + + lulc_shrub_area = round( + sum(row[col] for col in shrub_cols) / len(shrub_cols), 2 + ) + + # Forest + forest_cols = [ + col + for col in lulc_df.columns + if col.startswith("tree_forest_in_ha_") + ] + + lulc_forest_area = round( + sum(row[col] for col in forest_cols) / len(forest_cols), + 2, + ) + + if area_in_ha > 0: + lulc_shrub_percent = round( + (lulc_shrub_area / area_in_ha) * 100, 2 + ) + + lulc_forest_percent = round( + (lulc_forest_area / area_in_ha) * 100, 2 + ) + + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + + crp_row = df_crp_intensity_mws_data.iloc[0] + area_in_ha = float(crp_row.get("area_in_ha", 0)) + cropped_area_in_ha = float(crp_row.get("sum_area_in_ha", 0)) + + lulc_crop_percent = round( + (cropped_area_in_ha / area_in_ha) * 100, 2 + ) + + else: + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 + except Exception as e: + print(f"Error in LULC vector: {e}") + + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 + + ############ Canal ######################## + try: + drainage_density_df = sheets["drainage_density"] + if drainage_density_df is not -1 and not drainage_density_df.empty: + mws_drainage_density_data = drainage_density_df[ + drainage_density_df["UID"] == specific_mws_id + ] + if not mws_drainage_density_data.empty: + row = mws_drainage_density_data.iloc[0] + drainage_density = round(row["drainage_density_std_in_km_per_km2"], 2) + else: + drainage_density = 0 + + else: + drainage_density = -9999 + + + except Exception as e: + print(f"Error in getting drainage_density data: {e}") + drainage_density = -9999 + + results.append( + { + "mws_id": specific_mws_id, + "terrainCluster_ID": terrainCluster_ID, + "avg_precipitation": avg_percipitation, + "cropping_intensity_trend": cropping_intensity_trend, + "cropping_intensity_avg": cropping_intensity_avg, + "avg_single_cropped": avg_single_cropped, + "avg_double_cropped": avg_double_cropped, + "avg_triple_cropped": avg_triply_cropped, + "avg_wsr_ratio_kharif": avg_wsr_ratio_kharif, + "avg_wsr_ratio_rabi": avg_wsr_ratio_rabi, + "avg_wsr_ratio_zaid": avg_wsr_ratio_zaid, + "avg_kharif_surface_water_mws": avg_kharif_surface_water_mws, + "avg_rabi_surface_water_mws": avg_rabi_surface_water_mws, + "avg_zaid_surface_water_mws": avg_zaid_surface_water_mws, + "trend_swb": trend_swb, + "trend_g": trend_g, + "drought_category": drought_category, + "avg_number_dry_spell": avg_dry_spell_in_weeks, + "avg_runoff": round(avg_runoff, 4), + "total_nrega_assets": nrega_assets_sum, + "mws_intersect_villages": mws_intersect_villages, + "degradation_land_area": round(degradation_land_area, 4), + "increase_in_tree_cover": round(afforestation_land_area, 4), + "decrease_in_tree_cover": round(deforestation_land_area, 4), + "degradation_cropping_intensity": round( + change_in_cropping_intensity_area, 4 + ), + "urbanization_area": round(urbanization_land_area, 4), + "lulc_slope_category": lulc_slope_category, + "lulc_plain_category": lulc_plain_category, + "area_wide_scale_restoration": round(wide_scale_restoration, 4), + "area_protection": round(area_protection, 4), + "aquifer_class": aquifer_class, + "soge_class": soge_class, + "lcw_conflict": lcw_conflict, + "mining": mining, + "green_credit": green_credit, + "factory_csr": factory_csr, + "mws_intersect_swb": mws_intersect_swb, + "relief": relief, + "mean_elevation": mean_elevation, + "relative_mean_elevation": relative_mean_elevation, + "canal_available": canal_available, + "river_available": river_available, + "lulc_shrub_percent": lulc_shrub_percent, + "lulc_forest_percent": lulc_forest_percent, + "lulc_crop_percent": lulc_crop_percent, + "drainage_density": drainage_density, + } + ) + + results_df = pd.DataFrame(results) + if file_type == "xlsx": + results_df.to_excel(file_xl_path + "_KYL_filter_data.xlsx", index=False) + elif file_type == "json": + results_list = results_df.to_dict(orient="records") + with open(file_xl_path + "_KYL_filter_data.json", "w") as json_file: + json.dump(results_list, json_file, indent=4) + elif file_type == "geojson": + layer_name = "deltaG_well_depth_" + district + "_" + block + mws_annual_geojson = get_url("mws_layers", layer_name) + response = requests.get(mws_annual_geojson) + response.raise_for_status() + + # Check if response has content + if response.content: + geojson_data = response.json() + deltaG_geojson = file_xl_path + "_deltaG_annual.geojson" + + with open(deltaG_geojson, "w") as f: + json.dump(geojson_data, f) + create_geojson_for_all_mws( + deltaG_geojson, + results_df, + file_xl_path + "_KYL_filter_data.geojson", + ) + file_path = get_mws_KYL_filter_data(state, district, block, file_type) + + except Exception as e: + return Response( + { + "status": "error", + "message": f"Error during file generation: {str(e)}", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + if file_path: + content_type_map = { + "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "json": "application/json", + "geojson": "application/geo+json", + } + content_type = content_type_map.get(file_type, "application/octet-stream") + + with open(file_path, "rb") as file: + response = HttpResponse( + file.read(), + content_type=content_type, + ) + response["Content-Disposition"] = ( + f"attachment; filename={district}_{block}_KYL_filter_data.{file_type}" + ) + return response + + else: + return Response( + {"status": "error", "message": "Failed to generate or download file"}, + status=status.HTTP_404_NOT_FOUND, + ) + + +def get_mws_KYL_filter_data(state, district, block, file_type): + state_folder = state.replace(" ", "_").upper() + district_folder = district.replace(" ", "_").upper() + file_xl_path = os.path.join( + EXCEL_PATH, + "data/stats_excel_files", + state_folder, + district_folder, + f"{district}_{block}", + ) + + file_path = None + if file_type == "xlsx": + file_path = os.path.join(file_xl_path + "_KYL_filter_data.xlsx") + elif file_type == "json": + file_path = os.path.join(file_xl_path + "_KYL_filter_data.json") + elif file_type == "geojson": + file_path = os.path.join(file_xl_path + "_KYL_filter_data.geojson") + + if os.path.exists(file_path): + return file_path + else: + return None diff --git a/stats_generator/utils.py b/stats_generator/utils.py index bcc2c694..fd563c3c 100644 --- a/stats_generator/utils.py +++ b/stats_generator/utils.py @@ -1,2269 +1,2269 @@ -import os -import requests, json -from django.http import HttpResponse, Http404 -from rest_framework.response import Response -import pandas as pd -import geopandas as gpd -from collections import defaultdict -from datetime import datetime -from nrm_app.settings import GEOSERVER_URL, EXCEL_PATH -import numpy as np -from shapely.geometry import Point, shape -from .models import LayerInfo -from django.http import HttpResponse -from rest_framework import status -from pathlib import Path - - -def fetch_layers_for_excel_generation(): - """ - Fetch all vector layers where `excel_to_be_generated` is True. - """ - layers = LayerInfo.objects.filter( - layer_type="vector", excel_to_be_generated=True - ).values("layer_name", "workspace", "start_year", "end_year") - return list(layers) - - -def get_url(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 get_vector_layer_geoserver(state, district, block, specific_sheets=None): - print(f"Generate Stats excel for {state}_{district}_{block}") - base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") - district_path = os.path.join( - base_path, state.replace(" ", "_").upper(), district.replace(" ", "_").upper() - ) - os.makedirs(district_path, exist_ok=True) - xlsx_file = os.path.join(district_path, f"{district}_{block}.xlsx") - - workspaces_to_process = specific_sheets - - # Handle existing sheets when adding specific workspaces - results = [] - file_exists = os.path.exists(xlsx_file) - mode = "a" if file_exists else "w" - - # Use append mode with if_sheet_exists='replace' - with pd.ExcelWriter( - xlsx_file, - engine="openpyxl", - mode=mode, - if_sheet_exists="replace" if mode == "a" else None, - ) as writer: - for layer in fetch_layers_for_excel_generation(): - workspace = layer["workspace"] - - if workspaces_to_process and workspace not in workspaces_to_process: - continue - - start_year = layer.get("start_year") - end_year = layer.get("end_year") - - if "{district}" in layer["layer_name"] and "{block}" in layer["layer_name"]: - layer_name = layer["layer_name"].format(district=district, block=block) - else: - layer_name = layer["layer_name"] - - print(f"Processing layer: {layer_name}") - print(f"Workspace for the layer is: {workspace}") - - geojson_data = None - try: - url = get_url(workspace, layer_name) - response = requests.get(url) - response.raise_for_status() - geojson_data = response.json() - except requests.exceptions.RequestException as e: - print(f"Failed to fetch data for {layer_name}: {e}") - results.append( - {"layer": layer_name, "status": "failed", "workspace": workspace} - ) - continue - - # Process the data based on workspace - if workspace == "terrain": - create_excel_for_terrain(geojson_data, xlsx_file, writer) - elif ( - workspace == "terrain_lulc" - and layer_name == f"{district}_{block}_lulc_slope" - ): - create_excel_for_terrain_lulc_slope(geojson_data, xlsx_file, writer) - elif ( - workspace == "terrain_lulc" - and layer_name == f"{district}_{block}_lulc_plain" - ): - create_excel_for_terrain_lulc_plain(geojson_data, xlsx_file, writer) - elif workspace == "swb": - create_excel_for_swb( - geojson_data, xlsx_file, writer, start_year, end_year - ) - create_excel_for_mws_intersect_swb( - geojson_data, writer, district, block - ) - elif workspace == "nrega_assets": - mws_lay_name = f"deltaG_well_depth_{district}_{block}" - mws_file_url = get_url("mws_layers", mws_lay_name) - - try: - response = requests.get(mws_file_url) - response.raise_for_status() - mws_geojson_datas = response.json() - except requests.exceptions.RequestException as e: - print(f"Failed to fetch MWS data: {e}") - continue - - create_excel_for_nrega_assets( - geojson_data, - mws_geojson_datas, - xlsx_file, - writer, - start_year, - end_year, - ) - try: - fetch_village_asset_count( - state, district, block, writer, xlsx_file, start_year, end_year - ) - except Exception as e: - print("Exception as e", str(e)) - - elif workspace == "crop_intensity": - create_excel_crop_inten( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "drought": - create_excel_crop_drou( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif ( - workspace == "mws_layers" - and layer_name == f"deltaG_well_depth_{district}_{block}" - ): - parsed_data_annual_mws = parse_geojson_annual_mws(geojson_data) - create_excel_annual_mws(parsed_data_annual_mws, xlsx_file, writer) - try: - create_excel_mws_inters_villages( - geojson_data, xlsx_file, writer, district, block - ) - except Exception as e: - print("Exception", str(e)) - elif ( - workspace == "mws_layers" - and layer_name == f"deltaG_fortnight_{district}_{block}" - ): - processed_data = [ - process_feature(feature) for feature in geojson_data["features"] - ] - create_excel_seas_mws( - processed_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "panchayat_boundaries": - create_excel_for_village_boun(geojson_data, writer) - elif workspace == "drought_causality": - create_excel_for_drought_causality( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "ccd": - create_excel_for_ccd( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "canopy_height": - create_excel_for_ch( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "tree_overall_ch": - create_excel_for_overall_tree_change(geojson_data, xlsx_file, writer) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Afforestation" - ): - create_excel_chan_detection_afforestation( - geojson_data, xlsx_file, writer - ) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_CropIntensity" - ): - create_excel_chan_detection_cropintensity( - geojson_data, xlsx_file, writer - ) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Deforestation" - ): - create_excel_chan_detection_deforestation( - geojson_data, xlsx_file, writer - ) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Degradation" - ): - create_excel_chan_detection_degradation(geojson_data, xlsx_file, writer) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Urbanization" - ): - create_excel_chan_detection_urbanization( - geojson_data, xlsx_file, writer - ) - elif workspace == "restoration": - create_excel_for_restoration(geojson_data, xlsx_file, writer) - elif workspace == "aquifer": - create_excel_for_aquifer(geojson_data, writer) - elif workspace == "soge": - create_excel_for_soge(geojson_data, xlsx_file, writer) - elif workspace == "lcw": - create_excel_for_lcw(geojson_data, writer) - elif workspace == "agroecological": - create_excel_for_agroecological(geojson_data, writer) - elif workspace == "factory_csr": - create_excel_for_factory_csr(geojson_data, writer) - elif workspace == "green_credit": - create_excel_for_green_credit(geojson_data, writer) - elif workspace == "mining": - create_excel_for_mining(geojson_data, writer) - elif workspace == "stream_order": - create_excel_for_stream_order(geojson_data, writer) - elif workspace == "mws_connectivity": - create_excel_for_mws_connectivity(geojson_data, writer) - elif workspace == "mws": - create_excel_for_mws(geojson_data, writer) - elif workspace == "facilities_proximity": - create_excel_for_facilities(geojson_data, writer) - elif workspace == "dem": - create_excel_for_dem(geojson_data, writer) - elif workspace == "canal": - create_excel_for_canal(geojson_data, writer) - elif workspace == "river": - create_excel_for_river(geojson_data, writer) - elif workspace == "lulc_vector": - create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) - elif workspace == "drainage_density": - create_excel_for_drainage_density(geojson_data, writer) - elif workspace == "antyodaya_2020": - create_excel_for_antyodaya_20(geojson_data, writer) - elif workspace == "livestocks": - create_excel_for_livestock(geojson_data, writer) - - results.append( - {"layer": layer_name, "status": "success", "workspace": workspace} - ) - - return results - - -def create_excel_for_livestock(data, writer): - try: - features = data.get("features", []) - df_data = [feature.get("properties", {}) for feature in features] - df = pd.DataFrame(df_data) - - # Columns to exclude - exclude_cols = ["state_name","district_name","TEHSIL"] - df = df.drop(columns=exclude_cols, errors="ignore") - - # Keep important columns first if they exist - first_cols = [c for c in ["pc11_village_id", "NAME"] if c in df.columns] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - - df.to_excel(writer, sheet_name="livestock", index=False) - print("Excel file created for livestock") - except Exception as e: - print(f"Error in getting livestock data: {e}") - - -def create_excel_for_antyodaya_20(data, writer): - try: - features = data.get("features", []) - df_data = [feature.get("properties", {}) for feature in features] - df = pd.DataFrame(df_data) - - # Keep important columns first if they exist - first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - - # Round numeric columns - numeric_cols = df.select_dtypes(include=["number"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="antyodaya", index=False) - print("Excel file created for antyodaya") - except Exception as e: - print(f"Error in getting antyodaya data: {e}") - - -def create_excel_for_drainage_density(data, writer): - import ast - print("Inside create_excel_for Drainage Density") - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), - "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), - "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="drainage_density", index=False) - print("Excel file created for Drainage Density") - - -def create_excel_for_lulc_vector(data, writer, start_year, end_year): - df_data = [] - features = data["features"] - years = list(range(start_year, end_year + 1)) - - classes = { - "barrenland": ("barrenland", "barrenla"), - "built_up_area": ("built-up_a", "built-up"), - "cropland": ("cropland_a", "cropland"), - "double_crop": ("doubly_cro", "doubly_c"), - "triple_crop": ("triply_cro", "triply_c"), - "tree_forest": ("tree_fores", "tree_for"), - "shrub_scrub": ("shrub_scru", "shrub_sc"), - "single_kharif": ("single_kha", "single_k"), - "single_non_kharif": ("single_non", "single_n"), - "k_water": ("k_water_ar", "k_water_"), - "kr_water": ("kr_water_a", "kr_water"), - "krz_water": ("krz_water_", "krz_wate"), - } - - def get_key(base_key, trunc_prefix, idx): - """Derive the property key for a given year index.""" - if idx == 0: - return base_key - return f"{trunc_prefix}_{idx}" - - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "sum_in_ha": (properties.get("sum") or 0) / 10000, - } - - for idx, year in enumerate(years): - for class_name, (base_key, trunc_prefix) in classes.items(): - key = get_key(base_key, trunc_prefix, idx) - row[f"{class_name}_in_ha_{year}"] = properties.get(key, 0) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="lulc_vector", index=False) - print("Excel file created for lulc vector") - - -def create_excel_for_canal(data, writer): - print("Inside create_excel_for Canal") - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "project_name": properties.get("prjname", ""), - "canal_code": properties.get("cancode", ""), - "canal_name": properties.get("canname", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="canal", index=False) - print("Excel file created for canal") - except Exception as e: - print("Canal Layer not found :: ", e) - - -def create_excel_for_river(data, writer): - print("Inside create_excel_for River") - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "river_name": properties.get("rivname", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="river", index=False) - print("Excel file created for river") - except Exception as e: - print("River Layer not found :: ", e) - - -def create_excel_for_dem(data, writer): - print("Inside create_excel_for DEM") - - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties.get("uid", ""), - "min_elevation_in_m": properties.get("min_elevation", ""), - "max_elevation_in_m": properties.get("max_elevation", ""), - "mean_elevation_in_m": properties.get("mean_elevation", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="dem", index=False) - print("Excel file created for dem") - - -def create_excel_for_mws_intersect_swb(swb_geojson, writer, district, block): - print("Inside create_excel_for_mws_intersect_swb") - - # --- Fetch MWS layer --- - mws_layer_name = f"mws_{district}_{block}" - mws_data_url = get_url("mws", mws_layer_name) - - mws_response = requests.get(mws_data_url) - if mws_response.status_code != 200: - print(f"Error fetching MWS data: {mws_response.status_code}") - return - - mws_geojson = mws_response.json() - - def calculate_intersection_area(geom1, geom2): - if geom1.intersects(geom2): - return geom1.intersection(geom2).area - return 0 - - rows = [] - - for mws_feature in mws_geojson["features"]: - mws_props = mws_feature["properties"] - mws_uid = mws_props.get("uid") - mws_geom = shape(mws_feature["geometry"]) - - for swb_feature in swb_geojson["features"]: - swb_props = swb_feature["properties"] - swb_geom = shape(swb_feature["geometry"]) - - intersection_area = calculate_intersection_area(mws_geom, swb_geom) - - if intersection_area > 0: - # waterbodies centroid calculation - centroid = swb_geom.centroid - lon, lat = centroid.x, centroid.y - - rows.append( - { - "UID": mws_uid, - "SWB_UID": swb_props.get("UID"), - "Waterbodies_name": swb_props.get("water_body_name"), - "Latitude": lat, - "Longitude": lon, - } - ) - - df = pd.DataFrame(rows) - - if not df.empty: - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="mws_intersect_swb", index=False) - print("Excel sheet 'mws_intersect_swb' created successfully") - - -def create_excel_for_facilities(data, writer): - try: - features = data["features"] - df_data = [feature["properties"] for feature in features] - - df = pd.DataFrame(df_data) - - first_cols = ["censuscode2011", "censusname"] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] - df.rename( - columns={ - col: f"{col}_in_km" - for col in df.columns - if col not in exclude_cols - }, - inplace=True - ) - - # Write to Excel - df.to_excel(writer, sheet_name="facilities_proximity", index=False) - - print("Excel file created for facilities_proximity") - except Exception as e: - print("facilities_proximity Layer not found :: ", e) - - - -def create_excel_for_mws(data, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "watershed_code": properties.get("wsconc", ""), - "basin_code": properties.get("bacode", ""), - "sub_basin_code": properties.get("sbcode", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mws", index=False) - print("Excel file created for mws") - - -def create_excel_for_mws_connectivity(data, writer): - """ - direction_map = { - '0': 'No Direction', - '1': 'North-East', - '2': 'East', - '3': 'South-East', - '4': 'South', - '5': 'South-West', - '6': 'West', - '7': 'North-West', - '8': 'North' - } - """ - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "direction": properties["direction"], - "downstream_mws": properties["downstream"], - "upstream_mws": properties["upstream"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mws_connectivity", index=False) - print("Excel file created for mws_connectivity") - - -def create_excel_for_stream_order(data, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "order_1_area_percent": properties["1"], - "order_2_area_percent": properties["2"], - "order_3_area_percent": properties["3"], - "order_4_area_percent": properties["4"], - "order_5_area_percent": properties["5"], - "order_6_area_percent": properties["6"], - "order_7_area_percent": properties["7"], - "order_8_area_percent": properties["8"], - "order_9_area_percent": properties["9"], - "order_10_area_percent": properties["10"], - "order_11_area_percent": properties["11"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="stream_order", index=False) - print("Excel file created for stream order") - - -def create_excel_for_mining(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["company_na"], - "proposal": properties["proposal"], - "sector_moefcc": properties["sector_moe"], - "village": properties["village"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mining", index=False) - print("Excel file created for mining") - except Exception as e: - print("Mining Layer not found :: ", e) - - -def create_excel_for_green_credit(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["division"], - # "parcel_id": properties["parcel_id"], - "land_info": properties["land_info"], - "kml_url": properties["kml_url"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="green_credit", index=False) - print("Excel file created for green_credit") - except Exception as e: - print("green credit Layer not found :: ", e) - - -def create_excel_for_factory_csr(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "Company_Name": properties["COMPANY NA"], - "ADDRESS": properties["ADDRESS"], - "LOCATION T": properties["LOCATION T"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="factory_csr", index=False) - print("Excel file created for factory_csr") - except Exception as e: - print("factory csr Layer not found :: ", e) - - -def create_excel_for_agroecological(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "organization_name": properties["organization_name"], - "organization_type": properties["organization_type"], - "created_at": properties["created_at"], - "contact_person": properties["contact_person"], - "email": properties["email"], - "domains": properties["domains"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="agroecological", index=False) - print("Excel file created for agroecological") - except Exception as e: - print("agroecological Layer not found :: ", e) - - -def create_excel_for_lcw(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "title_of_conflict": properties["Title of Conflict"], - "link_to_conflict": properties["Link to conflict"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="lcw_conflict", index=False) - print("Excel file created for lcw_conflict") - except Exception as e: - print("lcw Layer not found :: ", e) - - -def create_excel_for_soge(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "soge_dev_percent": properties["sgw_dev_pe"], - "class_code": properties["code"], - "class_name": properties["class"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="soge_vector", index=False) - print(f"Excel file created for soge_vector") - - -def create_excel_for_aquifer(data, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - if properties.get("aquifer_class", "") == "Hard-Rock": - aquifer_class = "Hard Rock" - else: - aquifer_class = "Alluvium" - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "aquifer_class": aquifer_class, - "principle_aq_Alluvium_percent": properties.get( - "principle_aq_Alluvium_percent", "0" - ), - "principle_aq_Banded Gneissic Complex_percent": properties.get( - "principle_aq_Banded Gneissic Complex_percent", "0" - ), - "principle_aq_Basalt_percent": properties.get( - "principle_aq_Basalt_percent", "0" - ), - "principle_aq_Charnockite_percent": properties.get( - "principle_aq_Charnockite_percent", "0" - ), - "principle_aq_Gneiss_percent": properties.get( - "principle_aq_Gneiss_percent", "0" - ), - "principle_aq_Granite_percent": properties.get( - "principle_aq_Granite_percent", "0" - ), - "principle_aq_Intrusive_percent": properties.get( - "principle_aq_Intrusive_percent", "0" - ), - "principle_aq_Khondalite_percent": properties.get( - "principle_aq_Khondalite_percent", "0" - ), - "principle_aq_Laterite_percent": properties.get( - "principle_aq_Laterite_percent", "0" - ), - "principle_aq_Limestone_percent": properties.get( - "principle_aq_Limestone_percent", "0" - ), - "principle_aq_Quartzite_percent": properties.get( - "principle_aq_Quartzite_percent", "0" - ), - "principle_aq_Sandstone_percent": properties.get( - "principle_aq_Sandstone_percent", "0" - ), - "principle_aq_Schist_percent": properties.get( - "principle_aq_Schist_percent", "0" - ), - "principle_aq_Shale_percent": properties.get( - "principle_aq_Shale_percent", "0" - ), - "principle_aq_None_percent": properties.get( - "principle_aq_None_percent", "0" - ), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - # Round numeric values - numeric_cols = df.select_dtypes(include=["number"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="aquifer_vector", index=False) - print("Excel file created for aquifer_vector") - - -def create_excel_for_restoration(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "wide_scale_restoration_area_in_ha": properties["Wide-scale"], - "protection_area_in_ha": properties["Protection"], - "mosaic_restoration_area_in_ha": properties["Mosaic Res"], - "excluded_areas_in_ha": properties["Excluded A"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="restoration_vector", index=False) - print(f"Excel file created for restoration_vector") - - -def create_excel_for_overall_tree_change(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "afforestation_area_in_ha": properties["Afforestation"], - "deforestation_area_in_ha": properties["Deforestation"], - "degradation_area_in_ha": properties["Degradation"], - "improvement_area_in_ha": properties["Improvement"], - "missing_data_in_ha": properties["Missing Data"], - "no_change_area_in_ha": properties["No_Change"], - "partially_degraded_area_in_ha": properties["Partially_Degraded"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="overall_tree_change", index=False) - print(f"Excel file created for overall_tree_change") - - -def create_excel_for_ccd(data, xlsx_file, writer, start_year, end_year): - df_data = [] - features = data["features"] - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - } - - for year in range(start_year, end_year + 1): - row["high_density_area_in_ha_" + str(year)] = properties.get( - "High_Density_" + str(year), None - ) - row["low_density_area_in_ha_" + str(year)] = properties.get( - "Low_Density_" + str(year), None - ) - row["missing_data_area_in_ha_" + str(year)] = properties.get( - "Missing_Data_" + str(year), None - ) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="Canopy_Cover_Density", index=False) - print(f"Excel file created for Canopy_Cover_Density") - - -def create_excel_for_ch(data, xlsx_file, writer, start_year, end_year): - df_data = [] - features = data["features"] - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - } - - for year in range(start_year, end_year + 1): - row["short_trees_area_in_ha_" + str(year)] = properties.get( - "Short_Trees_" + str(year), None - ) - row["medium_trees_area_in_ha_" + str(year)] = properties.get( - "Medium_Height_Trees_" + str(year), None - ) - row["tall_trees_area_in_ha_" + str(year)] = properties.get( - "Tall_Trees_" + str(year), None - ) - row["missing_data_area_in_ha_" + str(year)] = properties.get( - "Missing_Data_" + str(year), None - ) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="Canopy_height", index=False) - print(f"Excel file created for Canopy_height") - - -def create_excel_for_drought_causality(data, xlsx_file, writer, start_year, end_year): - df_data = [] - features = data["features"] - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - } - - for year in range(start_year, end_year + 1): - row["severe_moderate_drought_causality_" + str(year)] = properties.get( - "se_mo_" + str(year), None - ) - row["mild_drought_causality_" + str(year)] = properties.get( - "mild_" + str(year), None - ) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="drought_causality", index=False) - print(f"Excel file created for drought_causality") - - -def create_excel_chan_detection_afforestation(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "barren_to_forest_area_in_ha": properties.get("ba_fo", None), - "built_up_to_forest_area_in_ha": properties.get("bu_fo", None), - "farm_to_forest_area_in_ha": properties.get("fa_fo", None), - "forest_to_forest_area_in_ha": properties.get("fo_fo", None), - "scrub_land_to_forest_area_in_ha": properties.get("sc_fo", None), - "total_afforestation_area_in_ha": properties.get("total_aff", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_afforestation", index=False) - print(f"Excel file created for change_detection_afforestation") - - -def create_excel_chan_detection_cropintensity(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "single_to_single_area_in_ha": properties.get("si_si", None), - "single_to_double_area_in_ha": properties.get("si_do", None), - "single_to_triple_area_in_ha": properties.get("si_tr", None), - "double_to_single_area_in_ha": properties.get("do_si", None), - "double_to_double_area_in_ha": properties.get("do_do", None), - "double_to_triple_area_in_ha": properties.get("do_tr", None), - "triple_to_single_area_in_ha": properties.get("tr_si", None), - "triple_to_double_area_in_ha": properties.get("tr_do", None), - "triple_to_triple_area_in_ha": properties.get("tr_tr", None), - "total_change_crop_intensity_area_in_ha": properties.get( - "total_chan", properties.get("total_change", None) - ), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_cropintensity", index=False) - print(f"Excel file created for change_detection_cropintensity") - - -def create_excel_chan_detection_deforestation(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "forest_to_barren_area_in_ha": properties.get("fo_ba", None), - "forest_to_built_up_area_in_ha": properties.get("fo_bu", None), - "forest_to_farm_area_in_ha": properties.get("fo_fa", None), - "forest_to_forest_area_in_ha": properties.get("fo_fo", None), - "forest_to_scrub_land_area_in_ha": properties.get("fo_sc", None), - "total_deforestation_area_in_ha": properties.get("total_def", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_deforestation", index=False) - print(f"Excel file created for change_detection_deforestation") - - -def create_excel_chan_detection_degradation(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "farm_to_barren_area_in_ha": properties.get("f_ba", None), - "farm_to_built_up_area_in_ha": properties.get("f_bu", None), - "farm_to_farm_area_in_ha": properties.get("f_f", None), - "farm_to_scrub_land_area_in_ha": properties.get("f_sc", None), - "total_degradation_area_in_ha": properties.get("total_deg", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_degradation", index=False) - print(f"Excel file created for change_detection_degradation") - - -def create_excel_chan_detection_urbanization(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "barren_shrub_to_built_up_area_in_ha": properties.get("b_bu", None), - "built_up_to_built_up_area_in_ha": properties.get("bu_bu", None), - "tree_farm_to_built_up_area_in_ha": properties.get("tr_bu", None), - "water_to_built_up_area_in_ha": properties.get("w_bu", None), - "total_urbanization_area_in_ha": properties.get("total_urb", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_urbanization", index=False) - print(f"Excel file created for change_detection_urbanization") - - -def create_excel_mws_inters_villages(mws_geojson, xlsx_file, writer, district, block): - print("Inside create_excel_mws_inters_villages") - admin_layer_name = district + "_" + block - admin_file_url = get_url("panchayat_boundaries", admin_layer_name) - - response = requests.get(admin_file_url) - if response.status_code != 200: - print(f"Error fetching data: {response.status_code}") - return - - village_geojson = response.json() - - def calculate_intersection_area_ha(village_geom, mws_geom, mws_area_ha): - if village_geom.intersects(mws_geom): - intersection = village_geom.intersection(mws_geom) - if mws_geom.area == 0: - return 0 - # ratio of intersection to full MWS geometry area (both in degrees²) - ratio = intersection.area / mws_geom.area - return ratio * mws_area_ha - return 0 - - mws_villages_dict = {} - - for mws_feature in mws_geojson["features"]: - mws_uid = mws_feature["properties"]["uid"] - mws_area = mws_feature["properties"]["area_in_ha"] - mws_geom = shape(mws_feature["geometry"]) - village_ids = set() - village_details = {} - - for village_feature in village_geojson["features"]: - village_id = village_feature["properties"]["vill_ID"] - if village_id == 0: - continue - - village_geom = shape(village_feature["geometry"]) - area_intersected_ha = calculate_intersection_area_ha( - village_geom, mws_geom, mws_area - ) - if area_intersected_ha > 0: - village_ids.add(village_id) - percentage_of_area = ( - (area_intersected_ha / mws_area) * 100 if mws_area > 0 else 0 - ) - village_details[village_id] = { - "area_intersect": round(area_intersected_ha, 2), - "percentage_of_area": round(percentage_of_area, 2), - } - - if village_ids: - mws_villages_dict[mws_uid] = { - "area_in_ha": mws_area, - "village_ids": list(village_ids), - "village_details": village_details, - } - - data = [ - { - "MWS UID": mws_uid, - "area_in_ha": values["area_in_ha"], - "Village IDs": values["village_ids"], - "Village Details": values["village_details"], - } - for mws_uid, values in mws_villages_dict.items() - ] - - df = pd.DataFrame(data) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="mws_intersect_villages", index=False) - print("The data has been saved to mws_intersect_villages") - - -# def create_excel_village_inters_mwss(mws_geojson, xlsx_file, writer, district, block): -# print("Inside create_excel_village_inters_mwss") -# admin_layer_name = district + '_' + block -# admin_file_url = get_url('panchayat_boundaries', admin_layer_name) - -# response = requests.get(admin_file_url) -# if response.status_code != 200: -# print(f"Error fetching data: {response.status_code}") -# return -# village_geojson = response.json() - -# def calculate_intersection_area(village_geom: BaseGeometry, mws_geom: BaseGeometry) : -# try: -# # Check for empty geometries -# if village_geom.is_empty or mws_geom.is_empty: -# return 0.0 - -# # Fix invalid geometries if needed -# if not village_geom.is_valid: -# village_geom = village_geom.buffer(0) -# if not mws_geom.is_valid: -# mws_geom = mws_geom.buffer(0) - -# # Calculate intersection -# if village_geom.intersects(mws_geom): -# intersection = village_geom.intersection(mws_geom) -# return intersection.area if not intersection.is_empty else 0.0 - -# return 0.0 - -# except Exception as e: -# print(f"Error calculating intersection area: {e}") -# return 0.0 - - -# data = [] - -# processed_villages = set() - -# for village_feature in village_geojson['features']: -# village_id = village_feature['properties']['vill_ID'] -# village_name = village_feature['properties']['vill_name'] - -# village_key = (village_id, village_name) - -# if village_key in processed_villages: -# continue -# processed_villages.add(village_key) - -# village_geom = shape(village_feature['geometry']) - -# mws_uids = [] -# intersection_areas = [] - -# for mws_feature in mws_geojson['features']: -# mws_geom = shape(mws_feature['geometry']) -# area_intersected = calculate_intersection_area(village_geom, mws_geom) -# if area_intersected > 0: -# mws_uids.append(mws_feature['properties']['uid']) -# intersection_areas.append(area_intersected) - -# data.append({ -# 'Village ID': village_id, -# 'Village Name': village_name, -# 'MWS UIDs': mws_uids, -# }) - -# df = pd.DataFrame(data) - -# ## for roundoff all numeric value upto 2 decimal -# numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns -# df[numeric_cols] = df[numeric_cols].round(2) - -# df.to_excel(writer, sheet_name='village_intersect_mwss', index=False) -# print("Excel created for village_intersect_mwss") - - -def create_excel_for_terrain(data, output_file, writer): - print("Inside create_excel_for_terrain function") - df_data = [] - - terrain_description = { - 0: "Broad Sloppy and Hilly", - 1: "Mostly Plains", - 2: "Mostly Hills and Valleys", - 3: "Broad Plains and Slopes", - } - - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "terrain_cluster_id": properties["terrainClu"], - "terrain_description": terrain_description.get(properties["terrainClu"]), - "hill_slope_area_percent": properties["hill_slope"], - "plain_area_percent": properties["plain_area"], - "ridge_area_percent": properties["ridge_area"], - "slopy_area_percent": properties["slopy_area"], - "valley_area_percent": properties["valley_are"], - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="terrain", index=False) - print(f"Excel file created for terrain vector") - - -def create_excel_for_terrain_lulc_slope(data, output_file, writer): - df_data = [] - - terrain_description = { - 0: "Broad Sloppy and Hilly", - 1: "Mostly Plains", - 2: "Mostly Hills and Valleys", - 3: "Broad Plains and Slopes", - } - - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "terrain_cluster_id": properties["terrain_cl"], - "terrain_description": terrain_description.get(properties["terrain_cl"]), - "cluster_name": properties["clust_name"], - "barren_area_percent": properties["barren"], - "forests_area_percent": properties["forests"], - "shrub_scrubs_area_percent": properties["shrub_scru"], - "single_kharif_area_percent": properties["sing_khari"], - "single_non_kharif_area_percent": properties["sing_non_k"], - "double_cropping_area_percent": properties["double"], - "triple_cropping_area_percent": properties["triple"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="terrain_lulc_slope", index=False) - print("Excel file created for terrain_lulc_slope") - - -def create_excel_for_terrain_lulc_plain(data, output_file, writer): - df_data = [] - - terrain_description = { - 0: "Broad Sloppy and Hilly", - 1: "Mostly Plains", - 2: "Mostly Hills and Valleys", - 3: "Broad Plains and Slopes", - } - - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "terrain_cluster_id": properties["terrain_cl"], - "terrain_description": terrain_description.get(properties["terrain_cl"]), - "cluster_name": properties["clust_name"], - "barren_area_percent": properties["barren"], - "forests_area_percent": properties["forest"], - "shrub_scrubs_area_percent": properties["shrubs_scr"], - "single_non_kharif_area_percent": properties["sing_non_k"], - "single_kharif_area_percent": properties["sing_crop"], - "double_cropping_area_percent": properties["double_cro"], - "triple_cropping_area_percent": properties["triple_cro"], - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="terrain_lulc_plain", index=False) - print("Excel file created for terrain_lulc_plain") - - -def create_excel_for_swb(data, output_file, writer, start_year, end_year): - df_data = [] - features = data.get("features", []) - - for feature in features: - properties = feature.get("properties", {}) - uid = properties.get("MWS_UID", "Unknown") - - def calculate_area(base_area, percentage): - if base_area == 0 or percentage == 0: - return 0 - return base_area * (percentage / 100) - - parts = uid.split("_") - num_uid_parts_is = [ - f"{parts[i]}_{parts[i+1]}" for i in range(0, len(parts) - 1, 2) - ] - if len(parts) % 2 == 1: # Check for an unpaired last part - num_uid_parts_is.append(parts[-1]) - - # Generate years dynamically based on start_year and end_year - years = range(start_year, end_year) - - for num_uid_part in num_uid_parts_is: - row = {"UID": num_uid_part} - - for year in years: - short_year = f"{str(year)[-2:]}-{str(year+1)[-2:]}" - - # Construct keys dynamically using the shortened year format - total_area_key = f"area_{short_year}" - kharif_key = f"k_{short_year}" - rabi_key = f"kr_{short_year}" - zaid_key = f"krz_{short_year}" - - # Get values from properties - total_area = properties.get(total_area_key, 0) - kharif_percentage = properties.get(kharif_key, 0) - rabi_percentage = properties.get(rabi_key, 0) - zaid_percentage = properties.get(zaid_key, 0) - - # Calculate areas - row[f"total_area_in_ha_{year}-{year+1}"] = total_area / len( - num_uid_parts_is - ) - row[f"kharif_area_in_ha_{year}-{year+1}"] = calculate_area( - total_area, kharif_percentage - ) / len(num_uid_parts_is) - row[f"rabi_area_in_ha_{year}-{year+1}"] = calculate_area( - total_area, rabi_percentage - ) / len(num_uid_parts_is) - row[f"zaid_area_in_ha_{year}-{year+1}"] = calculate_area( - total_area, zaid_percentage - ) / len(num_uid_parts_is) - - # Add total SWB area - row["total_swb_area_in_ha"] = properties.get("area_ored", 0) / len( - num_uid_parts_is - ) - df_data.append(row) - - df = pd.DataFrame(df_data) - agg_dict = {col: "sum" for col in df.columns if col != "UID"} - grouped_df = df.groupby("UID").agg(agg_dict).reset_index() - - df = grouped_df.sort_values(["UID"]) - - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="surfaceWaterBodies_annual", index=False) - print("Excel file created for surfaceWaterBodies_annual") - - -def create_excel_for_nrega_assets( - nrega_data, mws_data, output_file, writer, start_year, end_year -): - workCategoryMapping = { - "SWC - Landscape level impact": "Soil and water conservation", - "Agri Impact - HH, Community": "Land restoration", - "Plantation": "Plantations", - "Irrigation - Site level impact": "Irrigation on farms", - "Irrigation Site level - Non RWH": "Other farm works", - "Household Livelihood": "Off-farm livelihood assets", - "Others - HH, Community": "Community assets", - } - - mws = gpd.GeoDataFrame.from_features(mws_data["features"]) - nrega = gpd.GeoDataFrame.from_features(nrega_data["features"]) - - # Set CRS if available in JSON - if "crs" in mws_data: - mws.set_crs(mws_data["crs"]["properties"]["name"], inplace=True) - if "crs" in nrega_data: - nrega.set_crs(nrega_data["crs"]["properties"]["name"], inplace=True) - - joined = gpd.sjoin(nrega, mws, how="inner", predicate="within") - counts = {} - - df_data = [] - valid_years = range(start_year, end_year) - - date_formats = [ - "%d-%b-%y %H:%M:%S.%f", - "%d-%b-%y %H:%M:%S", - "%d-%m-%y %H:%M:%S.%f", - "%d-%m-%y %H:%M:%S", - "%d-%b-%Y %H:%M:%S.%f", - "%d-%b-%Y %H:%M:%S", - "%d-%m-%Y %H:%M:%S.%f", - "%d-%m-%Y %H:%M:%S", - "%Y-%m-%d %H:%M:%S.%f", - "%Y-%m-%d %H:%M:%S", - "%Y/%m/%d %H:%M:%S.%f", - "%Y/%m/%d %H:%M:%S", - "%Y-%m-%dT%H:%M:%SZ", - ] - - for _, row in joined.iterrows(): - creation_t = row["creation_t"] - work_category = row["WorkCatego"] - mws_id = row["uid"] - - if isinstance(creation_t, pd.Timestamp): - creation_t = creation_t.strftime("%d-%m-%Y %H:%M:%S") - - date_obj = None - for date_format in date_formats: - try: - date_obj = datetime.strptime(creation_t, date_format) - break - except ValueError: - continue - - if date_obj is None: - continue - - year = date_obj.year - if year < 100: - year += 2000 - - if year not in valid_years: - continue - - category = workCategoryMapping.get(work_category, "Others - HH, Community") - - if mws_id not in counts: - counts[mws_id] = { - year: {cat: 0 for cat in workCategoryMapping.values()} - for year in range(start_year, end_year) - } - - if category not in counts[mws_id][year]: - counts[mws_id][year][category] = 0 - counts[mws_id][year][category] += 1 - - for mws_id, year_data in counts.items(): - row = {"mws_id": mws_id} - for year, categories in year_data.items(): - for category in workCategoryMapping.values(): - count = categories.get(category, 0) - row[f"{category}_count_{year}"] = count - df_data.append(row) - - if not df_data: - print("No data was collected for the DataFrame.") - else: - print(f"Collected {len(df_data)} rows of data for the DataFrame.") - - if df_data: - df = pd.DataFrame(df_data) - df.to_excel(writer, sheet_name="nrega_annual", index=False) - print("Excel file created for nrega_annual") - return "successfully created" - else: - print("No data available to write to Excel.") - - -def create_excel_village_nrega_assets( - result_df, output_file, writer, all_villages_df, start_year, end_year -): - workCategoryMapping = { - "SWC - Landscape level impact": "Soil and water conservation", - "Agri Impact - HH, Community": "Land restoration", - "Plantation": "Plantations", - "Irrigation - Site level impact": "Irrigation on farms", - "Irrigation Site level - Non RWH": "Other farm works", - "Household Livelihood": "Off-farm livelihood assets", - "Others - HH, Community": "Community assets", - } - - # start_year, end_year = 2017, 2022 - year_range = range(start_year, end_year + 1) - - # Initialize all-zero DataFrame for all villages - rows = [] - for _, row in all_villages_df.iterrows(): - base_row = {"vill_id": row["vill_ID"], "vill_name": row["vill_name"]} - for year in year_range: - for cat in workCategoryMapping.values(): - base_row[f"{cat}_count_{year}"] = 0 - rows.append(base_row) - - final_df = pd.DataFrame(rows) - - # Fill counts from assets - for _, row in result_df.iterrows(): - creation_t = row["creation_t"] - try: - date_obj = pd.to_datetime(creation_t, errors="coerce") - if pd.isnull(date_obj): - continue - except: - continue - - year = date_obj.year - if year not in year_range: - continue - - category = workCategoryMapping.get(row["WorkCatego"]) - if not category: - continue - - mask = (final_df["vill_id"] == row["vill_ID"]) & ( - final_df["vill_name"] == row["vill_name"] - ) - col_name = f"{category}_count_{year}" - final_df.loc[mask, col_name] += 1 - - # Sort columns for clean layout - id_cols = ["vill_id", "vill_name"] - category_cols = sorted( - [col for col in final_df.columns if col not in id_cols], - key=lambda x: (int(x.split("_")[-1]), x), - ) - final_df = final_df[id_cols + category_cols] - - # Save to Excel - final_df = final_df.drop_duplicates(subset=["vill_id", "vill_name"]) - final_df.to_excel(writer, sheet_name="nrega_assets_village", index=False) - print("Excel file created successfully with all villages.") - - -def fetch_village_asset_count( - state, district, block, writer, output_file, start_year, end_year -): - # 1. Read village data - village_gdf = gpd.read_file(get_url("panchayat_boundaries", f"{district}_{block}"))[ - ["vill_ID", "vill_name", "geometry"] - ].copy() - print("Village data loaded") - - # 2. Get NREGA data with timeout to prevent hanging - try: - nrega_url = get_url("nrega_assets", f"{district}_{block}") - print(f"Fetching: {nrega_url}") - - # Add timeout to prevent hanging - response = requests.get(nrega_url, timeout=120) - nrega_json = response.json() - print("NREGA data fetched successfully") - - except Exception as e: - print(f"Failed to get NREGA data: {e}") - # Return villages with "No Asset" if API fails - result_df = village_gdf[["vill_ID", "vill_name"]].copy() - result_df["Asset ID"] = "No Asset" - result_df["creation_t"] = "No Asset" - result_df["WorkCatego"] = "No Asset" - create_excel_village_nrega_assets( - result_df, - output_file, - writer, - village_gdf[["vill_ID", "vill_name"]], - start_year, - end_year, - ) - return result_df, village_gdf[["vill_ID", "vill_name"]] - - # 3. Process asset features - points_data = [] - features = nrega_json.get("features", []) - print(f"Processing {len(features)} features") - - for feature in features: - try: - point = Point(feature["geometry"]["coordinates"]) - properties = feature["properties"] - points_data.append( - { - "geometry": point, - "Asset ID": properties.get("Asset ID", "MISSING"), - "creation_t": properties.get("creation_t", ""), - "WorkCatego": properties.get("WorkCatego", ""), - } - ) - except: - continue - - # If no valid points, return no assets - if not points_data: - print("No valid asset points found") - result_df = village_gdf[["vill_ID", "vill_name"]].copy() - result_df["Asset ID"] = "No Asset" - result_df["creation_t"] = "No Asset" - result_df["WorkCatego"] = "No Asset" - create_excel_village_nrega_assets( - result_df, - output_file, - writer, - village_gdf[["vill_ID", "vill_name"]], - start_year, - end_year, - ) - return result_df, village_gdf[["vill_ID", "vill_name"]] - - print("Asset points created") - - # 4. Create points GeoDataFrame and match CRS - points_gdf = gpd.GeoDataFrame(points_data, geometry="geometry") - if village_gdf.crs != points_gdf.crs: - points_gdf.set_crs(village_gdf.crs, inplace=True) - - # 5. Find which village each asset belongs to - joined_gdf = gpd.sjoin(points_gdf, village_gdf, how="inner", predicate="within") - - # 6. Get asset + village info - result_df = joined_gdf[ - ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] - ].copy() - - # 7. Add villages that have no assets - villages_with_assets = result_df["vill_ID"].unique() - no_asset_villages = village_gdf[ - ~village_gdf["vill_ID"].isin(villages_with_assets) - ].copy() - no_asset_villages["Asset ID"] = "No Asset" - no_asset_villages["creation_t"] = "No Asset" - no_asset_villages["WorkCatego"] = "No Asset" - - result_df = pd.concat( - [ - result_df, - no_asset_villages[ - ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] - ], - ], - ignore_index=True, - ) - - print("Processing complete") - - # 8. Create Excel file - create_excel_village_nrega_assets( - result_df, - output_file, - writer, - village_gdf[["vill_ID", "vill_name"]], - start_year, - end_year, - ) - - return result_df, village_gdf[["vill_ID", "vill_name"]] - - -def analyze_results(village_asset_count, village_gdf): - villages_with_counts = village_gdf.merge( - village_asset_count, on="vill_ID", how="left" - ) - villages_with_counts["asset_count"] = villages_with_counts["asset_count"].fillna(0) - return villages_with_counts - - -def create_excel_crop_inten(data, output_file, writer, start_year, end_year): - df_data = [] - - features = data["features"] - for feature in features: - properties = feature.get("properties", {}) - uid = properties.get("uid", "Unknown") - row = {"UID": uid, "area_in_ha": properties.get("area_in_ha", 0)} - - # Process each year in range using new key naming convention - for year in range(start_year, end_year + 1): - cropping_key = f"cropping_intensity_{year}" - single_c_key = f"single_cropped_area_{year}" - single_k_key = f"single_kharif_cropped_area_{year}" - single_n_key = f"single_non_kharif_cropped_area_{year}" - doubly_c_key = f"doubly_cropped_area_{year}" - triply_c_key = f"triply_cropped_area_{year}" - - row[f"cropping_intensity_unit_less_{year}-{year + 1}"] = properties.get( - cropping_key, 0 - ) - row[f"single_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - single_c_key, 0 - ) - row[f"single_kharif_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - single_k_key, 0 - ) - row[f"single_non_kharif_cropped_area_in_ha_{year}-{year + 1}"] = ( - properties.get(single_n_key, 0) - ) - row[f"doubly_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - doubly_c_key, 0 - ) - row[f"triply_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - triply_c_key, 0 - ) - - row["sum_area_in_ha"] = properties.get("sum", 0) / 10000 - df_data.append(row) - - # Create and format DataFrame - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - # Round numeric columns - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - # Write to Excel - df.to_excel(writer, sheet_name="croppingIntensity_annual", index=False) - print("Excel file created for cropping intensity.") - - -def create_excel_crop_drou(data, output_file, writer, start_year, end_year): - df_data = [] - - features = data["features"] - for feature in features: - properties = feature.get("properties", {}) - row = {"UID": properties.get("uid", "Unknown ID")} - - for year in range(start_year, end_year + 1): - drlb_key = f"drlb_{year}" - drysp_key = f"drysp_{year}" - kh_cr_key = f"kh_cr_{year}" - m_ons_key = f"m_ons_{year}" - pcr_k_key = f"pcr_k_{year}" - t_wks_key = f"t_wks_{year}" - - # Get drought levels (drlb) and count occurrences - drlb_value = properties.get(drlb_key, "") - row[f"No_Drought_in_weeks_{year}"] = drlb_value.count("0") - row[f"Mild_in_weeks_{year}"] = drlb_value.count("1") - row[f"Moderate_in_weeks_{year}"] = drlb_value.count("2") - row[f"Severe_in_weeks_{year}"] = drlb_value.count("3") - - # Add other properties - row[f"drysp_unit_4_weeks_{year}"] = properties.get(drysp_key, "0") - row[f"kharif_cropped_sqkm_{year}"] = properties.get(kh_cr_key, "0") - row[f"monsoon_onset_{year}"] = properties.get(m_ons_key, "0") - row[f"kharif_cropped_area_percent_{year}"] = properties.get(pcr_k_key, "0") - row[f"total_weeks_{year}"] = properties.get(t_wks_key, "0") - - df_data.append(row) - - # Create DataFrame - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - # Write to Excel - df.to_excel(writer, sheet_name="croppingDrought_kharif", index=False) - print("Excel file created for cropping drought.") - - -def parse_geojson_annual_mws(data): - features = data["features"] - - all_data = defaultdict(lambda: defaultdict(dict)) - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - - for key, value in properties.items(): - if isinstance(key, str) and isinstance(value, str): - if key.startswith("20") and len(key) == 9: - year = key - try: - # Attempt to parse the value as JSON - year_data = json.loads(value.replace("'", '"')) - all_data[uid][year] = year_data - except Exception as e: - print(f"Couldn't parse data for {uid}, {key}: {e}") - - return all_data - - -def create_excel_annual_mws(data, output_file, writer): - df_data = [] - year_columns = ["ET", "RunOff", "G", "DeltaG", "Precipitation", "WellDepth"] - - for uid, years in data.items(): - row = {"UID": uid} - - for year, metrics in years.items(): - start_year = year[:4] - end_year = str(int(start_year) + 1) - formatted_year = f"{start_year}-{end_year}" - - for col in year_columns: - if col == "WellDepth": - column_name = f"{col}_in_m_{formatted_year}" - row[column_name] = metrics.get(col, "N/A") - else: - column_name = f"{col}_in_mm_{formatted_year}" - row[column_name] = metrics.get(col, "N/A") - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="hydrological_annual", index=False) - print("Excel file created for hydrological_annual") - - -def parse_json_seas_mws(file_path): - with open(file_path, "r") as file: - data = json.load(file) - return data - - -def get_season(month): - if month in (3, 4, 5, 6): - return "zaid" - elif month in (7, 8, 9, 10): - return "kharif" - elif month in (11, 12, 1, 2): - return "rabi" - - -def process_feature(feature): - uid = feature["properties"]["uid"] - results = { - "UID": uid, - "precipitation": {"kharif": {}, "rabi": {}, "zaid": {}}, - "et": {"kharif": {}, "rabi": {}, "zaid": {}}, - "runoff": {"kharif": {}, "rabi": {}, "zaid": {}}, - "delta g": {"kharif": {}, "rabi": {}, "zaid": {}}, - "g": {"kharif": {}, "rabi": {}, "zaid": {}}, - } - - variable_mapping = { - "Precipitation": "precipitation", - "ET": "et", - "RunOff": "runoff", - "DeltaG": "delta g", - "G": "g", - } - - for key, value in feature["properties"].items(): - if key.startswith("20"): - try: - date = datetime.strptime(key, "%Y-%m-%d") - year = date.year - month = date.month - season = get_season(month) - if season == "rabi": - current_year = year - 1 if month in (1, 2) else year - elif season == "zaid": - current_year = year - 1 if month in (3, 4, 5, 6) else year - else: - current_year = year - data = json.loads(value) - - for json_var, result_var in variable_mapping.items(): - if json_var in data: - if current_year not in results[result_var][season]: - results[result_var][season][current_year] = 0.0 - results[result_var][season][current_year] += float( - data[json_var] - ) - - except (ValueError, json.JSONDecodeError) as e: - print(f"Error processing data for date {key}: {e}") - continue - return results - - -def create_excel_seas_mws(processed_data, output_file, writer, start_year, end_year): - variables = ["precipitation", "et", "runoff", "delta g", "g"] - seasons = ["kharif", "rabi", "zaid"] - - data = {"UID": []} - for variable in variables: - for year in range(start_year, end_year + 1): - for season in seasons: - end_to_year = year + 1 - column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" - data[column_name] = [] - - for feature_data in processed_data: - data["UID"].append(feature_data["UID"]) - for variable in variables: - for year in range(start_year, end_year + 1): - for season in seasons: - end_to_year = year + 1 - column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" - value = feature_data[variable].get(season, {}).get(year, 0.0) - data[column_name].append(value) - - df = pd.DataFrame(data) - df = df.sort_values("UID") - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="hydrological_seasonal", index=False) - print(f"Excel file created hydrological_seasonal") - - -def create_excel_for_village_boun(old_geojson, writer): - results = [] - - village_data = {} - - for feature in old_geojson["features"]: - properties = feature["properties"] - - # Extract properties - state_census_ID = properties.get("state_cen", None) - dist_census_ID = properties.get("dist_cen", None) - block_census_ID = properties.get("block_cen", None) - village_id = properties.get("vill_ID", None) - village_name = properties.get("vill_name", None) - - # Initialize village data using village_id as the key - if village_id not in village_data: - village_data[village_id] = { - "village_name": village_name, - "TOT_P": 0, - "P_LIT": 0, - "P_SC": 0, - "P_ST": 0, - "state_census_ID": state_census_ID, - "dist_census_ID": dist_census_ID, - "block_census_ID": block_census_ID, - "geometry": feature["geometry"], - } - - village_data[village_id]["TOT_P"] += properties.get("TOT_P", 0) - village_data[village_id]["P_LIT"] += properties.get("P_LIT", 0) - village_data[village_id]["P_SC"] += properties.get("P_SC", 0) - village_data[village_id]["P_ST"] += properties.get("P_ST", 0) - - for village_id, data in village_data.items(): - total_popu = data["TOT_P"] - literacy_rate = data["P_LIT"] * 100 / total_popu if total_popu > 0 else 0.0 - total_SC_popu = data["P_SC"] - total_ST_popu = data["P_ST"] - sc_perce = (data["P_SC"] * 100 / total_popu) if total_popu > 0 else 0.0 - st_perce = (data["P_ST"] * 100 / total_popu) if total_popu > 0 else 0.0 - - results.append( - { - "state_census_ID": data["state_census_ID"], - "dist_census_ID": data["dist_census_ID"], - "block_census_ID": data["block_census_ID"], - "village_id": village_id, - "village_name": data["village_name"], - "total_population_count": total_popu, - "total_SC_population_count": total_SC_popu, - "total_ST_population_count": total_ST_popu, - "literacy_rate_percent": literacy_rate, - "SC_percent": sc_perce, - "ST_percent": st_perce, - } - ) - - results_df = pd.DataFrame(results) - results_df.to_excel(writer, sheet_name="social_economic_indicator", index=False) - - print(f"Excel file created for social_economic_indicator") - - -def download_layers_excel_file(state, district, block): - base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") - state_path = os.path.join(base_path, state.upper()) - district_path = os.path.join(state_path, district.upper()) - filename = f"{district}_{block}.xlsx" - file_path = os.path.join(district_path, filename) - - output_dir = Path(file_path).parent - output_dir.mkdir(parents=True, exist_ok=True) - - if not os.path.exists(file_path): - try: - if not get_vector_layer_geoserver(state, district, block): - return Response( - { - "status": "error", - "message": "Failed to generate vector layer from GeoServer.", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - if not os.path.exists(file_path): - return Response( - {"status": "error", "message": "Failed to generate Excel file."}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - except Exception as e: - return Response( - { - "status": "error", - "message": f"Error during file generation: {str(e)}", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - else: - print(f"Excel file already exists at: {file_path}") - - # Single file reading logic - only written once! - if os.path.exists(file_path): - try: - with open(file_path, "rb") as file: - response = HttpResponse( - file.read(), - content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ) - response["Content-Disposition"] = f"attachment; filename={filename}" - return response - except Exception as e: - return Response( - {"status": "error", "message": f"Error reading file: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - else: - return Response( - {"status": "error", "message": "Failed to locate Excel file."}, - status=status.HTTP_404_NOT_FOUND, - ) - - -def generate_stats_excel_file(state, district, block): - """ - Deletes existing Excel layer file and forces regeneration. - Then returns the newly generated file. - """ - base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") - state_path = os.path.join(base_path, state.upper()) - district_path = os.path.join(state_path, district.upper()) - filename = f"{district}_{block}.xlsx" - file_path = os.path.join(district_path, filename) - - try: - # Create directory if it doesn't exist - output_dir = Path(file_path).parent - output_dir.mkdir(parents=True, exist_ok=True) - - # ALWAYS delete existing file if it exists - if os.path.exists(file_path): - os.remove(file_path) - - from .mws_indicators import generate_mws_data_for_kyl_filters - from .village_indicators import get_generate_filter_data_village - from public_api.views import get_tehsil_json - - get_vector_layer_geoserver(state, district, block) - get_tehsil_json(state, district, block, 1) - generate_mws_data_for_kyl_filters(state, district, block, "json", 1) - get_generate_filter_data_village(state, district, block, 1) - - if not os.path.exists(file_path): - return Response( - { - "status": "error", - "message": "Excel file generation completed but file not found.", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - # Return the newly generated file - with open(file_path, "rb") as file: - response = HttpResponse( - file.read(), - content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ) - response["Content-Disposition"] = f"attachment; filename={filename}" - return response - - except Exception as e: - return Response( - {"status": "error", "message": f"Failed to generate stats excel: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - -def add_sheets_to_excel(state, district, block, sheets): - try: - sheets_to_add = [sheet.strip() for sheet in sheets.split(",") if sheet.strip()] - results = [] - - for sheet in sheets_to_add: - r = get_vector_layer_geoserver(state, district, block, sheet) - results.append(r) - - from .mws_indicators import generate_mws_data_for_kyl_filters - from .village_indicators import get_generate_filter_data_village - - from public_api.views import get_tehsil_json - - get_tehsil_json(state, district, block, 1) - generate_mws_data_for_kyl_filters(state, district, block, "json", 1) - get_generate_filter_data_village(state, district, block, 1) - - successful = [r for r in results if r["status"] == "success"] - failed = [r for r in results if r["status"] == "failed"] - - response_data = { - "status": "success" if successful else "failed", - "message": f"Stats data added info. {len(successful)} successful, {len(failed)} failed.", - } - - return response_data - - except Exception as e: - return { - "status": "error", - "message": str(e), - } +import os +import requests, json +from django.http import HttpResponse, Http404 +from rest_framework.response import Response +import pandas as pd +import geopandas as gpd +from collections import defaultdict +from datetime import datetime +from nrm_app.settings import GEOSERVER_URL, EXCEL_PATH +import numpy as np +from shapely.geometry import Point, shape +from .models import LayerInfo +from django.http import HttpResponse +from rest_framework import status +from pathlib import Path + + +def fetch_layers_for_excel_generation(): + """ + Fetch all vector layers where `excel_to_be_generated` is True. + """ + layers = LayerInfo.objects.filter( + layer_type="vector", excel_to_be_generated=True + ).values("layer_name", "workspace", "start_year", "end_year") + return list(layers) + + +def get_url(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 get_vector_layer_geoserver(state, district, block, specific_sheets=None): + print(f"Generate Stats excel for {state}_{district}_{block}") + base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") + district_path = os.path.join( + base_path, state.replace(" ", "_").upper(), district.replace(" ", "_").upper() + ) + os.makedirs(district_path, exist_ok=True) + xlsx_file = os.path.join(district_path, f"{district}_{block}.xlsx") + + workspaces_to_process = specific_sheets + + # Handle existing sheets when adding specific workspaces + results = [] + file_exists = os.path.exists(xlsx_file) + mode = "a" if file_exists else "w" + + # Use append mode with if_sheet_exists='replace' + with pd.ExcelWriter( + xlsx_file, + engine="openpyxl", + mode=mode, + if_sheet_exists="replace" if mode == "a" else None, + ) as writer: + for layer in fetch_layers_for_excel_generation(): + workspace = layer["workspace"] + + if workspaces_to_process and workspace not in workspaces_to_process: + continue + + start_year = layer.get("start_year") + end_year = layer.get("end_year") + + if "{district}" in layer["layer_name"] and "{block}" in layer["layer_name"]: + layer_name = layer["layer_name"].format(district=district, block=block) + else: + layer_name = layer["layer_name"] + + print(f"Processing layer: {layer_name}") + print(f"Workspace for the layer is: {workspace}") + + geojson_data = None + try: + url = get_url(workspace, layer_name) + response = requests.get(url) + response.raise_for_status() + geojson_data = response.json() + except requests.exceptions.RequestException as e: + print(f"Failed to fetch data for {layer_name}: {e}") + results.append( + {"layer": layer_name, "status": "failed", "workspace": workspace} + ) + continue + + # Process the data based on workspace + if workspace == "terrain": + create_excel_for_terrain(geojson_data, xlsx_file, writer) + elif ( + workspace == "terrain_lulc" + and layer_name == f"{district}_{block}_lulc_slope" + ): + create_excel_for_terrain_lulc_slope(geojson_data, xlsx_file, writer) + elif ( + workspace == "terrain_lulc" + and layer_name == f"{district}_{block}_lulc_plain" + ): + create_excel_for_terrain_lulc_plain(geojson_data, xlsx_file, writer) + elif workspace == "swb": + create_excel_for_swb( + geojson_data, xlsx_file, writer, start_year, end_year + ) + create_excel_for_mws_intersect_swb( + geojson_data, writer, district, block + ) + elif workspace == "nrega_assets": + mws_lay_name = f"deltaG_well_depth_{district}_{block}" + mws_file_url = get_url("mws_layers", mws_lay_name) + + try: + response = requests.get(mws_file_url) + response.raise_for_status() + mws_geojson_datas = response.json() + except requests.exceptions.RequestException as e: + print(f"Failed to fetch MWS data: {e}") + continue + + create_excel_for_nrega_assets( + geojson_data, + mws_geojson_datas, + xlsx_file, + writer, + start_year, + end_year, + ) + try: + fetch_village_asset_count( + state, district, block, writer, xlsx_file, start_year, end_year + ) + except Exception as e: + print("Exception as e", str(e)) + + elif workspace == "crop_intensity": + create_excel_crop_inten( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "drought": + create_excel_crop_drou( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif ( + workspace == "mws_layers" + and layer_name == f"deltaG_well_depth_{district}_{block}" + ): + parsed_data_annual_mws = parse_geojson_annual_mws(geojson_data) + create_excel_annual_mws(parsed_data_annual_mws, xlsx_file, writer) + try: + create_excel_mws_inters_villages( + geojson_data, xlsx_file, writer, district, block + ) + except Exception as e: + print("Exception", str(e)) + elif ( + workspace == "mws_layers" + and layer_name == f"deltaG_fortnight_{district}_{block}" + ): + processed_data = [ + process_feature(feature) for feature in geojson_data["features"] + ] + create_excel_seas_mws( + processed_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "panchayat_boundaries": + create_excel_for_village_boun(geojson_data, writer) + elif workspace == "drought_causality": + create_excel_for_drought_causality( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "ccd": + create_excel_for_ccd( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "canopy_height": + create_excel_for_ch( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "tree_overall_ch": + create_excel_for_overall_tree_change(geojson_data, xlsx_file, writer) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Afforestation" + ): + create_excel_chan_detection_afforestation( + geojson_data, xlsx_file, writer + ) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_CropIntensity" + ): + create_excel_chan_detection_cropintensity( + geojson_data, xlsx_file, writer + ) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Deforestation" + ): + create_excel_chan_detection_deforestation( + geojson_data, xlsx_file, writer + ) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Degradation" + ): + create_excel_chan_detection_degradation(geojson_data, xlsx_file, writer) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Urbanization" + ): + create_excel_chan_detection_urbanization( + geojson_data, xlsx_file, writer + ) + elif workspace == "restoration": + create_excel_for_restoration(geojson_data, xlsx_file, writer) + elif workspace == "aquifer": + create_excel_for_aquifer(geojson_data, writer) + elif workspace == "soge": + create_excel_for_soge(geojson_data, xlsx_file, writer) + elif workspace == "lcw": + create_excel_for_lcw(geojson_data, writer) + elif workspace == "agroecological": + create_excel_for_agroecological(geojson_data, writer) + elif workspace == "factory_csr": + create_excel_for_factory_csr(geojson_data, writer) + elif workspace == "green_credit": + create_excel_for_green_credit(geojson_data, writer) + elif workspace == "mining": + create_excel_for_mining(geojson_data, writer) + elif workspace == "stream_order": + create_excel_for_stream_order(geojson_data, writer) + elif workspace == "mws_connectivity": + create_excel_for_mws_connectivity(geojson_data, writer) + elif workspace == "mws": + create_excel_for_mws(geojson_data, writer) + elif workspace == "facilities_proximity": + create_excel_for_facilities(geojson_data, writer) + elif workspace == "dem": + create_excel_for_dem(geojson_data, writer) + elif workspace == "canal": + create_excel_for_canal(geojson_data, writer) + elif workspace == "river": + create_excel_for_river(geojson_data, writer) + elif workspace == "lulc_vector": + create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) + elif workspace == "drainage_density": + create_excel_for_drainage_density(geojson_data, writer) + elif workspace == "antyodaya_2020": + create_excel_for_antyodaya_20(geojson_data, writer) + elif workspace == "livestocks": + create_excel_for_livestock(geojson_data, writer) + + results.append( + {"layer": layer_name, "status": "success", "workspace": workspace} + ) + + return results + + +def create_excel_for_livestock(data, writer): + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Columns to exclude + exclude_cols = ["state_name","district_name","TEHSIL"] + df = df.drop(columns=exclude_cols, errors="ignore") + + # Keep important columns first if they exist + first_cols = [c for c in ["pc11_village_id", "NAME"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + df.to_excel(writer, sheet_name="livestock", index=False) + print("Excel file created for livestock") + except Exception as e: + print(f"Error in getting livestock data: {e}") + + +def create_excel_for_antyodaya_20(data, writer): + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Keep important columns first if they exist + first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + # Round numeric columns + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="antyodaya", index=False) + print("Excel file created for antyodaya") + except Exception as e: + print(f"Error in getting antyodaya data: {e}") + + +def create_excel_for_drainage_density(data, writer): + import ast + print("Inside create_excel_for Drainage Density") + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), + "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), + "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="drainage_density", index=False) + print("Excel file created for Drainage Density") + + +def create_excel_for_lulc_vector(data, writer, start_year, end_year): + df_data = [] + features = data["features"] + years = list(range(start_year, end_year + 1)) + + classes = { + "barrenland": ("barrenland", "barrenla"), + "built_up_area": ("built-up_a", "built-up"), + "cropland": ("cropland_a", "cropland"), + "double_crop": ("doubly_cro", "doubly_c"), + "triple_crop": ("triply_cro", "triply_c"), + "tree_forest": ("tree_fores", "tree_for"), + "shrub_scrub": ("shrub_scru", "shrub_sc"), + "single_kharif": ("single_kha", "single_k"), + "single_non_kharif": ("single_non", "single_n"), + "k_water": ("k_water_ar", "k_water_"), + "kr_water": ("kr_water_a", "kr_water"), + "krz_water": ("krz_water_", "krz_wate"), + } + + def get_key(base_key, trunc_prefix, idx): + """Derive the property key for a given year index.""" + if idx == 0: + return base_key + return f"{trunc_prefix}_{idx}" + + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "sum_in_ha": (properties.get("sum") or 0) / 10000, + } + + for idx, year in enumerate(years): + for class_name, (base_key, trunc_prefix) in classes.items(): + key = get_key(base_key, trunc_prefix, idx) + row[f"{class_name}_in_ha_{year}"] = properties.get(key, 0) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="lulc_vector", index=False) + print("Excel file created for lulc vector") + + +def create_excel_for_canal(data, writer): + print("Inside create_excel_for Canal") + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "project_name": properties.get("prjname", ""), + "canal_code": properties.get("cancode", ""), + "canal_name": properties.get("canname", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="canal", index=False) + print("Excel file created for canal") + except Exception as e: + print("Canal Layer not found :: ", e) + + +def create_excel_for_river(data, writer): + print("Inside create_excel_for River") + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "river_name": properties.get("rivname", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="river", index=False) + print("Excel file created for river") + except Exception as e: + print("River Layer not found :: ", e) + + +def create_excel_for_dem(data, writer): + print("Inside create_excel_for DEM") + + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties.get("uid", ""), + "min_elevation_in_m": properties.get("min_elevation", ""), + "max_elevation_in_m": properties.get("max_elevation", ""), + "mean_elevation_in_m": properties.get("mean_elevation", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="dem", index=False) + print("Excel file created for dem") + + +def create_excel_for_mws_intersect_swb(swb_geojson, writer, district, block): + print("Inside create_excel_for_mws_intersect_swb") + + # --- Fetch MWS layer --- + mws_layer_name = f"mws_{district}_{block}" + mws_data_url = get_url("mws", mws_layer_name) + + mws_response = requests.get(mws_data_url) + if mws_response.status_code != 200: + print(f"Error fetching MWS data: {mws_response.status_code}") + return + + mws_geojson = mws_response.json() + + def calculate_intersection_area(geom1, geom2): + if geom1.intersects(geom2): + return geom1.intersection(geom2).area + return 0 + + rows = [] + + for mws_feature in mws_geojson["features"]: + mws_props = mws_feature["properties"] + mws_uid = mws_props.get("uid") + mws_geom = shape(mws_feature["geometry"]) + + for swb_feature in swb_geojson["features"]: + swb_props = swb_feature["properties"] + swb_geom = shape(swb_feature["geometry"]) + + intersection_area = calculate_intersection_area(mws_geom, swb_geom) + + if intersection_area > 0: + # waterbodies centroid calculation + centroid = swb_geom.centroid + lon, lat = centroid.x, centroid.y + + rows.append( + { + "UID": mws_uid, + "SWB_UID": swb_props.get("UID"), + "Waterbodies_name": swb_props.get("water_body_name"), + "Latitude": lat, + "Longitude": lon, + } + ) + + df = pd.DataFrame(rows) + + if not df.empty: + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="mws_intersect_swb", index=False) + print("Excel sheet 'mws_intersect_swb' created successfully") + + +def create_excel_for_facilities(data, writer): + try: + features = data["features"] + df_data = [feature["properties"] for feature in features] + + df = pd.DataFrame(df_data) + + first_cols = ["censuscode2011", "censusname"] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] + df.rename( + columns={ + col: f"{col}_in_km" + for col in df.columns + if col not in exclude_cols + }, + inplace=True + ) + + # Write to Excel + df.to_excel(writer, sheet_name="facilities_proximity", index=False) + + print("Excel file created for facilities_proximity") + except Exception as e: + print("facilities_proximity Layer not found :: ", e) + + + +def create_excel_for_mws(data, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "watershed_code": properties.get("wsconc", ""), + "basin_code": properties.get("bacode", ""), + "sub_basin_code": properties.get("sbcode", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mws", index=False) + print("Excel file created for mws") + + +def create_excel_for_mws_connectivity(data, writer): + """ + direction_map = { + '0': 'No Direction', + '1': 'North-East', + '2': 'East', + '3': 'South-East', + '4': 'South', + '5': 'South-West', + '6': 'West', + '7': 'North-West', + '8': 'North' + } + """ + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "direction": properties["direction"], + "downstream_mws": properties["downstream"], + "upstream_mws": properties["upstream"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mws_connectivity", index=False) + print("Excel file created for mws_connectivity") + + +def create_excel_for_stream_order(data, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "order_1_area_percent": properties["1"], + "order_2_area_percent": properties["2"], + "order_3_area_percent": properties["3"], + "order_4_area_percent": properties["4"], + "order_5_area_percent": properties["5"], + "order_6_area_percent": properties["6"], + "order_7_area_percent": properties["7"], + "order_8_area_percent": properties["8"], + "order_9_area_percent": properties["9"], + "order_10_area_percent": properties["10"], + "order_11_area_percent": properties["11"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="stream_order", index=False) + print("Excel file created for stream order") + + +def create_excel_for_mining(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["company_na"], + "proposal": properties["proposal"], + "sector_moefcc": properties["sector_moe"], + "village": properties["village"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mining", index=False) + print("Excel file created for mining") + except Exception as e: + print("Mining Layer not found :: ", e) + + +def create_excel_for_green_credit(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["division"], + # "parcel_id": properties["parcel_id"], + "land_info": properties["land_info"], + "kml_url": properties["kml_url"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="green_credit", index=False) + print("Excel file created for green_credit") + except Exception as e: + print("green credit Layer not found :: ", e) + + +def create_excel_for_factory_csr(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "Company_Name": properties["COMPANY NA"], + "ADDRESS": properties["ADDRESS"], + "LOCATION T": properties["LOCATION T"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="factory_csr", index=False) + print("Excel file created for factory_csr") + except Exception as e: + print("factory csr Layer not found :: ", e) + + +def create_excel_for_agroecological(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "organization_name": properties["organization_name"], + "organization_type": properties["organization_type"], + "created_at": properties["created_at"], + "contact_person": properties["contact_person"], + "email": properties["email"], + "domains": properties["domains"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="agroecological", index=False) + print("Excel file created for agroecological") + except Exception as e: + print("agroecological Layer not found :: ", e) + + +def create_excel_for_lcw(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "title_of_conflict": properties["Title of Conflict"], + "link_to_conflict": properties["Link to conflict"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="lcw_conflict", index=False) + print("Excel file created for lcw_conflict") + except Exception as e: + print("lcw Layer not found :: ", e) + + +def create_excel_for_soge(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "soge_dev_percent": properties["sgw_dev_pe"], + "class_code": properties["code"], + "class_name": properties["class"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="soge_vector", index=False) + print(f"Excel file created for soge_vector") + + +def create_excel_for_aquifer(data, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + if properties.get("aquifer_class", "") == "Hard-Rock": + aquifer_class = "Hard Rock" + else: + aquifer_class = "Alluvium" + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "aquifer_class": aquifer_class, + "principle_aq_Alluvium_percent": properties.get( + "principle_aq_Alluvium_percent", "0" + ), + "principle_aq_Banded Gneissic Complex_percent": properties.get( + "principle_aq_Banded Gneissic Complex_percent", "0" + ), + "principle_aq_Basalt_percent": properties.get( + "principle_aq_Basalt_percent", "0" + ), + "principle_aq_Charnockite_percent": properties.get( + "principle_aq_Charnockite_percent", "0" + ), + "principle_aq_Gneiss_percent": properties.get( + "principle_aq_Gneiss_percent", "0" + ), + "principle_aq_Granite_percent": properties.get( + "principle_aq_Granite_percent", "0" + ), + "principle_aq_Intrusive_percent": properties.get( + "principle_aq_Intrusive_percent", "0" + ), + "principle_aq_Khondalite_percent": properties.get( + "principle_aq_Khondalite_percent", "0" + ), + "principle_aq_Laterite_percent": properties.get( + "principle_aq_Laterite_percent", "0" + ), + "principle_aq_Limestone_percent": properties.get( + "principle_aq_Limestone_percent", "0" + ), + "principle_aq_Quartzite_percent": properties.get( + "principle_aq_Quartzite_percent", "0" + ), + "principle_aq_Sandstone_percent": properties.get( + "principle_aq_Sandstone_percent", "0" + ), + "principle_aq_Schist_percent": properties.get( + "principle_aq_Schist_percent", "0" + ), + "principle_aq_Shale_percent": properties.get( + "principle_aq_Shale_percent", "0" + ), + "principle_aq_None_percent": properties.get( + "principle_aq_None_percent", "0" + ), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + # Round numeric values + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="aquifer_vector", index=False) + print("Excel file created for aquifer_vector") + + +def create_excel_for_restoration(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "wide_scale_restoration_area_in_ha": properties["Wide-scale"], + "protection_area_in_ha": properties["Protection"], + "mosaic_restoration_area_in_ha": properties["Mosaic Res"], + "excluded_areas_in_ha": properties["Excluded A"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="restoration_vector", index=False) + print(f"Excel file created for restoration_vector") + + +def create_excel_for_overall_tree_change(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "afforestation_area_in_ha": properties["Afforestation"], + "deforestation_area_in_ha": properties["Deforestation"], + "degradation_area_in_ha": properties["Degradation"], + "improvement_area_in_ha": properties["Improvement"], + "missing_data_in_ha": properties["Missing Data"], + "no_change_area_in_ha": properties["No_Change"], + "partially_degraded_area_in_ha": properties["Partially_Degraded"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="overall_tree_change", index=False) + print(f"Excel file created for overall_tree_change") + + +def create_excel_for_ccd(data, xlsx_file, writer, start_year, end_year): + df_data = [] + features = data["features"] + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + } + + for year in range(start_year, end_year + 1): + row["high_density_area_in_ha_" + str(year)] = properties.get( + "High_Density_" + str(year), None + ) + row["low_density_area_in_ha_" + str(year)] = properties.get( + "Low_Density_" + str(year), None + ) + row["missing_data_area_in_ha_" + str(year)] = properties.get( + "Missing_Data_" + str(year), None + ) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="Canopy_Cover_Density", index=False) + print(f"Excel file created for Canopy_Cover_Density") + + +def create_excel_for_ch(data, xlsx_file, writer, start_year, end_year): + df_data = [] + features = data["features"] + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + } + + for year in range(start_year, end_year + 1): + row["short_trees_area_in_ha_" + str(year)] = properties.get( + "Short_Trees_" + str(year), None + ) + row["medium_trees_area_in_ha_" + str(year)] = properties.get( + "Medium_Height_Trees_" + str(year), None + ) + row["tall_trees_area_in_ha_" + str(year)] = properties.get( + "Tall_Trees_" + str(year), None + ) + row["missing_data_area_in_ha_" + str(year)] = properties.get( + "Missing_Data_" + str(year), None + ) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="Canopy_height", index=False) + print(f"Excel file created for Canopy_height") + + +def create_excel_for_drought_causality(data, xlsx_file, writer, start_year, end_year): + df_data = [] + features = data["features"] + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + } + + for year in range(start_year, end_year + 1): + row["severe_moderate_drought_causality_" + str(year)] = properties.get( + "se_mo_" + str(year), None + ) + row["mild_drought_causality_" + str(year)] = properties.get( + "mild_" + str(year), None + ) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="drought_causality", index=False) + print(f"Excel file created for drought_causality") + + +def create_excel_chan_detection_afforestation(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "barren_to_forest_area_in_ha": properties.get("ba_fo", None), + "built_up_to_forest_area_in_ha": properties.get("bu_fo", None), + "farm_to_forest_area_in_ha": properties.get("fa_fo", None), + "forest_to_forest_area_in_ha": properties.get("fo_fo", None), + "scrub_land_to_forest_area_in_ha": properties.get("sc_fo", None), + "total_afforestation_area_in_ha": properties.get("total_aff", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_afforestation", index=False) + print(f"Excel file created for change_detection_afforestation") + + +def create_excel_chan_detection_cropintensity(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "single_to_single_area_in_ha": properties.get("si_si", None), + "single_to_double_area_in_ha": properties.get("si_do", None), + "single_to_triple_area_in_ha": properties.get("si_tr", None), + "double_to_single_area_in_ha": properties.get("do_si", None), + "double_to_double_area_in_ha": properties.get("do_do", None), + "double_to_triple_area_in_ha": properties.get("do_tr", None), + "triple_to_single_area_in_ha": properties.get("tr_si", None), + "triple_to_double_area_in_ha": properties.get("tr_do", None), + "triple_to_triple_area_in_ha": properties.get("tr_tr", None), + "total_change_crop_intensity_area_in_ha": properties.get( + "total_chan", properties.get("total_change", None) + ), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_cropintensity", index=False) + print(f"Excel file created for change_detection_cropintensity") + + +def create_excel_chan_detection_deforestation(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "forest_to_barren_area_in_ha": properties.get("fo_ba", None), + "forest_to_built_up_area_in_ha": properties.get("fo_bu", None), + "forest_to_farm_area_in_ha": properties.get("fo_fa", None), + "forest_to_forest_area_in_ha": properties.get("fo_fo", None), + "forest_to_scrub_land_area_in_ha": properties.get("fo_sc", None), + "total_deforestation_area_in_ha": properties.get("total_def", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_deforestation", index=False) + print(f"Excel file created for change_detection_deforestation") + + +def create_excel_chan_detection_degradation(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "farm_to_barren_area_in_ha": properties.get("f_ba", None), + "farm_to_built_up_area_in_ha": properties.get("f_bu", None), + "farm_to_farm_area_in_ha": properties.get("f_f", None), + "farm_to_scrub_land_area_in_ha": properties.get("f_sc", None), + "total_degradation_area_in_ha": properties.get("total_deg", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_degradation", index=False) + print(f"Excel file created for change_detection_degradation") + + +def create_excel_chan_detection_urbanization(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "barren_shrub_to_built_up_area_in_ha": properties.get("b_bu", None), + "built_up_to_built_up_area_in_ha": properties.get("bu_bu", None), + "tree_farm_to_built_up_area_in_ha": properties.get("tr_bu", None), + "water_to_built_up_area_in_ha": properties.get("w_bu", None), + "total_urbanization_area_in_ha": properties.get("total_urb", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_urbanization", index=False) + print(f"Excel file created for change_detection_urbanization") + + +def create_excel_mws_inters_villages(mws_geojson, xlsx_file, writer, district, block): + print("Inside create_excel_mws_inters_villages") + admin_layer_name = district + "_" + block + admin_file_url = get_url("panchayat_boundaries", admin_layer_name) + + response = requests.get(admin_file_url) + if response.status_code != 200: + print(f"Error fetching data: {response.status_code}") + return + + village_geojson = response.json() + + def calculate_intersection_area_ha(village_geom, mws_geom, mws_area_ha): + if village_geom.intersects(mws_geom): + intersection = village_geom.intersection(mws_geom) + if mws_geom.area == 0: + return 0 + # ratio of intersection to full MWS geometry area (both in degrees²) + ratio = intersection.area / mws_geom.area + return ratio * mws_area_ha + return 0 + + mws_villages_dict = {} + + for mws_feature in mws_geojson["features"]: + mws_uid = mws_feature["properties"]["uid"] + mws_area = mws_feature["properties"]["area_in_ha"] + mws_geom = shape(mws_feature["geometry"]) + village_ids = set() + village_details = {} + + for village_feature in village_geojson["features"]: + village_id = village_feature["properties"]["vill_ID"] + if village_id == 0: + continue + + village_geom = shape(village_feature["geometry"]) + area_intersected_ha = calculate_intersection_area_ha( + village_geom, mws_geom, mws_area + ) + if area_intersected_ha > 0: + village_ids.add(village_id) + percentage_of_area = ( + (area_intersected_ha / mws_area) * 100 if mws_area > 0 else 0 + ) + village_details[village_id] = { + "area_intersect": round(area_intersected_ha, 2), + "percentage_of_area": round(percentage_of_area, 2), + } + + if village_ids: + mws_villages_dict[mws_uid] = { + "area_in_ha": mws_area, + "village_ids": list(village_ids), + "village_details": village_details, + } + + data = [ + { + "MWS UID": mws_uid, + "area_in_ha": values["area_in_ha"], + "Village IDs": values["village_ids"], + "Village Details": values["village_details"], + } + for mws_uid, values in mws_villages_dict.items() + ] + + df = pd.DataFrame(data) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="mws_intersect_villages", index=False) + print("The data has been saved to mws_intersect_villages") + + +# def create_excel_village_inters_mwss(mws_geojson, xlsx_file, writer, district, block): +# print("Inside create_excel_village_inters_mwss") +# admin_layer_name = district + '_' + block +# admin_file_url = get_url('panchayat_boundaries', admin_layer_name) + +# response = requests.get(admin_file_url) +# if response.status_code != 200: +# print(f"Error fetching data: {response.status_code}") +# return +# village_geojson = response.json() + +# def calculate_intersection_area(village_geom: BaseGeometry, mws_geom: BaseGeometry) : +# try: +# # Check for empty geometries +# if village_geom.is_empty or mws_geom.is_empty: +# return 0.0 + +# # Fix invalid geometries if needed +# if not village_geom.is_valid: +# village_geom = village_geom.buffer(0) +# if not mws_geom.is_valid: +# mws_geom = mws_geom.buffer(0) + +# # Calculate intersection +# if village_geom.intersects(mws_geom): +# intersection = village_geom.intersection(mws_geom) +# return intersection.area if not intersection.is_empty else 0.0 + +# return 0.0 + +# except Exception as e: +# print(f"Error calculating intersection area: {e}") +# return 0.0 + + +# data = [] + +# processed_villages = set() + +# for village_feature in village_geojson['features']: +# village_id = village_feature['properties']['vill_ID'] +# village_name = village_feature['properties']['vill_name'] + +# village_key = (village_id, village_name) + +# if village_key in processed_villages: +# continue +# processed_villages.add(village_key) + +# village_geom = shape(village_feature['geometry']) + +# mws_uids = [] +# intersection_areas = [] + +# for mws_feature in mws_geojson['features']: +# mws_geom = shape(mws_feature['geometry']) +# area_intersected = calculate_intersection_area(village_geom, mws_geom) +# if area_intersected > 0: +# mws_uids.append(mws_feature['properties']['uid']) +# intersection_areas.append(area_intersected) + +# data.append({ +# 'Village ID': village_id, +# 'Village Name': village_name, +# 'MWS UIDs': mws_uids, +# }) + +# df = pd.DataFrame(data) + +# ## for roundoff all numeric value upto 2 decimal +# numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns +# df[numeric_cols] = df[numeric_cols].round(2) + +# df.to_excel(writer, sheet_name='village_intersect_mwss', index=False) +# print("Excel created for village_intersect_mwss") + + +def create_excel_for_terrain(data, output_file, writer): + print("Inside create_excel_for_terrain function") + df_data = [] + + terrain_description = { + 0: "Broad Sloppy and Hilly", + 1: "Mostly Plains", + 2: "Mostly Hills and Valleys", + 3: "Broad Plains and Slopes", + } + + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "terrain_cluster_id": properties["terrainClu"], + "terrain_description": terrain_description.get(properties["terrainClu"]), + "hill_slope_area_percent": properties["hill_slope"], + "plain_area_percent": properties["plain_area"], + "ridge_area_percent": properties["ridge_area"], + "slopy_area_percent": properties["slopy_area"], + "valley_area_percent": properties["valley_are"], + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="terrain", index=False) + print(f"Excel file created for terrain vector") + + +def create_excel_for_terrain_lulc_slope(data, output_file, writer): + df_data = [] + + terrain_description = { + 0: "Broad Sloppy and Hilly", + 1: "Mostly Plains", + 2: "Mostly Hills and Valleys", + 3: "Broad Plains and Slopes", + } + + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "terrain_cluster_id": properties["terrain_cl"], + "terrain_description": terrain_description.get(properties["terrain_cl"]), + "cluster_name": properties["clust_name"], + "barren_area_percent": properties["barren"], + "forests_area_percent": properties["forests"], + "shrub_scrubs_area_percent": properties["shrub_scru"], + "single_kharif_area_percent": properties["sing_khari"], + "single_non_kharif_area_percent": properties["sing_non_k"], + "double_cropping_area_percent": properties["double"], + "triple_cropping_area_percent": properties["triple"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="terrain_lulc_slope", index=False) + print("Excel file created for terrain_lulc_slope") + + +def create_excel_for_terrain_lulc_plain(data, output_file, writer): + df_data = [] + + terrain_description = { + 0: "Broad Sloppy and Hilly", + 1: "Mostly Plains", + 2: "Mostly Hills and Valleys", + 3: "Broad Plains and Slopes", + } + + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "terrain_cluster_id": properties["terrain_cl"], + "terrain_description": terrain_description.get(properties["terrain_cl"]), + "cluster_name": properties["clust_name"], + "barren_area_percent": properties["barren"], + "forests_area_percent": properties["forest"], + "shrub_scrubs_area_percent": properties["shrubs_scr"], + "single_non_kharif_area_percent": properties["sing_non_k"], + "single_kharif_area_percent": properties["sing_crop"], + "double_cropping_area_percent": properties["double_cro"], + "triple_cropping_area_percent": properties["triple_cro"], + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="terrain_lulc_plain", index=False) + print("Excel file created for terrain_lulc_plain") + + +def create_excel_for_swb(data, output_file, writer, start_year, end_year): + df_data = [] + features = data.get("features", []) + + for feature in features: + properties = feature.get("properties", {}) + uid = properties.get("MWS_UID", "Unknown") + + def calculate_area(base_area, percentage): + if base_area == 0 or percentage == 0: + return 0 + return base_area * (percentage / 100) + + parts = uid.split("_") + num_uid_parts_is = [ + f"{parts[i]}_{parts[i+1]}" for i in range(0, len(parts) - 1, 2) + ] + if len(parts) % 2 == 1: # Check for an unpaired last part + num_uid_parts_is.append(parts[-1]) + + # Generate years dynamically based on start_year and end_year + years = range(start_year, end_year) + + for num_uid_part in num_uid_parts_is: + row = {"UID": num_uid_part} + + for year in years: + short_year = f"{str(year)[-2:]}-{str(year+1)[-2:]}" + + # Construct keys dynamically using the shortened year format + total_area_key = f"area_{short_year}" + kharif_key = f"k_{short_year}" + rabi_key = f"kr_{short_year}" + zaid_key = f"krz_{short_year}" + + # Get values from properties + total_area = properties.get(total_area_key, 0) + kharif_percentage = properties.get(kharif_key, 0) + rabi_percentage = properties.get(rabi_key, 0) + zaid_percentage = properties.get(zaid_key, 0) + + # Calculate areas + row[f"total_area_in_ha_{year}-{year+1}"] = total_area / len( + num_uid_parts_is + ) + row[f"kharif_area_in_ha_{year}-{year+1}"] = calculate_area( + total_area, kharif_percentage + ) / len(num_uid_parts_is) + row[f"rabi_area_in_ha_{year}-{year+1}"] = calculate_area( + total_area, rabi_percentage + ) / len(num_uid_parts_is) + row[f"zaid_area_in_ha_{year}-{year+1}"] = calculate_area( + total_area, zaid_percentage + ) / len(num_uid_parts_is) + + # Add total SWB area + row["total_swb_area_in_ha"] = properties.get("area_ored", 0) / len( + num_uid_parts_is + ) + df_data.append(row) + + df = pd.DataFrame(df_data) + agg_dict = {col: "sum" for col in df.columns if col != "UID"} + grouped_df = df.groupby("UID").agg(agg_dict).reset_index() + + df = grouped_df.sort_values(["UID"]) + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="surfaceWaterBodies_annual", index=False) + print("Excel file created for surfaceWaterBodies_annual") + + +def create_excel_for_nrega_assets( + nrega_data, mws_data, output_file, writer, start_year, end_year +): + workCategoryMapping = { + "SWC - Landscape level impact": "Soil and water conservation", + "Agri Impact - HH, Community": "Land restoration", + "Plantation": "Plantations", + "Irrigation - Site level impact": "Irrigation on farms", + "Irrigation Site level - Non RWH": "Other farm works", + "Household Livelihood": "Off-farm livelihood assets", + "Others - HH, Community": "Community assets", + } + + mws = gpd.GeoDataFrame.from_features(mws_data["features"]) + nrega = gpd.GeoDataFrame.from_features(nrega_data["features"]) + + # Set CRS if available in JSON + if "crs" in mws_data: + mws.set_crs(mws_data["crs"]["properties"]["name"], inplace=True) + if "crs" in nrega_data: + nrega.set_crs(nrega_data["crs"]["properties"]["name"], inplace=True) + + joined = gpd.sjoin(nrega, mws, how="inner", predicate="within") + counts = {} + + df_data = [] + valid_years = range(start_year, end_year) + + date_formats = [ + "%d-%b-%y %H:%M:%S.%f", + "%d-%b-%y %H:%M:%S", + "%d-%m-%y %H:%M:%S.%f", + "%d-%m-%y %H:%M:%S", + "%d-%b-%Y %H:%M:%S.%f", + "%d-%b-%Y %H:%M:%S", + "%d-%m-%Y %H:%M:%S.%f", + "%d-%m-%Y %H:%M:%S", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S", + "%Y/%m/%d %H:%M:%S.%f", + "%Y/%m/%d %H:%M:%S", + "%Y-%m-%dT%H:%M:%SZ", + ] + + for _, row in joined.iterrows(): + creation_t = row["creation_t"] + work_category = row["WorkCatego"] + mws_id = row["uid"] + + if isinstance(creation_t, pd.Timestamp): + creation_t = creation_t.strftime("%d-%m-%Y %H:%M:%S") + + date_obj = None + for date_format in date_formats: + try: + date_obj = datetime.strptime(creation_t, date_format) + break + except ValueError: + continue + + if date_obj is None: + continue + + year = date_obj.year + if year < 100: + year += 2000 + + if year not in valid_years: + continue + + category = workCategoryMapping.get(work_category, "Others - HH, Community") + + if mws_id not in counts: + counts[mws_id] = { + year: {cat: 0 for cat in workCategoryMapping.values()} + for year in range(start_year, end_year) + } + + if category not in counts[mws_id][year]: + counts[mws_id][year][category] = 0 + counts[mws_id][year][category] += 1 + + for mws_id, year_data in counts.items(): + row = {"mws_id": mws_id} + for year, categories in year_data.items(): + for category in workCategoryMapping.values(): + count = categories.get(category, 0) + row[f"{category}_count_{year}"] = count + df_data.append(row) + + if not df_data: + print("No data was collected for the DataFrame.") + else: + print(f"Collected {len(df_data)} rows of data for the DataFrame.") + + if df_data: + df = pd.DataFrame(df_data) + df.to_excel(writer, sheet_name="nrega_annual", index=False) + print("Excel file created for nrega_annual") + return "successfully created" + else: + print("No data available to write to Excel.") + + +def create_excel_village_nrega_assets( + result_df, output_file, writer, all_villages_df, start_year, end_year +): + workCategoryMapping = { + "SWC - Landscape level impact": "Soil and water conservation", + "Agri Impact - HH, Community": "Land restoration", + "Plantation": "Plantations", + "Irrigation - Site level impact": "Irrigation on farms", + "Irrigation Site level - Non RWH": "Other farm works", + "Household Livelihood": "Off-farm livelihood assets", + "Others - HH, Community": "Community assets", + } + + # start_year, end_year = 2017, 2022 + year_range = range(start_year, end_year + 1) + + # Initialize all-zero DataFrame for all villages + rows = [] + for _, row in all_villages_df.iterrows(): + base_row = {"vill_id": row["vill_ID"], "vill_name": row["vill_name"]} + for year in year_range: + for cat in workCategoryMapping.values(): + base_row[f"{cat}_count_{year}"] = 0 + rows.append(base_row) + + final_df = pd.DataFrame(rows) + + # Fill counts from assets + for _, row in result_df.iterrows(): + creation_t = row["creation_t"] + try: + date_obj = pd.to_datetime(creation_t, errors="coerce") + if pd.isnull(date_obj): + continue + except: + continue + + year = date_obj.year + if year not in year_range: + continue + + category = workCategoryMapping.get(row["WorkCatego"]) + if not category: + continue + + mask = (final_df["vill_id"] == row["vill_ID"]) & ( + final_df["vill_name"] == row["vill_name"] + ) + col_name = f"{category}_count_{year}" + final_df.loc[mask, col_name] += 1 + + # Sort columns for clean layout + id_cols = ["vill_id", "vill_name"] + category_cols = sorted( + [col for col in final_df.columns if col not in id_cols], + key=lambda x: (int(x.split("_")[-1]), x), + ) + final_df = final_df[id_cols + category_cols] + + # Save to Excel + final_df = final_df.drop_duplicates(subset=["vill_id", "vill_name"]) + final_df.to_excel(writer, sheet_name="nrega_assets_village", index=False) + print("Excel file created successfully with all villages.") + + +def fetch_village_asset_count( + state, district, block, writer, output_file, start_year, end_year +): + # 1. Read village data + village_gdf = gpd.read_file(get_url("panchayat_boundaries", f"{district}_{block}"))[ + ["vill_ID", "vill_name", "geometry"] + ].copy() + print("Village data loaded") + + # 2. Get NREGA data with timeout to prevent hanging + try: + nrega_url = get_url("nrega_assets", f"{district}_{block}") + print(f"Fetching: {nrega_url}") + + # Add timeout to prevent hanging + response = requests.get(nrega_url, timeout=120) + nrega_json = response.json() + print("NREGA data fetched successfully") + + except Exception as e: + print(f"Failed to get NREGA data: {e}") + # Return villages with "No Asset" if API fails + result_df = village_gdf[["vill_ID", "vill_name"]].copy() + result_df["Asset ID"] = "No Asset" + result_df["creation_t"] = "No Asset" + result_df["WorkCatego"] = "No Asset" + create_excel_village_nrega_assets( + result_df, + output_file, + writer, + village_gdf[["vill_ID", "vill_name"]], + start_year, + end_year, + ) + return result_df, village_gdf[["vill_ID", "vill_name"]] + + # 3. Process asset features + points_data = [] + features = nrega_json.get("features", []) + print(f"Processing {len(features)} features") + + for feature in features: + try: + point = Point(feature["geometry"]["coordinates"]) + properties = feature["properties"] + points_data.append( + { + "geometry": point, + "Asset ID": properties.get("Asset ID", "MISSING"), + "creation_t": properties.get("creation_t", ""), + "WorkCatego": properties.get("WorkCatego", ""), + } + ) + except: + continue + + # If no valid points, return no assets + if not points_data: + print("No valid asset points found") + result_df = village_gdf[["vill_ID", "vill_name"]].copy() + result_df["Asset ID"] = "No Asset" + result_df["creation_t"] = "No Asset" + result_df["WorkCatego"] = "No Asset" + create_excel_village_nrega_assets( + result_df, + output_file, + writer, + village_gdf[["vill_ID", "vill_name"]], + start_year, + end_year, + ) + return result_df, village_gdf[["vill_ID", "vill_name"]] + + print("Asset points created") + + # 4. Create points GeoDataFrame and match CRS + points_gdf = gpd.GeoDataFrame(points_data, geometry="geometry") + if village_gdf.crs != points_gdf.crs: + points_gdf.set_crs(village_gdf.crs, inplace=True) + + # 5. Find which village each asset belongs to + joined_gdf = gpd.sjoin(points_gdf, village_gdf, how="inner", predicate="within") + + # 6. Get asset + village info + result_df = joined_gdf[ + ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] + ].copy() + + # 7. Add villages that have no assets + villages_with_assets = result_df["vill_ID"].unique() + no_asset_villages = village_gdf[ + ~village_gdf["vill_ID"].isin(villages_with_assets) + ].copy() + no_asset_villages["Asset ID"] = "No Asset" + no_asset_villages["creation_t"] = "No Asset" + no_asset_villages["WorkCatego"] = "No Asset" + + result_df = pd.concat( + [ + result_df, + no_asset_villages[ + ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] + ], + ], + ignore_index=True, + ) + + print("Processing complete") + + # 8. Create Excel file + create_excel_village_nrega_assets( + result_df, + output_file, + writer, + village_gdf[["vill_ID", "vill_name"]], + start_year, + end_year, + ) + + return result_df, village_gdf[["vill_ID", "vill_name"]] + + +def analyze_results(village_asset_count, village_gdf): + villages_with_counts = village_gdf.merge( + village_asset_count, on="vill_ID", how="left" + ) + villages_with_counts["asset_count"] = villages_with_counts["asset_count"].fillna(0) + return villages_with_counts + + +def create_excel_crop_inten(data, output_file, writer, start_year, end_year): + df_data = [] + + features = data["features"] + for feature in features: + properties = feature.get("properties", {}) + uid = properties.get("uid", "Unknown") + row = {"UID": uid, "area_in_ha": properties.get("area_in_ha", 0)} + + # Process each year in range using new key naming convention + for year in range(start_year, end_year + 1): + cropping_key = f"cropping_intensity_{year}" + single_c_key = f"single_cropped_area_{year}" + single_k_key = f"single_kharif_cropped_area_{year}" + single_n_key = f"single_non_kharif_cropped_area_{year}" + doubly_c_key = f"doubly_cropped_area_{year}" + triply_c_key = f"triply_cropped_area_{year}" + + row[f"cropping_intensity_unit_less_{year}-{year + 1}"] = properties.get( + cropping_key, 0 + ) + row[f"single_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + single_c_key, 0 + ) + row[f"single_kharif_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + single_k_key, 0 + ) + row[f"single_non_kharif_cropped_area_in_ha_{year}-{year + 1}"] = ( + properties.get(single_n_key, 0) + ) + row[f"doubly_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + doubly_c_key, 0 + ) + row[f"triply_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + triply_c_key, 0 + ) + + row["sum_area_in_ha"] = properties.get("sum", 0) / 10000 + df_data.append(row) + + # Create and format DataFrame + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + # Round numeric columns + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + # Write to Excel + df.to_excel(writer, sheet_name="croppingIntensity_annual", index=False) + print("Excel file created for cropping intensity.") + + +def create_excel_crop_drou(data, output_file, writer, start_year, end_year): + df_data = [] + + features = data["features"] + for feature in features: + properties = feature.get("properties", {}) + row = {"UID": properties.get("uid", "Unknown ID")} + + for year in range(start_year, end_year + 1): + drlb_key = f"drlb_{year}" + drysp_key = f"drysp_{year}" + kh_cr_key = f"kh_cr_{year}" + m_ons_key = f"m_ons_{year}" + pcr_k_key = f"pcr_k_{year}" + t_wks_key = f"t_wks_{year}" + + # Get drought levels (drlb) and count occurrences + drlb_value = properties.get(drlb_key, "") + row[f"No_Drought_in_weeks_{year}"] = drlb_value.count("0") + row[f"Mild_in_weeks_{year}"] = drlb_value.count("1") + row[f"Moderate_in_weeks_{year}"] = drlb_value.count("2") + row[f"Severe_in_weeks_{year}"] = drlb_value.count("3") + + # Add other properties + row[f"drysp_unit_4_weeks_{year}"] = properties.get(drysp_key, "0") + row[f"kharif_cropped_sqkm_{year}"] = properties.get(kh_cr_key, "0") + row[f"monsoon_onset_{year}"] = properties.get(m_ons_key, "0") + row[f"kharif_cropped_area_percent_{year}"] = properties.get(pcr_k_key, "0") + row[f"total_weeks_{year}"] = properties.get(t_wks_key, "0") + + df_data.append(row) + + # Create DataFrame + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + # Write to Excel + df.to_excel(writer, sheet_name="croppingDrought_kharif", index=False) + print("Excel file created for cropping drought.") + + +def parse_geojson_annual_mws(data): + features = data["features"] + + all_data = defaultdict(lambda: defaultdict(dict)) + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + + for key, value in properties.items(): + if isinstance(key, str) and isinstance(value, str): + if key.startswith("20") and len(key) == 9: + year = key + try: + # Attempt to parse the value as JSON + year_data = json.loads(value.replace("'", '"')) + all_data[uid][year] = year_data + except Exception as e: + print(f"Couldn't parse data for {uid}, {key}: {e}") + + return all_data + + +def create_excel_annual_mws(data, output_file, writer): + df_data = [] + year_columns = ["ET", "RunOff", "G", "DeltaG", "Precipitation", "WellDepth"] + + for uid, years in data.items(): + row = {"UID": uid} + + for year, metrics in years.items(): + start_year = year[:4] + end_year = str(int(start_year) + 1) + formatted_year = f"{start_year}-{end_year}" + + for col in year_columns: + if col == "WellDepth": + column_name = f"{col}_in_m_{formatted_year}" + row[column_name] = metrics.get(col, "N/A") + else: + column_name = f"{col}_in_mm_{formatted_year}" + row[column_name] = metrics.get(col, "N/A") + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="hydrological_annual", index=False) + print("Excel file created for hydrological_annual") + + +def parse_json_seas_mws(file_path): + with open(file_path, "r") as file: + data = json.load(file) + return data + + +def get_season(month): + if month in (3, 4, 5, 6): + return "zaid" + elif month in (7, 8, 9, 10): + return "kharif" + elif month in (11, 12, 1, 2): + return "rabi" + + +def process_feature(feature): + uid = feature["properties"]["uid"] + results = { + "UID": uid, + "precipitation": {"kharif": {}, "rabi": {}, "zaid": {}}, + "et": {"kharif": {}, "rabi": {}, "zaid": {}}, + "runoff": {"kharif": {}, "rabi": {}, "zaid": {}}, + "delta g": {"kharif": {}, "rabi": {}, "zaid": {}}, + "g": {"kharif": {}, "rabi": {}, "zaid": {}}, + } + + variable_mapping = { + "Precipitation": "precipitation", + "ET": "et", + "RunOff": "runoff", + "DeltaG": "delta g", + "G": "g", + } + + for key, value in feature["properties"].items(): + if key.startswith("20"): + try: + date = datetime.strptime(key, "%Y-%m-%d") + year = date.year + month = date.month + season = get_season(month) + if season == "rabi": + current_year = year - 1 if month in (1, 2) else year + elif season == "zaid": + current_year = year - 1 if month in (3, 4, 5, 6) else year + else: + current_year = year + data = json.loads(value) + + for json_var, result_var in variable_mapping.items(): + if json_var in data: + if current_year not in results[result_var][season]: + results[result_var][season][current_year] = 0.0 + results[result_var][season][current_year] += float( + data[json_var] + ) + + except (ValueError, json.JSONDecodeError) as e: + print(f"Error processing data for date {key}: {e}") + continue + return results + + +def create_excel_seas_mws(processed_data, output_file, writer, start_year, end_year): + variables = ["precipitation", "et", "runoff", "delta g", "g"] + seasons = ["kharif", "rabi", "zaid"] + + data = {"UID": []} + for variable in variables: + for year in range(start_year, end_year + 1): + for season in seasons: + end_to_year = year + 1 + column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" + data[column_name] = [] + + for feature_data in processed_data: + data["UID"].append(feature_data["UID"]) + for variable in variables: + for year in range(start_year, end_year + 1): + for season in seasons: + end_to_year = year + 1 + column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" + value = feature_data[variable].get(season, {}).get(year, 0.0) + data[column_name].append(value) + + df = pd.DataFrame(data) + df = df.sort_values("UID") + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="hydrological_seasonal", index=False) + print(f"Excel file created hydrological_seasonal") + + +def create_excel_for_village_boun(old_geojson, writer): + results = [] + + village_data = {} + + for feature in old_geojson["features"]: + properties = feature["properties"] + + # Extract properties + state_census_ID = properties.get("state_cen", None) + dist_census_ID = properties.get("dist_cen", None) + block_census_ID = properties.get("block_cen", None) + village_id = properties.get("vill_ID", None) + village_name = properties.get("vill_name", None) + + # Initialize village data using village_id as the key + if village_id not in village_data: + village_data[village_id] = { + "village_name": village_name, + "TOT_P": 0, + "P_LIT": 0, + "P_SC": 0, + "P_ST": 0, + "state_census_ID": state_census_ID, + "dist_census_ID": dist_census_ID, + "block_census_ID": block_census_ID, + "geometry": feature["geometry"], + } + + village_data[village_id]["TOT_P"] += properties.get("TOT_P", 0) + village_data[village_id]["P_LIT"] += properties.get("P_LIT", 0) + village_data[village_id]["P_SC"] += properties.get("P_SC", 0) + village_data[village_id]["P_ST"] += properties.get("P_ST", 0) + + for village_id, data in village_data.items(): + total_popu = data["TOT_P"] + literacy_rate = data["P_LIT"] * 100 / total_popu if total_popu > 0 else 0.0 + total_SC_popu = data["P_SC"] + total_ST_popu = data["P_ST"] + sc_perce = (data["P_SC"] * 100 / total_popu) if total_popu > 0 else 0.0 + st_perce = (data["P_ST"] * 100 / total_popu) if total_popu > 0 else 0.0 + + results.append( + { + "state_census_ID": data["state_census_ID"], + "dist_census_ID": data["dist_census_ID"], + "block_census_ID": data["block_census_ID"], + "village_id": village_id, + "village_name": data["village_name"], + "total_population_count": total_popu, + "total_SC_population_count": total_SC_popu, + "total_ST_population_count": total_ST_popu, + "literacy_rate_percent": literacy_rate, + "SC_percent": sc_perce, + "ST_percent": st_perce, + } + ) + + results_df = pd.DataFrame(results) + results_df.to_excel(writer, sheet_name="social_economic_indicator", index=False) + + print(f"Excel file created for social_economic_indicator") + + +def download_layers_excel_file(state, district, block): + base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") + state_path = os.path.join(base_path, state.upper()) + district_path = os.path.join(state_path, district.upper()) + filename = f"{district}_{block}.xlsx" + file_path = os.path.join(district_path, filename) + + output_dir = Path(file_path).parent + output_dir.mkdir(parents=True, exist_ok=True) + + if not os.path.exists(file_path): + try: + if not get_vector_layer_geoserver(state, district, block): + return Response( + { + "status": "error", + "message": "Failed to generate vector layer from GeoServer.", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + if not os.path.exists(file_path): + return Response( + {"status": "error", "message": "Failed to generate Excel file."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + except Exception as e: + return Response( + { + "status": "error", + "message": f"Error during file generation: {str(e)}", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + else: + print(f"Excel file already exists at: {file_path}") + + # Single file reading logic - only written once! + if os.path.exists(file_path): + try: + with open(file_path, "rb") as file: + response = HttpResponse( + file.read(), + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + response["Content-Disposition"] = f"attachment; filename={filename}" + return response + except Exception as e: + return Response( + {"status": "error", "message": f"Error reading file: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + else: + return Response( + {"status": "error", "message": "Failed to locate Excel file."}, + status=status.HTTP_404_NOT_FOUND, + ) + + +def generate_stats_excel_file(state, district, block): + """ + Deletes existing Excel layer file and forces regeneration. + Then returns the newly generated file. + """ + base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") + state_path = os.path.join(base_path, state.upper()) + district_path = os.path.join(state_path, district.upper()) + filename = f"{district}_{block}.xlsx" + file_path = os.path.join(district_path, filename) + + try: + # Create directory if it doesn't exist + output_dir = Path(file_path).parent + output_dir.mkdir(parents=True, exist_ok=True) + + # ALWAYS delete existing file if it exists + if os.path.exists(file_path): + os.remove(file_path) + + from .mws_indicators import generate_mws_data_for_kyl_filters + from .village_indicators import get_generate_filter_data_village + from public_api.views import get_tehsil_json + + get_vector_layer_geoserver(state, district, block) + get_tehsil_json(state, district, block, 1) + generate_mws_data_for_kyl_filters(state, district, block, "json", 1) + get_generate_filter_data_village(state, district, block, 1) + + if not os.path.exists(file_path): + return Response( + { + "status": "error", + "message": "Excel file generation completed but file not found.", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + # Return the newly generated file + with open(file_path, "rb") as file: + response = HttpResponse( + file.read(), + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + response["Content-Disposition"] = f"attachment; filename={filename}" + return response + + except Exception as e: + return Response( + {"status": "error", "message": f"Failed to generate stats excel: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + +def add_sheets_to_excel(state, district, block, sheets): + try: + sheets_to_add = [sheet.strip() for sheet in sheets.split(",") if sheet.strip()] + results = [] + + for sheet in sheets_to_add: + r = get_vector_layer_geoserver(state, district, block, sheet) + results.append(r) + + from .mws_indicators import generate_mws_data_for_kyl_filters + from .village_indicators import get_generate_filter_data_village + + from public_api.views import get_tehsil_json + + get_tehsil_json(state, district, block, 1) + generate_mws_data_for_kyl_filters(state, district, block, "json", 1) + get_generate_filter_data_village(state, district, block, 1) + + successful = [r for r in results if r["status"] == "success"] + failed = [r for r in results if r["status"] == "failed"] + + response_data = { + "status": "success" if successful else "failed", + "message": f"Stats data added info. {len(successful)} successful, {len(failed)} failed.", + } + + return response_data + + except Exception as e: + return { + "status": "error", + "message": str(e), + } From 4efc2a5b6b2e38bf6f698b416b01963970fe103c Mon Sep 17 00:00:00 2001 From: "Ankit K." Date: Thu, 11 Jun 2026 17:46:12 +0530 Subject: [PATCH 28/38] Hotfix/email timeout (#1010) * patched name * email timeout increased to 15 minutes --- plans/tests.py | 12 ++++++++++++ plans/utils.py | 24 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/plans/tests.py b/plans/tests.py index bb68993b..d59c6045 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -471,6 +471,18 @@ def test_block_name_with_spaces_matches_underscore_block_param(self): self.assertTrue(result) + def test_block_name_with_underscores_matches_underscore_block_param(self): + _create_settlement(plan_id="42", block_name="test_block", settlement_id="SETT004") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_id"], "SETT004") + def test_only_matching_plan_id_returned(self): _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") diff --git a/plans/utils.py b/plans/utils.py index 7e96cfcf..f4f51403 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -5,6 +5,7 @@ import dateutil.parser import requests +from django.db.models import Q from dpr.models import ( ODK_settlement, ODK_well, ODK_waterbody, @@ -37,6 +38,27 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() + +def _block_filter_q(block): + """ + Accept both historical block-name storage styles in the DB: + `foo bar` and `foo_bar`. + """ + raw = (block or "").strip() + if not raw: + return Q() + + variants = { + raw, + raw.replace("_", " ").strip(), + raw.replace(" ", "_").strip(), + } + + query = Q() + for variant in variants: + query |= Q(block_name__icontains=variant) + return query + _RESOURCE_TYPES_FLAT_HEADER = frozenset({ "settlement", "well", "waterbody", "cropping", }) @@ -603,7 +625,7 @@ def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) if has_block_col: - qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) + qs = qs.filter(_block_filter_q(block)) raw_rows = list(qs.values(data_field, *projection_fields)) logger.info( From 62f424fc265a040c3f2ab9f9682d97777f8b364c Mon Sep 17 00:00:00 2001 From: Aman Verma Date: Thu, 11 Jun 2026 17:51:57 +0530 Subject: [PATCH 29/38] Revert "Hotfix/email timeout (#1010)" (#1012) This reverts commit 0bfb37afbf7e93c572388374c1ea1d917af44bbc. --- plans/tests.py | 12 ------------ plans/utils.py | 24 +----------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/plans/tests.py b/plans/tests.py index d59c6045..bb68993b 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -471,18 +471,6 @@ def test_block_name_with_spaces_matches_underscore_block_param(self): self.assertTrue(result) - def test_block_name_with_underscores_matches_underscore_block_param(self): - _create_settlement(plan_id="42", block_name="test_block", settlement_id="SETT004") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_id"], "SETT004") - def test_only_matching_plan_id_returned(self): _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") diff --git a/plans/utils.py b/plans/utils.py index f4f51403..7e96cfcf 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -5,7 +5,6 @@ import dateutil.parser import requests -from django.db.models import Q from dpr.models import ( ODK_settlement, ODK_well, ODK_waterbody, @@ -38,27 +37,6 @@ def normalize_name(name): return "" return name.lower().replace(" ", "_").strip() - -def _block_filter_q(block): - """ - Accept both historical block-name storage styles in the DB: - `foo bar` and `foo_bar`. - """ - raw = (block or "").strip() - if not raw: - return Q() - - variants = { - raw, - raw.replace("_", " ").strip(), - raw.replace(" ", "_").strip(), - } - - query = Q() - for variant in variants: - query |= Q(block_name__icontains=variant) - return query - _RESOURCE_TYPES_FLAT_HEADER = frozenset({ "settlement", "well", "waterbody", "cropping", }) @@ -625,7 +603,7 @@ def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) if has_block_col: - qs = qs.filter(_block_filter_q(block)) + qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) raw_rows = list(qs.values(data_field, *projection_fields)) logger.info( From aacd9210d37b3742a07af09be6f7c7383f78f1c5 Mon Sep 17 00:00:00 2001 From: shiv1122prakash Date: Tue, 16 Jun 2026 16:15:51 +0530 Subject: [PATCH 30/38] updated column with unit (#1001) * updated column with unit * updated sheet not aviable * chnages for handle exception * chnages for drainage density * added for livestock * chnages for excel --- computing/lulc_X_terrain/lulc_on_plain_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 00c073ed..5c706e32 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -31,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters_bk02_june" + + "_lulcXplains_clusters" ) asset_id = get_gee_asset_path(state, district, block) + asset_description From bfd875e0750ed7888c3b6d82e19741a89fc98d29 Mon Sep 17 00:00:00 2001 From: "shiv.prakash1" Date: Tue, 16 Jun 2026 17:10:24 +0530 Subject: [PATCH 31/38] chnages for conflict --- computing/lulc_X_terrain/lulc_on_plain_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 5c706e32..00c073ed 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -31,7 +31,7 @@ def lulc_on_plain_cluster( valid_gee_text(district.lower()) + "_" + valid_gee_text(block.lower()) - + "_lulcXplains_clusters" + + "_lulcXplains_clusters_bk02_june" ) asset_id = get_gee_asset_path(state, district, block) + asset_description From 0496b15e6dd30572b8d532781ea591b78faf0ff2 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 16 Jun 2026 06:00:49 -0700 Subject: [PATCH 32/38] remove weasyprint use --- dpr/Plan Uchukabeda.docx | Bin 0 -> 46245 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dpr/Plan Uchukabeda.docx diff --git a/dpr/Plan Uchukabeda.docx b/dpr/Plan Uchukabeda.docx new file mode 100644 index 0000000000000000000000000000000000000000..e493c9fe1dce9f6be8a0fce5cd6abe74040925ad GIT binary patch literal 46245 zcmZU(b6{lMvIp9+ZCevN6Wg|J+qRR5C!W~0C!9$pM#t79>DYYv&bjBkd+z(EyLYXs z+SP?sd+quu%RxY50ssJ508HwPeywWhw{$Q7U>q6%!1$!gPJ!xyj5x#Q7UW(LWFKRx{6jnSA@j)G?T zmizr7fOxG*pwGXJT2D+IWMGi1RZI?1?q9r7#B^@SeIW6TOa>^uN3<{k-O!Qik2lQ) z#tgd?Ui)N4B`e0ny z^X*TN7x|M|utfgbG846Co?Q-nR|rp5jPa$0WTbIR%CK9b=OlfztCfaJ)7sBcWbaV~ zC)BxphXE$92`%;$=%&i}TQ%KHblZr1kds|Fb;JBIM84LCBVXa^nkKzWPg1Ra_`}e{16^qnrQp+YwBf_Tyn@3Yjo@D>k(?Sr zjjG)uNg2P{zjA-LoQRPJ+!coFt$Y<%4CoOz5}*wc9`<{@LVh_kfm+3VL=35Wkbdng zJMdTwEi#j5$e`1EOD8Hp>;Go*;`fYDUX3R%`12K5zHuNl{y<*zf~jB#Q2wtd`cTUW zKYm7U6C40Q`203rEhIq+}q4=>viQquipYt-u zK_9~Oxe}R*^SxE$<^YYB+pBkP#j`Gm3Z)% zSWwW6xj}`Mkof|35-Tf>8M54Y&%vo+KatNhxwTi~pj$}qNxa23cH=C#!QI*pVHS}R zK@#=tBE&ovK?m!7%@lk6C12vp*V^>(!Ha?C`DTQ+#9-{&8}I40*Q2y!q22ej8zYF zEod8{*$6?NPf&;`VC=#w-&MUjG}*ReL2j-tThpEp2Gxk|QHaPHct2)A6*a=4KVaAO zJU5+&OB=@)7IxPe-Kc_v+|4`K8!+D5CU|HvO#*W_ejtx|30OtJ<)uB`UfHnbq{(2G zi{wJebV<)K^i38WHwS%4z2hwt(m!%vLE@fRAsf=odx^cj4qg-dcU*4Q)^JWg<6;j9 z0HFTwxR^LQ|0^u&6OL;vs68+AkOuCtHZUS8xGAAaBC}!}_zfw!?F-}Nvfm&(S$6{i zo}#h7Ml#>U@HO+jw2Z$1MJ!=j6suED3J%#tu2*Q*A@L5-o|NKuW-B+$ zRIRB}EBZPCl-#c^GFJGN6XH#bHvabm*kH7=1RU~WVw4}$Y(y$ zgi4j9dV6x1Uzv$7;c}jC2tF3?&Pe_{F@7gKU;d^60EWRJ|5dPF zPOj$6pH=JO@Y$UH$qZ0{f!kU~%Kh6rygCz?mD=|#fK2l3`M$bofj8Tet4@BZ?-l`$ z{4Dwy=8uCY&U~YO0NwAV;^ZdC(6nV7I7V6VVv*aic_fbaqk

        a(5g`n_qiIM!Vdj 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 ti~ehsKLW4k^3VOjf8_ Date: Tue, 16 Jun 2026 06:02:35 -0700 Subject: [PATCH 33/38] remove unwanted file --- dpr/Plan Uchukabeda.docx | Bin 46245 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dpr/Plan Uchukabeda.docx diff --git a/dpr/Plan Uchukabeda.docx b/dpr/Plan Uchukabeda.docx deleted file mode 100644 index e493c9fe1dce9f6be8a0fce5cd6abe74040925ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46245 zcmZU(b6{lMvIp9+ZCevN6Wg|J+qRR5C!W~0C!9$pM#t79>DYYv&bjBkd+z(EyLYXs z+SP?sd+quu%RxY50ssJ508HwPeywWhw{$Q7U>q6%!1$!gPJ!xyj5x#Q7UW(LWFKRx{6jnSA@j)G?T zmizr7fOxG*pwGXJT2D+IWMGi1RZI?1?q9r7#B^@SeIW6TOa>^uN3<{k-O!Qik2lQ) z#tgd?Ui)N4B`e0ny z^X*TN7x|M|utfgbG846Co?Q-nR|rp5jPa$0WTbIR%CK9b=OlfztCfaJ)7sBcWbaV~ zC)BxphXE$92`%;$=%&i}TQ%KHblZr1kds|Fb;JBIM84LCBVXa^nkKzWPg1Ra_`}e{16^qnrQp+YwBf_Tyn@3Yjo@D>k(?Sr zjjG)uNg2P{zjA-LoQRPJ+!coFt$Y<%4CoOz5}*wc9`<{@LVh_kfm+3VL=35Wkbdng zJMdTwEi#j5$e`1EOD8Hp>;Go*;`fYDUX3R%`12K5zHuNl{y<*zf~jB#Q2wtd`cTUW zKYm7U6C40Q`203rEhIq+}q4=>viQquipYt-u zK_9~Oxe}R*^SxE$<^YYB+pBkP#j`Gm3Z)% zSWwW6xj}`Mkof|35-Tf>8M54Y&%vo+KatNhxwTi~pj$}qNxa23cH=C#!QI*pVHS}R zK@#=tBE&ovK?m!7%@lk6C12vp*V^>(!Ha?C`DTQ+#9-{&8}I40*Q2y!q22ej8zYF zEod8{*$6?NPf&;`VC=#w-&MUjG}*ReL2j-tThpEp2Gxk|QHaPHct2)A6*a=4KVaAO zJU5+&OB=@)7IxPe-Kc_v+|4`K8!+D5CU|HvO#*W_ejtx|30OtJ<)uB`UfHnbq{(2G zi{wJebV<)K^i38WHwS%4z2hwt(m!%vLE@fRAsf=odx^cj4qg-dcU*4Q)^JWg<6;j9 z0HFTwxR^LQ|0^u&6OL;vs68+AkOuCtHZUS8xGAAaBC}!}_zfw!?F-}Nvfm&(S$6{i zo}#h7Ml#>U@HO+jw2Z$1MJ!=j6suED3J%#tu2*Q*A@L5-o|NKuW-B+$ zRIRB}EBZPCl-#c^GFJGN6XH#bHvabm*kH7=1RU~WVw4}$Y(y$ zgi4j9dV6x1Uzv$7;c}jC2tF3?&Pe_{F@7gKU;d^60EWRJ|5dPF zPOj$6pH=JO@Y$UH$qZ0{f!kU~%Kh6rygCz?mD=|#fK2l3`M$bofj8Tet4@BZ?-l`$ z{4Dwy=8uCY&U~YO0NwAV;^ZdC(6nV7I7V6VVv*aic_fbaqk

        a(5g`n_qiIM!Vdj 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 ti~ehsKLW4k^3VOjf8_ Date: Tue, 16 Jun 2026 13:55:01 +0000 Subject: [PATCH 34/38] rebased staging --- .../lulc_X_terrain/lulc_on_plain_cluster.py | 608 +-- computing/utils.py | 2242 ++++---- computing/views.py | 958 ++-- dpr/tasks.py | 246 +- dpr/utils.py | 1080 ++-- dpr/views.py | 128 +- nrm_app/settings.py | 872 ++-- plans/api.py | 1578 +++--- plans/tests.py | 1112 ++-- plans/utils.py | 1478 +++--- stats_generator/mws_indicators.py | 2260 ++++---- stats_generator/utils.py | 4538 ++++++++--------- 12 files changed, 8550 insertions(+), 8550 deletions(-) diff --git a/computing/lulc_X_terrain/lulc_on_plain_cluster.py b/computing/lulc_X_terrain/lulc_on_plain_cluster.py index 00c073ed..1f2d04e6 100644 --- a/computing/lulc_X_terrain/lulc_on_plain_cluster.py +++ b/computing/lulc_X_terrain/lulc_on_plain_cluster.py @@ -1,304 +1,304 @@ -import ee -from nrm_app.celery import app -from computing.utils import ( - sync_layer_to_geoserver, - save_layer_info_to_db, - update_layer_sync_status, - create_chunk, - merge_chunks, -) -from utilities.gee_utils import ( - ee_initialize, - check_task_status, - valid_gee_text, - get_gee_asset_path, - is_gee_asset_exists, - export_vector_asset_to_gee, - make_asset_public, - get_gee_dir_path, -) -from .utils import aez_lulcXterrain_cluster_centroids, process_mws, calculate_area -from utilities.constants import AEZ, GEE_HELPER_PATH - - -@app.task(bind=True) -def lulc_on_plain_cluster( - self, state, district, block, start_year, end_year, gee_account_id -): - ee_initialize(gee_account_id) - - asset_description = ( - valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_lulcXplains_clusters_bk02_june" - ) - asset_id = get_gee_asset_path(state, district, block) + asset_description - - if not is_gee_asset_exists(asset_id): - aez_india = ee.FeatureCollection(AEZ) - - landforms = ee.Image( - get_gee_asset_path(state, district, block) - + "terrain_raster_" - + valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - ) # The eleven landforms raster - - mwsheds = ee.FeatureCollection( - get_gee_asset_path(state, district, block) - + "filtered_mws_" - + valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_uid" - ) - - filtered_aez = aez_india.filterBounds(mwsheds.geometry()) - - aez_no = filtered_aez.first().get("ae_regcode").getInfo() - - lulc_imgs = [] - for y in range(start_year, end_year + 1): - lulc_img = ee.Image( - get_gee_asset_path(state, district, block) - + valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_" - + str(y) - + "-07-01_" - + str(y + 1) - + "-06-30_LULCmap_10m" - ) - lulc_imgs.append(lulc_img) - - lulc_img_collection = ee.ImageCollection.fromImages(lulc_imgs) - study_area_lulc = lulc_img_collection.mode().clip(mwsheds) - study_area_landforms = landforms.clip(mwsheds) - - mwsheds_with_clusters = process_mws(mwsheds) - plain_mwsheds = mwsheds_with_clusters.filter( - ee.Filter.neq("terrain_cluster", 2) - ) - plain_centroids = aez_lulcXterrain_cluster_centroids[f"aez{aez_no}"]["plains"] - - chunk_size = 50 - rois, descs = create_chunk(mwsheds, asset_description, chunk_size) - - - tasks = [] - temp_assets = [] - for roi, desc in zip(rois, descs): - chunk_with_clusters = process_mws(roi) - plain_chunk = chunk_with_clusters.filter( - ee.Filter.neq("terrain_cluster", 2) - ) - - - result_chunk = process_feature_collection( - plain_chunk, study_area_landforms, study_area_lulc, plain_centroids - ) - - chunk_asset_id = get_gee_dir_path([state, district, block], GEE_HELPER_PATH) + desc - temp_assets.append(chunk_asset_id) - - - task = export_vector_asset_to_gee( - result_chunk, desc, chunk_asset_id - ) - if task: - tasks.append(task) - - - print("Started all chunk tasks") - task_id_list = check_task_status(tasks) - print("All chunk tasks completed:", task_id_list) - - - # Merge all chunks into one feature collection - print("Starting merge task") - final_task_id = merge_chunks( - mwsheds, - [state, district, block], - asset_description, - chunk_size, - merge_asset_id=asset_id, - ) - if final_task_id: - final_task_status = check_task_status([final_task_id]) - print("Final merge task completed:", final_task_status) - - - # Clean up temporary assets - for chunk_id in temp_assets: - if is_gee_asset_exists(chunk_id): - try: - ee.data.deleteAsset(chunk_id) - print(f"Deleted temp asset {chunk_id}") - except Exception as e: - print(f"Failed to delete {chunk_id}: {e}") - - - layer_at_geoserver = False - if is_gee_asset_exists(asset_id): - layer_id = save_layer_info_to_db( - state, - district, - block, - layer_name=f"{valid_gee_text(district.lower())}_{valid_gee_text(block.lower())}_lulc_plain", - asset_id=asset_id, - dataset_name="Terrain LULC", - misc={ - "start_year": start_year, - "end_year": end_year, - }, - ) - make_asset_public(asset_id) - - fc = ee.FeatureCollection(asset_id).getInfo() - fc = {"features": fc["features"], "type": fc["type"]} - res = sync_layer_to_geoserver( - state, - fc, - valid_gee_text(district.lower()) - + "_" - + valid_gee_text(block.lower()) - + "_lulc_plain", - "terrain_lulc", - ) - print(res) - if res["status_code"] == 201 and layer_id: - update_layer_sync_status(layer_id=layer_id, sync_to_geoserver=True) - print("sync to geoserver flag updated") - layer_at_geoserver = True - return layer_at_geoserver - - -def process_feature_collection(fc, landforms, area_lulc, plain_centroids): - """ - Process an entire FeatureCollection by applying the L2 cluster assignment. - """ - return fc.map(lambda f: assign_l2_cluster(f, landforms, area_lulc, plain_centroids)) - - -def assign_l2_cluster(feature, landforms, area_lulc, plain_centroids): - """ - Assigns L2 clusters to features based on landform and land use characteristics. - """ - study_area = feature.geometry() - lf300x2k = landforms.clip(study_area) - - # Get LULC data - lulc = area_lulc.select("predicted_label") - - # # Convert 10 landforms to 4 general landforms - # slopy = lf300x2k.eq(6) - # plains = lf300x2k.eq(5).Or(lf300x2k.gte(12)) - # steep_slopes = lf300x2k.eq(8) - # ridge = lf300x2k.eq(7).Or(lf300x2k.gte(9).And(lf300x2k.lte(11))) - # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) - - # Convert 10 landforms to 4 general landforms - slopy = lf300x2k.eq(6) - plains = lf300x2k.eq(5) - steep_slopes = lf300x2k.eq(8) - # ridge = lf300x2k.gte(9).Or(lf300x2k.eq(7)) - # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) - ridge = lf300x2k.eq(3).Or(lf300x2k.eq(7)).Or(lf300x2k.eq(10)).Or(lf300x2k.eq(11)) - valleys = lf300x2k.eq(1).Or(lf300x2k.eq(2)).Or(lf300x2k.eq(4)).Or(lf300x2k.eq(9)) - - # Calculate areas - plain_area = calculate_area(plains, study_area) - valley_area = calculate_area(valleys, study_area) - hill_slopes_area = calculate_area(steep_slopes, study_area) - slopy_area = calculate_area(slopy, study_area) - - plain_plus_slope_area = plain_area.add(slopy_area) - - # Calculate LULC proportions - def calculate_lulc_proportion(lulc_class): - area_image = ( - plains.eq(1) - .And(lulc.eq(lulc_class)) - .multiply(ee.Image.pixelArea()) - .rename("area") - ) - - area = area_image.reduceRegion( - reducer=ee.Reducer.sum(), geometry=study_area, scale=30, maxPixels=1e10 - ) - - return ee.Number(area.get("area")).divide(1e6).divide(plain_plus_slope_area) - - # Calculate all proportions - plains_barren = calculate_lulc_proportion(7) # Barren - plains_double_crop = calculate_lulc_proportion(10) # Double crop - plains_shrubs_scrubs = calculate_lulc_proportion(12) # Shrubs/scrubs - plains_single_crop = calculate_lulc_proportion(8) # Single crop - plains_single_non_kharif_crop = calculate_lulc_proportion(9) # Single non-kharif - plains_forest = calculate_lulc_proportion(6) # Forest - plains_triple_crop = calculate_lulc_proportion(11) # Triple crop - - # Create feature vector - plain_new_feature_vector = ee.List( - [ - plains_barren, - plains_double_crop, - plains_shrubs_scrubs, - plains_single_crop, - plains_single_non_kharif_crop, - plains_forest, - plains_triple_crop, - ] - ) - - # Convert centroids to ee.List format - centroid_vectors = [ - plain_centroids[str(i)]["cluster_vector"] for i in range(len(plain_centroids)) - ] - ee_centroid_vectors = ee.List(centroid_vectors) - - # Calculate distances - def diff_func(value_pair): - return ( - ee.Number(ee.List(value_pair).get(0)) - .subtract(ee.Number(ee.List(value_pair).get(1))) - .pow(2) - ) - - def calculate_distances(centroid): - centroid_list = ee.List(centroid) - paired_values = centroid_list.zip(plain_new_feature_vector) - return paired_values.map(diff_func).reduce(ee.Reducer.sum()) - - distances_plain = ee_centroid_vectors.map(calculate_distances) - - # Find closest cluster - min_distance_plain = distances_plain.reduce(ee.Reducer.min()) - closest_cluster_index_plain = distances_plain.indexOf(min_distance_plain) - - # Create cluster names dictionary - cluster_names = ee.Dictionary( - { - str(i): plain_centroids[str(i)]["cluster_name"] - for i in range(len(plain_centroids)) - } - ) - - # Set cluster index and name - return ( - feature.set("LxP_cluster", closest_cluster_index_plain) - .set( - "clust_name", - cluster_names.get(closest_cluster_index_plain.format()), - ) - .set("barren", plains_barren.multiply(100)) - .set("double_crop", plains_double_crop.multiply(100)) - .set("shrubs_scrubs", plains_shrubs_scrubs.multiply(100)) - .set("sing_crop", plains_single_crop.multiply(100)) - .set("sing_non_kharif_crop", plains_single_non_kharif_crop.multiply(100)) - .set("forest", plains_forest.multiply(100)) - .set("triple_crop", plains_triple_crop.multiply(100)) - ) +import ee +from nrm_app.celery import app +from computing.utils import ( + sync_layer_to_geoserver, + save_layer_info_to_db, + update_layer_sync_status, + create_chunk, + merge_chunks, +) +from utilities.gee_utils import ( + ee_initialize, + check_task_status, + valid_gee_text, + get_gee_asset_path, + is_gee_asset_exists, + export_vector_asset_to_gee, + make_asset_public, + get_gee_dir_path, +) +from .utils import aez_lulcXterrain_cluster_centroids, process_mws, calculate_area +from utilities.constants import AEZ, GEE_HELPER_PATH + + +@app.task(bind=True) +def lulc_on_plain_cluster( + self, state, district, block, start_year, end_year, gee_account_id +): + ee_initialize(gee_account_id) + + asset_description = ( + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_lulcXplains_clusters_bk02_june" + ) + asset_id = get_gee_asset_path(state, district, block) + asset_description + + if not is_gee_asset_exists(asset_id): + aez_india = ee.FeatureCollection(AEZ) + + landforms = ee.Image( + get_gee_asset_path(state, district, block) + + "terrain_raster_" + + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + ) # The eleven landforms raster + + mwsheds = ee.FeatureCollection( + get_gee_asset_path(state, district, block) + + "filtered_mws_" + + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_uid" + ) + + filtered_aez = aez_india.filterBounds(mwsheds.geometry()) + + aez_no = filtered_aez.first().get("ae_regcode").getInfo() + + lulc_imgs = [] + for y in range(start_year, end_year + 1): + lulc_img = ee.Image( + get_gee_asset_path(state, district, block) + + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_" + + str(y) + + "-07-01_" + + str(y + 1) + + "-06-30_LULCmap_10m" + ) + lulc_imgs.append(lulc_img) + + lulc_img_collection = ee.ImageCollection.fromImages(lulc_imgs) + study_area_lulc = lulc_img_collection.mode().clip(mwsheds) + study_area_landforms = landforms.clip(mwsheds) + + mwsheds_with_clusters = process_mws(mwsheds) + plain_mwsheds = mwsheds_with_clusters.filter( + ee.Filter.neq("terrain_cluster", 2) + ) + plain_centroids = aez_lulcXterrain_cluster_centroids[f"aez{aez_no}"]["plains"] + + chunk_size = 50 + rois, descs = create_chunk(mwsheds, asset_description, chunk_size) + + + tasks = [] + temp_assets = [] + for roi, desc in zip(rois, descs): + chunk_with_clusters = process_mws(roi) + plain_chunk = chunk_with_clusters.filter( + ee.Filter.neq("terrain_cluster", 2) + ) + + + result_chunk = process_feature_collection( + plain_chunk, study_area_landforms, study_area_lulc, plain_centroids + ) + + chunk_asset_id = get_gee_dir_path([state, district, block], GEE_HELPER_PATH) + desc + temp_assets.append(chunk_asset_id) + + + task = export_vector_asset_to_gee( + result_chunk, desc, chunk_asset_id + ) + if task: + tasks.append(task) + + + print("Started all chunk tasks") + task_id_list = check_task_status(tasks) + print("All chunk tasks completed:", task_id_list) + + + # Merge all chunks into one feature collection + print("Starting merge task") + final_task_id = merge_chunks( + mwsheds, + [state, district, block], + asset_description, + chunk_size, + merge_asset_id=asset_id, + ) + if final_task_id: + final_task_status = check_task_status([final_task_id]) + print("Final merge task completed:", final_task_status) + + + # Clean up temporary assets + for chunk_id in temp_assets: + if is_gee_asset_exists(chunk_id): + try: + ee.data.deleteAsset(chunk_id) + print(f"Deleted temp asset {chunk_id}") + except Exception as e: + print(f"Failed to delete {chunk_id}: {e}") + + + layer_at_geoserver = False + if is_gee_asset_exists(asset_id): + layer_id = save_layer_info_to_db( + state, + district, + block, + layer_name=f"{valid_gee_text(district.lower())}_{valid_gee_text(block.lower())}_lulc_plain", + asset_id=asset_id, + dataset_name="Terrain LULC", + misc={ + "start_year": start_year, + "end_year": end_year, + }, + ) + make_asset_public(asset_id) + + fc = ee.FeatureCollection(asset_id).getInfo() + fc = {"features": fc["features"], "type": fc["type"]} + res = sync_layer_to_geoserver( + state, + fc, + valid_gee_text(district.lower()) + + "_" + + valid_gee_text(block.lower()) + + "_lulc_plain", + "terrain_lulc", + ) + print(res) + if res["status_code"] == 201 and layer_id: + update_layer_sync_status(layer_id=layer_id, sync_to_geoserver=True) + print("sync to geoserver flag updated") + layer_at_geoserver = True + return layer_at_geoserver + + +def process_feature_collection(fc, landforms, area_lulc, plain_centroids): + """ + Process an entire FeatureCollection by applying the L2 cluster assignment. + """ + return fc.map(lambda f: assign_l2_cluster(f, landforms, area_lulc, plain_centroids)) + + +def assign_l2_cluster(feature, landforms, area_lulc, plain_centroids): + """ + Assigns L2 clusters to features based on landform and land use characteristics. + """ + study_area = feature.geometry() + lf300x2k = landforms.clip(study_area) + + # Get LULC data + lulc = area_lulc.select("predicted_label") + + # # Convert 10 landforms to 4 general landforms + # slopy = lf300x2k.eq(6) + # plains = lf300x2k.eq(5).Or(lf300x2k.gte(12)) + # steep_slopes = lf300x2k.eq(8) + # ridge = lf300x2k.eq(7).Or(lf300x2k.gte(9).And(lf300x2k.lte(11))) + # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) + + # Convert 10 landforms to 4 general landforms + slopy = lf300x2k.eq(6) + plains = lf300x2k.eq(5) + steep_slopes = lf300x2k.eq(8) + # ridge = lf300x2k.gte(9).Or(lf300x2k.eq(7)) + # valleys = lf300x2k.gte(1).And(lf300x2k.lte(4)) + ridge = lf300x2k.eq(3).Or(lf300x2k.eq(7)).Or(lf300x2k.eq(10)).Or(lf300x2k.eq(11)) + valleys = lf300x2k.eq(1).Or(lf300x2k.eq(2)).Or(lf300x2k.eq(4)).Or(lf300x2k.eq(9)) + + # Calculate areas + plain_area = calculate_area(plains, study_area) + valley_area = calculate_area(valleys, study_area) + hill_slopes_area = calculate_area(steep_slopes, study_area) + slopy_area = calculate_area(slopy, study_area) + + plain_plus_slope_area = plain_area.add(slopy_area) + + # Calculate LULC proportions + def calculate_lulc_proportion(lulc_class): + area_image = ( + plains.eq(1) + .And(lulc.eq(lulc_class)) + .multiply(ee.Image.pixelArea()) + .rename("area") + ) + + area = area_image.reduceRegion( + reducer=ee.Reducer.sum(), geometry=study_area, scale=30, maxPixels=1e10 + ) + + return ee.Number(area.get("area")).divide(1e6).divide(plain_plus_slope_area) + + # Calculate all proportions + plains_barren = calculate_lulc_proportion(7) # Barren + plains_double_crop = calculate_lulc_proportion(10) # Double crop + plains_shrubs_scrubs = calculate_lulc_proportion(12) # Shrubs/scrubs + plains_single_crop = calculate_lulc_proportion(8) # Single crop + plains_single_non_kharif_crop = calculate_lulc_proportion(9) # Single non-kharif + plains_forest = calculate_lulc_proportion(6) # Forest + plains_triple_crop = calculate_lulc_proportion(11) # Triple crop + + # Create feature vector + plain_new_feature_vector = ee.List( + [ + plains_barren, + plains_double_crop, + plains_shrubs_scrubs, + plains_single_crop, + plains_single_non_kharif_crop, + plains_forest, + plains_triple_crop, + ] + ) + + # Convert centroids to ee.List format + centroid_vectors = [ + plain_centroids[str(i)]["cluster_vector"] for i in range(len(plain_centroids)) + ] + ee_centroid_vectors = ee.List(centroid_vectors) + + # Calculate distances + def diff_func(value_pair): + return ( + ee.Number(ee.List(value_pair).get(0)) + .subtract(ee.Number(ee.List(value_pair).get(1))) + .pow(2) + ) + + def calculate_distances(centroid): + centroid_list = ee.List(centroid) + paired_values = centroid_list.zip(plain_new_feature_vector) + return paired_values.map(diff_func).reduce(ee.Reducer.sum()) + + distances_plain = ee_centroid_vectors.map(calculate_distances) + + # Find closest cluster + min_distance_plain = distances_plain.reduce(ee.Reducer.min()) + closest_cluster_index_plain = distances_plain.indexOf(min_distance_plain) + + # Create cluster names dictionary + cluster_names = ee.Dictionary( + { + str(i): plain_centroids[str(i)]["cluster_name"] + for i in range(len(plain_centroids)) + } + ) + + # Set cluster index and name + return ( + feature.set("LxP_cluster", closest_cluster_index_plain) + .set( + "clust_name", + cluster_names.get(closest_cluster_index_plain.format()), + ) + .set("barren", plains_barren.multiply(100)) + .set("double_crop", plains_double_crop.multiply(100)) + .set("shrubs_scrubs", plains_shrubs_scrubs.multiply(100)) + .set("sing_crop", plains_single_crop.multiply(100)) + .set("sing_non_kharif_crop", plains_single_non_kharif_crop.multiply(100)) + .set("forest", plains_forest.multiply(100)) + .set("triple_crop", plains_triple_crop.multiply(100)) + ) diff --git a/computing/utils.py b/computing/utils.py index b0d9a4a8..da6b632b 100644 --- a/computing/utils.py +++ b/computing/utils.py @@ -1,1121 +1,1121 @@ -import copy -import json -import logging -import os -import shutil -import zipfile -from datetime import datetime, timedelta - -import ee -import fiona -import geopandas as gpd -import requests -from django.conf import settings -from shapely.geometry import shape -from shapely.validation import explain_validity - -from computing.models import Dataset, Layer -from geoadmin.models import ( - DistrictSOI, - State_Disritct_Block_Properties, - StateSOI, - TehsilSOI, -) -from projects.models import Project -from utilities.constants import ( - ADMIN_BOUNDARY_OUTPUT_DIR, - GEE_ASSET_PATH, - GEE_HELPER_PATH, - GEE_PATHS, - SHAPEFILE_DIR, -) -from utilities.gee_utils import ( - check_task_status, - ee_initialize, - get_gee_asset_path, - get_gee_dir_path, - get_geojson_from_gcs, - is_asset_public, - is_gee_asset_exists, - sync_vector_to_gcs, - valid_gee_text, -) -from utilities.geoserver_utils import Geoserver -from django.core.mail import EmailMessage, get_connection -import time - -logger = logging.getLogger(__name__) - - -def generate_shape_files(path): - gdf = gpd.read_file(path + ".json") - if os.path.exists(path): - # Only replace the target shapefile directory. Removing the parent - # state/workspace directory here corrupts sibling outputs on reruns. - shutil.rmtree(path) - - os.makedirs(os.path.dirname(path), exist_ok=True) - gdf.to_file( - path, - driver="ESRI Shapefile", - ) - return path - - -def convert_to_zip(dir_name, file_type): - if file_type == "gpkg": - with zipfile.ZipFile(dir_name + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf: - zipf.write(dir_name + ".gpkg", arcname=os.path.basename(dir_name + ".gpkg")) - return dir_name + ".zip" - else: - return shutil.make_archive(dir_name, "zip", dir_name + "/") - - -def push_shape_to_geoserver( - path, store_name=None, workspace=None, layer_name=None, file_type="shp" -): - geo = Geoserver() - - print(f"layer_name: {layer_name}") - if layer_name: - try: - print(f"Attempting to delete store: {layer_name}") - geo.delete_vector_store(workspace=workspace, store=layer_name) - print(f"Successfully deleted store: {layer_name}") - except Exception as e: - print(f"Store does not exist or error deleting: {str(e)}") - - zip_path = convert_to_zip(path, file_type) - print(f"Zip path: {zip_path}") - print(f"Store name: {store_name}") - print(f"Workspace: {workspace}") - - response = geo.create_shp_datastore( - path=zip_path, - store_name=store_name, - workspace=workspace, - file_extension=file_type, - ) - print(f"Response: {response}") - return response - - -def kml_to_geojson(state_name, district_name, block_name, kml_path): - fiona.drvsupport.supported_drivers["kml"] = ( - "rw" # enable KML support which is disabled by default - ) - fiona.drvsupport.supported_drivers["KML"] = ( - "rw" # enable KML support which is disabled by default - ) - gdf = gpd.read_file(kml_path) - geometry_types = gdf.geometry.geometry.type.unique() - state_dir = os.path.join(ADMIN_BOUNDARY_OUTPUT_DIR, state_name) - - for gtype in geometry_types: - df = gdf.loc[gdf.geometry.geometry.type == gtype] - path = os.path.join(state_dir, f"{district_name}_{block_name}_{gtype}") - df.to_file(path + ".json", driver="GeoJSON") - generate_shape_files(path) - push_shape_to_geoserver(path, workspace="test_workspace") - - -def convert_kml_to_shapefile(kml_path, output_dir, shapefile_name): - if not os.path.exists(output_dir + "/" + shapefile_name): - os.makedirs(output_dir + "/" + shapefile_name) - - shapefile_path = os.path.join( - output_dir + "/" + shapefile_name, shapefile_name + ".shp" - ) - print("path path", shapefile_path) - cmd = f"ogr2ogr -f 'ESRI Shapefile' {shapefile_path} {kml_path}" # output.shp input.kml - os.system(command=cmd) - - return output_dir + "/" + shapefile_name - - -def kml_to_shp(state_name, district_name, block_name, kml_path): - shapefile_name = f"{district_name}_{block_name}" - shapefile_layer_path = convert_kml_to_shapefile( - kml_path, SHAPEFILE_DIR, shapefile_name - ) - - push_shape_to_geoserver(shapefile_layer_path, workspace="customkml") - - # os.remove(kml_path) - # shutil.rmtree(shapefile_layer_path) - os.remove(shapefile_layer_path + ".zip") - - -def sync_layer_to_geoserver(state_name, fc, layer_name, workspace): - state_dir = os.path.join("data/fc_to_shape", state_name) - if not os.path.exists(state_dir): - os.mkdir(state_dir) - path = os.path.join(state_dir, f"{layer_name}") - # Write the feature collection into json file - with open(path + ".json", "w") as f: - try: - f.write(f"{json.dumps(fc)}") - except Exception as e: - print(e) - - path = generate_shape_files(path) - return push_shape_to_geoserver(path, workspace=workspace, layer_name=layer_name) - - -def sync_fc_to_geoserver(fc, shp_folder, layer_name, workspace, style_name=None): - try: - geojson_fc = fc.getInfo() - except Exception as e: - print("Exception in getInfo()", e) - task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") - check_task_status([task_id]) - - geojson_fc = get_geojson_from_gcs(layer_name) - geo = Geoserver() - if len(geojson_fc["features"]) > 0: - state_dir = os.path.join("data/fc_to_shape", shp_folder) - if not os.path.exists(state_dir): - os.mkdir(state_dir) - path = os.path.join(state_dir, f"{layer_name}") - - # Convert to GeoDataFrame - gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) - - # Set CRS (Earth Engine uses EPSG:4326 by default) - gdf.crs = "EPSG:4326" - - gdf = fix_invalid_geometry_in_gdf(gdf) - - # Save as GeoPackage - gdf.to_file(path + ".gpkg", driver="GPKG") - res = push_shape_to_geoserver(path, workspace=workspace, file_type="gpkg") - if style_name: - style_res = geo.publish_style( - layer_name=layer_name, style_name=style_name, workspace=workspace - ) - print("Style response:", style_res) - return res - else: - return "No features in FeatureCollection" - - -def sync_project_fc_to_geoserver(fc, project_name, layer_name, workspace): - print("inside") - print(layer_name) - try: - geojson_fc = fc.getInfo() - except Exception as e: - print("Exception in getInfo()", e) - task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") - check_task_status([task_id]) - - geojson_fc = get_geojson_from_gcs(layer_name) - print(len(geojson_fc["features"])) - if len(geojson_fc["features"]) > 0: - state_dir = os.path.join("data/fc_to_shape", project_name) - if not os.path.exists(state_dir): - os.mkdir(state_dir) - path = os.path.join(state_dir, f"{layer_name}") - - # Convert to GeoDataFrame - gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) - - # Set CRS (Earth Engine uses EPSG:4326 by default) - gdf.crs = "EPSG:4326" - - gdf = fix_invalid_geometry_in_gdf(gdf) - - # Save as GeoPackage - gdf.to_file(path + ".gpkg", driver="GPKG") - print("pushed to geoserver") - return push_shape_to_geoserver( - path, workspace=workspace, layer_name=layer_name, file_type="gpkg" - ) - else: - print("no features found") - return - - -def to_camelcase(text): - words = text.split() - camelcase = words[0].lower() - for word in words[1:]: - camelcase += word.capitalize() - return camelcase - - -def create_chunk(aoi, description, chunk_size): - size = aoi.size().getInfo() - parts = size // chunk_size - # task_ids = [] - rois = [] - descs = [] - for part in range(parts + 1): - start = part * chunk_size - end = start + chunk_size - block_name_for_parts = description + "_" + str(start) + "-" + str(end) - roi = ee.FeatureCollection(aoi.toList(aoi.size()).slice(start, end)) - if roi.size().getInfo() > 0: - descs.append(block_name_for_parts) - rois.append(roi) - - return rois, descs - - -def merge_chunks( - aoi, - folder_list, - description, - chunk_size, - chunk_asset_path=GEE_HELPER_PATH, - merge_asset_path=GEE_ASSET_PATH, - merge_asset_id=None, -): - print("Merge Chunk task initiated") - ee_initialize() - size = aoi.size().getInfo() - parts = size // chunk_size - assets = [] - for part in range(parts + 1): - start = part * chunk_size - end = start + chunk_size - block_name_for_parts = description + "_" + str(start) + "-" + str(end) - src_asset_id = ( - get_gee_dir_path(folder_list, chunk_asset_path) + block_name_for_parts - ) - if is_gee_asset_exists(src_asset_id): - assets.append(ee.FeatureCollection(src_asset_id)) - - asset = ee.FeatureCollection(assets).flatten() - - asset_id = merge_asset_id or ( - get_gee_dir_path(folder_list, merge_asset_path) + description - ) - try: - # Export an ee.FeatureCollection as an Earth Engine asset. - task = ee.batch.Export.table.toAsset( - **{ - "collection": asset, - "description": description, - "assetId": asset_id, - } - ) - - task.start() - print("Successfully started the merge chunk", task.status()) - return task.status()["id"] - except Exception as e: - print(f"Error occurred in running merge task: {e}") - return None - - -def fix_invalid_geometry_in_gdf(gdf): - invalid = gdf[~gdf.is_valid] - if not invalid.empty: - print("Invalid geometries found:") - for idx, geom in invalid.geometry.items(): - print(f"Index {idx}: {explain_validity(geom)}") - gdf.loc[idx, "geometry"] = gdf.loc[idx, "geometry"].buffer(0) - - return gdf - - -def get_season_key(date): - """Return season key like 'rabi_2017-2018' based on Indian cropping seasons.""" - month = date.month - year = date.year - next_year = year + 1 - - if month in [1, 2]: - return f"rabi_{year - 1}-{year}" # Jan–Feb → Rabi of previous year - elif month in [11, 12]: - return f"rabi_{year}-{next_year}" # Nov–Dec → Rabi starting this year - elif month in [3, 4, 5, 6]: - return f"zaid_{year}-{next_year}" - elif month in [7, 8, 9, 10]: - return f"kharif_{year}-{next_year}" - else: - return None - - -def get_agri_year_key(season_key): - """Convert a season key to agricultural year key (e.g., rabi_2017-2018 → 2017-2018).""" - season, years = season_key.split("_") - start_year, end_year = map(int, years.split("-")) - - if season in ["kharif", "rabi"]: - return f"{start_year}-{end_year}" - elif season == "zaid": - return f"{start_year - 1}-{start_year}" # Zaid 2018-2019 → Agri year 2017-2018 - else: - return None - - -def calculate_precipitation_season( - geojson_filepath, draught_asset_id, start_year=2017, end_year=2024 -): - - # Load the GeoJSON file - with open(geojson_filepath, "r") as f: - feature_collection = json.load(f) - - features_ee = [] - - for feature in feature_collection["features"]: - original_props = feature["properties"] - new_props = {} - - # Copy UID - if "uid" in original_props: - new_props["uid"] = original_props["uid"] - - agri_year_totals = {} - - # Parse precipitation date keys - for key, val in original_props.items(): - try: - date = datetime.strptime(key, "%Y-%m-%d") - season_key = get_season_key(date) - if not season_key: - continue - - agri_key = get_agri_year_key(season_key) - if not agri_key: - continue - - agri_start = int(agri_key.split("-")[0]) - if not (start_year <= agri_start <= end_year): - continue - - season = season_key.split("_")[0] # kharif, rabi etc - full_key = f"{season}_{agri_key}" - - agri_year_totals[full_key] = agri_year_totals.get(full_key, 0) + float( - val - ) - - except Exception: - continue - - # Add all seasonal totals to new_props - for agri_key, total in agri_year_totals.items(): - new_props[f"precipitation_{agri_key}"] = total - - # Create EE Feature - geom_ee = ee.Geometry(feature["geometry"]) - feature_ee = ee.Feature(geom_ee, new_props) - features_ee.append(feature_ee) - - # Left side FC - mws_fc = ee.FeatureCollection(features_ee) - - return mws_fc - - -def generate_geojson_with_ci_and_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): - # Load project - proj_obj = Project.objects.get(pk=proj_id) - - # Build CI and NDVI asset paths - asset_path_ci = ( - get_gee_dir_path( - [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] - ) - + ci_asset - ) - - asset_path_ndvi = ( - get_gee_dir_path( - [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] - ) - + ndvi_asset - ) - - # Load FeatureCollections - zoi = ee.FeatureCollection(zoi_asset) - ci = ee.FeatureCollection(asset_path_ci) - ndvi = ee.FeatureCollection(asset_path_ndvi) - - # ------------------------- - # STEP 1: Join ZOI with Cropping Intensity - # ------------------------- - join = ee.Join.inner() - filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") - zoi_ci_joined = join.apply(zoi, ci, filter) - - def merge_zoi_ci(pair): - zoi_feat = ee.Feature(pair.get("primary")) - ci_feat = ee.Feature(pair.get("secondary")) - merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) - return ee.Feature(zoi_feat.geometry(), merged_props) - - zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) - - # ------------------------- - # STEP 2: Join ZOI+CI with NDVI - # ------------------------- - zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) - - def merge_zoi_ci_ndvi(pair): - ci_feat = ee.Feature(pair.get("primary")) - ndvi_feat = ee.Feature(pair.get("secondary")) - merged_props = ci_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) - return ee.Feature(ci_feat.geometry(), merged_props) - - final_merged = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) - - # ------------------------- - # STEP 3: Export or Push to GeoServer - # ------------------------- - layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" - sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") - - -def get_directory_size(path): - total_size = 0 - for dirpath, dirnames, filenames in os.walk(path): - for filename in filenames: - file_path = os.path.join(dirpath, filename) - if os.path.isfile(file_path): - total_size += os.path.getsize(file_path) - return total_size - - -def generate_geojson_with_ci_ndvi_ndmi( - zoi_asset, ci_asset, ndvi_asset, ndmi_asset, proj_id -): - - # Load project - proj_obj = Project.objects.get(pk=proj_id) - - zoi = ee.FeatureCollection(zoi_asset) - print("Number of features zoi:", zoi.size().getInfo()) - - ci = ee.FeatureCollection(ci_asset) - print("Number of features zoi:", ci.size().getInfo()) - ndvi = ee.FeatureCollection(ndmi_asset) - print("Number of features zoi:", ndvi.size().getInfo()) - ndmi = ee.FeatureCollection(ndmi_asset) - print("Number of features zoi:", ndmi.size().getInfo()) - - # ------------------------- - # STEP 1: Join ZOI with CI - # ------------------------- - join = ee.Join.inner() - filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") - zoi_ci_joined = join.apply(zoi, ci, filter) - - def merge_zoi_ci(pair): - zoi_feat = ee.Feature(pair.get("primary")) - ci_feat = ee.Feature(pair.get("secondary")) - merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) - return ee.Feature(zoi_feat.geometry(), merged_props) # ✅ keep ZOI geom - - zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) - - # ------------------------- - # STEP 2: Join with NDVI - # ------------------------- - zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) - - def merge_zoi_ci_ndvi(pair): - prev_feat = ee.Feature(pair.get("primary")) - ndvi_feat = ee.Feature(pair.get("secondary")) - merged_props = prev_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) - return ee.Feature(prev_feat.geometry(), merged_props) # ✅ still ZOI geom - - zoi_ci_ndvi = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) - - # ------------------------- - # STEP 3: Join with NDMI - # ------------------------- - zoi_ndmi_joined = join.apply(zoi_ci_ndvi, ndmi, filter) - - def merge_zoi_ci_ndvi_ndmi(pair): - prev_feat = ee.Feature(pair.get("primary")) - ndmi_feat = ee.Feature(pair.get("secondary")) - merged_props = prev_feat.toDictionary().combine(ndmi_feat.toDictionary(), True) - return ee.Feature(prev_feat.geometry(), merged_props) # ✅ keep ZOI geom - - final_merged = ee.FeatureCollection(zoi_ndmi_joined.map(merge_zoi_ci_ndvi_ndmi)) - - # ------------------------- - # STEP 4: Export or Push to GeoServer - # ------------------------- - layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" - print(layer_name) - sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") - - -def generate_geojson_with_ci_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): - # Load project - proj_obj = Project.objects.get(pk=proj_id) - - # Initialize Earth Engine - ee_initialize(4) - - # Load FeatureCollections - zoi = ee.FeatureCollection(zoi_asset) - ci = ee.FeatureCollection(ci_asset) - ndvi = ee.FeatureCollection(ndvi_asset) - - print("ZOI:", zoi.size().getInfo()) - print("CI:", ci.size().getInfo()) - print("NDVI:", ndvi.size().getInfo()) - - # Common join logic on UID - join = ee.Join.inner() - uid_filter = ee.Filter.equals(leftField="UID", rightField="UID") - - # --- Join ZOI + CI --- - zoi_ci_joined = join.apply(zoi, ci, uid_filter) - - def merge_zoi_ci(pair): - zoi_feat = ee.Feature(pair.get("primary")) - ci_feat = ee.Feature(pair.get("secondary")) - merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) - # Keep ZOI geometry only - return ee.Feature(zoi_feat.geometry(), merged_props) - - zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) - - # --- Join with NDVI --- - zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, uid_filter) - - def merge_with_ndvi(pair): - base_feat = ee.Feature(pair.get("primary")) - ndvi_feat = ee.Feature(pair.get("secondary")) - merged_props = base_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) - # Always retain ZOI geometry - return ee.Feature(base_feat.geometry(), merged_props) - - merged_final = ee.FeatureCollection(zoi_ndvi_joined.map(merge_with_ndvi)) - - # --- Ensure ZOI geometry retained in all features --- - merged_final = merged_final.map( - lambda f: ee.Feature( - f.setGeometry( - ee.Feature( - zoi.filter(ee.Filter.eq("UID", f.get("UID"))).first() - ).geometry() - ) - ) - ) - - layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" - print(layer_name) - - sync_project_fc_to_geoserver(merged_final, proj_obj.name, layer_name, "waterrej") - - -def save_layer_info_to_db( - state, - district, - block, - layer_name, - asset_id, - dataset_name, - sync_to_geoserver=False, - layer_version="1.0", - algorithm=None, - algorithm_version="1.0", - misc=None, - is_override=False, -): - print("inside the save_layer_info_to_db function") - - dataset = Dataset.objects.get(name=dataset_name) - - try: - state_obj = StateSOI.objects.get(state_name__iexact=state) - district_obj = DistrictSOI.objects.get( - district_name__iexact=district, state=state_obj - ) - block_obj = TehsilSOI.objects.get( - tehsil_name__iexact=block, district=district_obj - ) - except Exception as e: - print("Error fetching in state district block:", e) - return - - is_public = is_asset_public(asset_id) - - # Check if there’s an existing layer - existing_layer = ( - Layer.objects.filter( - dataset=dataset, - layer_name=layer_name, - state=state_obj, - district=district_obj, - block=block_obj, - ) - .order_by("-layer_version") - .first() - ) - - if existing_layer: - if existing_layer.algorithm_version != algorithm_version: - # Algorithm version changed --> create new record with incremented layer_version - new_layer_version = str(float(existing_layer.layer_version) + 1) - print( - f"Algorithm version changed. Creating new layer version: {new_layer_version}" - ) - layer_obj = Layer.objects.create( - dataset=dataset, - layer_name=layer_name, - state=state_obj, - district=district_obj, - block=block_obj, - layer_version=new_layer_version, - algorithm=algorithm, - algorithm_version=algorithm_version, - is_sync_to_geoserver=sync_to_geoserver, - is_public_gee_asset=is_public, - is_override=is_override, - misc=misc, - gee_asset_path=asset_id, - ) - else: - # Algorithm version is same --> update existing layer - print("Algorithm version same. Updating existing layer.") - for field, value in { - "algorithm": algorithm, - "algorithm_version": algorithm_version, - "is_sync_to_geoserver": sync_to_geoserver, - "is_public_gee_asset": is_public, - "is_override": is_override, - "misc": misc, - "gee_asset_path": asset_id, - }.items(): - setattr(existing_layer, field, value) - existing_layer.save() - layer_obj = existing_layer - else: - # No existing record --> create a new one - print("No existing layer found. Creating new one.") - layer_obj = Layer.objects.create( - dataset=dataset, - layer_name=layer_name, - state=state_obj, - district=district_obj, - block=block_obj, - layer_version=layer_version, - algorithm=algorithm, - algorithm_version=algorithm_version, - is_sync_to_geoserver=sync_to_geoserver, - is_public_gee_asset=is_public, - is_override=is_override, - misc=misc, - gee_asset_path=asset_id, - ) - - print(f"Saved layer info (id={layer_obj.id}, version={layer_obj.layer_version})") - return layer_obj.id - - -def get_existing_end_year(dataset_name, layer_name): - """fetch objects from db on the basis of dataset name and layer_name""" - dataset = Dataset.objects.get(name=dataset_name) - layer_obj = Layer.objects.get(dataset=dataset, layer_name=layer_name) - existing_end_date = layer_obj.misc["end_year"] - print("existing_end_date", existing_end_date) - return existing_end_date - - -def get_layer_object(state, district, block, layer_name, dataset_name): - state_obj = StateSOI.objects.get(state_name__iexact=state) - district_obj = DistrictSOI.objects.get( - district_name__iexact=district, state=state_obj - ) - block_obj = TehsilSOI.objects.get(tehsil_name__iexact=block, district=district_obj) - layer_obj = ( - Layer.objects.filter( - state=state_obj, - district=district_obj, - block=block_obj, - layer_name=layer_name, - dataset__name=dataset_name, - ) - .order_by("-layer_version") - .first() - ) - return layer_obj - - -def update_dashboard_geojson( - state=None, - district=None, - block=None, - layer_name=None, - workspace_name=None, - proj_id=None, -): - if state and block and block: - print(f"🔄 Updating GeoJSON for {state}, {district}, {block}") - - # Get related objects - state_obj = StateSOI.objects.get(state_name=state) - district_obj = DistrictSOI.objects.get(district_name=district) - tehsil_obj = TehsilSOI.objects.get(tehsil_name=block) # fixed typo - - # Get or create main record - obj, created = State_Disritct_Block_Properties.objects.get_or_create( - state=state_obj, district=district_obj, tehsil=tehsil_obj - ) - else: - obj = Project.objects.get(pk=proj_id) - - # Map suffix to json_key - suffix_to_key = { - "wb": "wb_geojson", - "zoi": "zoi_geojson", - "mws": "mws_geojson", - } - - # Detect which key this layer corresponds to - json_key = None - for suffix, key in suffix_to_key.items(): - if layer_name == f"{state}_{district}_{block}_{suffix}": - json_key = key - break - - if not json_key: - print(f"⚠️ Layer name {layer_name} did not match any known type.") - return - - # Construct GeoServer URL - waterrej_url = ( - f"https://geoserver.core-stack.org:8443/geoserver/waterrej/ows?" - f"service=WFS&version=1.0.0&request=GetFeature&typeName={workspace_name}:{layer_name}" - f"&outputFormat=application%2Fjson" - ) - - # Load existing dashboard_geojson or create new - if proj_id: - misc = obj.dashboard_geojson or {} - else: - misc = obj.geojson_path or {} - - # Ensure waterrej section exists - if "waterrej" not in misc: - misc["waterrej"] = {} - - # Update or add this specific json_key - misc["waterrej"][json_key] = waterrej_url - - # Save the updated JSON field - obj.dashboard_geojson = misc - obj.save() - - print(f"✅ Added/Updated {json_key} for {state}, {district}, {block}") - - -def clean_geometry(geom): - """ - Clean geometry: - - Dissolve multipolygon → single polygon - - Remove holes automatically - - Fix invalid topology - - Buffer tiny polygons - """ - - # 1. Dissolve multi-polygons and remove holes - geom = geom.dissolve(maxError=1) - - # 2. Fix invalid rings by simplifying slightly (NEVER buffer(0)) - geom = geom.simplify(1) - - # 3. Buffer polygons smaller than 1 pixel (< 900 m²) - area = geom.area() - geom = ee.Algorithms.If( - area.lt(900), - geom.buffer(15), - geom, # ensure raster pixel center is captured - ) - - return ee.Geometry(geom) - - -def safe_reduce_max(image, geom, scale=30): - geom = clean_geometry(geom) - - val = ( - image.unmask(0) - .reduceRegion( - reducer=ee.Reducer.max(), - geometry=geom, - scale=scale, - maxPixels=1e13, - tileScale=4, - bestEffort=True, - ) - .get("b1") - ) - - return ee.Number(ee.Algorithms.If(val, val, 0)) - - -# ------------------------------------------------------ -# SAFE REDUCE MAX FUNCTION -# ------------------------------------------------------ -def safe_reduce_max(image, geom, scale=30): - geom = clean_geometry(geom) - - result = ( - image.unmask(0) - .reduceRegion( - reducer=ee.Reducer.max(), - geometry=geom, - scale=scale, - maxPixels=1e13, - tileScale=4, - bestEffort=True, - ) - .get("b1") - ) - - # Convert null → 0 - return ee.Number(ee.Algorithms.If(result, result, 0)) - - -# ------------------------------------------------------ -# MAIN FUNCTION TO PROCESS SWB LAYER -# ------------------------------------------------------ -def generate_swb_layer_with_max_so_catchment( - roi=None, - app_type="MWS", - asset_suffix=None, - asset_folder=None, - gee_account_id=None, -): - ee_initialize(gee_account_id) - - # Build asset paths - base_path = get_gee_dir_path( - asset_folder, asset_path=GEE_PATHS[app_type]["GEE_ASSET_PATH"] - ) - - so_asset = f"{base_path}stream_order_{asset_suffix}_raster" - ca_asset = f"{base_path}catchment_area_{asset_suffix}_raster" - - # Load rasters - stream_order_band = ee.Image(so_asset).select("b1") - catchment_band = ee.Image(ca_asset).select("b1") - - # Processing per waterbody - def compute_for_feature(feature): - geom = feature.geometry() - - max_so = safe_reduce_max(stream_order_band, geom, scale=30) - max_ca = safe_reduce_max(catchment_band, geom, scale=30) - - return feature.set( - { - "max_stream_order": max_so, - "max_catchment_area": max_ca, - } - ) - - # Map over the feature collection - return roi.map(compute_for_feature) - - -def _get_prod_backend_url(): - return getattr(settings, "PROD_BACKEND_URL", "").rstrip("/") - - -def _get_prod_api_key(): - return getattr(settings, "PROD_BACKEND_API_KEY", "") - - -def _sync_layer_to_prod_db(payload: dict): - prod_url = _get_prod_backend_url() - if not prod_url: - return None - - endpoint = prod_url + "/api/v1/sync_layer_remote/" - try: - response = requests.post( - endpoint, - json=payload, - headers={"X-Api-Key": _get_prod_api_key()}, - timeout=30, - ) - if response.status_code not in (200, 201): - logger.warning( - "Prod DB sync returned %s for layer %s: %s", - response.status_code, - payload.get("layer_name"), - response.text, - ) - return None - layer_id = response.json().get("layer_id") - logger.info( - "Layer %s synced to prod DB (id=%s).", payload.get("layer_name"), layer_id - ) - return layer_id - except requests.RequestException as e: - logger.error( - "Failed to sync layer %s to prod DB: %s", payload.get("layer_name"), e - ) - return None - - -def _update_layer_sync_remote( - layer_id, sync_to_geoserver=None, is_stac_specs_generated=None -): - prod_url = _get_prod_backend_url() - if not prod_url or layer_id is None: - return - - endpoint = prod_url + "/api/v1/update_layer_sync_remote/" - payload = { - "layer_id": layer_id, - "sync_to_geoserver": sync_to_geoserver, - "is_stac_specs_generated": is_stac_specs_generated, - } - try: - response = requests.post( - endpoint, - json=payload, - headers={"X-Api-Key": _get_prod_api_key()}, - timeout=30, - ) - if response.status_code not in (200, 201): - logger.warning( - "Prod layer sync status update returned %s for layer %s: %s", - response.status_code, - layer_id, - response.text, - ) - else: - logger.info("Layer sync status updated on prod DB for id=%s.", layer_id) - except requests.RequestException as e: - logger.error( - "Failed to update layer sync status on prod DB for id=%s: %s", layer_id, e - ) - - -def update_layer_sync_status( - layer_id, sync_to_geoserver=None, is_stac_specs_generated=None -): - if _get_prod_backend_url(): - _update_layer_sync_remote( - layer_id, - sync_to_geoserver=sync_to_geoserver, - is_stac_specs_generated=is_stac_specs_generated, - ) - return layer_id - - try: - layer_obj = Layer.objects.filter(id=layer_id).first() - if layer_obj is None: - return None - - update_fields = [] - if sync_to_geoserver is not None: - layer_obj.is_sync_to_geoserver = sync_to_geoserver - update_fields.append("is_sync_to_geoserver") - if is_stac_specs_generated is not None: - layer_obj.is_stac_specs_generated = is_stac_specs_generated - update_fields.append("is_stac_specs_generated") - - # `save(update_fields=...)` fires the post_save signal so the STAC - # auto-trigger handler in `computing.signals` can pick up the flip. - if update_fields: - layer_obj.save(update_fields=update_fields) - print( - f"Updated {update_fields} for layer ID: {layer_id} " - f"(sync={sync_to_geoserver}, stac={is_stac_specs_generated})" - ) - return layer_id - - except Exception as e: - print(f"Error updating layer sync status: {e}") - - -# send missing layer to recipient email -def send_missing_layers_report(result: dict, recipients: list = None) -> bool: - if recipients is None: - recipients = getattr(settings, "MISSING_LAYER_RECIPIENTS", []) - - if isinstance(recipients, str): - recipients = [recipients] - - if not recipients: - logger.error("No recipients configured for missing layers report.") - return False - - summary = [] - total_missing = 0 - - for layer, data in result.items(): - count = len(data.get("missing_layers", [])) - total_missing += count - summary.append(f"{layer}: {count}") - body = ( - "Missing Layers Report\n\n" - f"Total Missing: {total_missing}\n\n" - + "\n".join(summary) - + "\n\nDetailed report attached." - ) - - attachment_content = json.dumps(result, indent=4) - max_retries = 3 - - for attempt in range(max_retries): - connection = None - try: - connection = get_connection(timeout=120) - connection.open() - email = EmailMessage( - subject="Missing Layers Report", - body=body, - from_email=settings.EMAIL_HOST_USER, - to=recipients, - connection=connection, - ) - email.attach( - "missing_layers.json", - attachment_content, - "application/json", - ) - email.send() - logger.info(f"Missing layers report sent to {recipients}") - logger.info( - f"Attachment size: " - f"{len(attachment_content.encode('utf-8')) / 1024:.2f} KB" - ) - return True - except Exception as e: - logger.exception(f"Attempt {attempt + 1}/{max_retries} failed: {e}") - if attempt < max_retries - 1: - wait_time = 5 * (attempt + 1) - logger.info(f"Retrying after {wait_time} seconds...") - time.sleep(wait_time) - else: - logger.error("All attempts to send email failed.") - return False - finally: - if connection: - try: - connection.close() - except Exception: - pass - - -def _is_cache_valid(cache: dict, workspace: str) -> bool: - if workspace not in cache: - return False - age = time.time() - cache[workspace]["cached_at"] - if age > 3600: - logger.info(f"Cache expired for {workspace} (age: {int(age)}s)") - return False - return True - - -def _set_cache(cache: dict, workspace: str, data: set): - cache[workspace] = { - "data": data, - "cached_at": time.time(), - } +import copy +import json +import logging +import os +import shutil +import zipfile +from datetime import datetime, timedelta + +import ee +import fiona +import geopandas as gpd +import requests +from django.conf import settings +from shapely.geometry import shape +from shapely.validation import explain_validity + +from computing.models import Dataset, Layer +from geoadmin.models import ( + DistrictSOI, + State_Disritct_Block_Properties, + StateSOI, + TehsilSOI, +) +from projects.models import Project +from utilities.constants import ( + ADMIN_BOUNDARY_OUTPUT_DIR, + GEE_ASSET_PATH, + GEE_HELPER_PATH, + GEE_PATHS, + SHAPEFILE_DIR, +) +from utilities.gee_utils import ( + check_task_status, + ee_initialize, + get_gee_asset_path, + get_gee_dir_path, + get_geojson_from_gcs, + is_asset_public, + is_gee_asset_exists, + sync_vector_to_gcs, + valid_gee_text, +) +from utilities.geoserver_utils import Geoserver +from django.core.mail import EmailMessage, get_connection +import time + +logger = logging.getLogger(__name__) + + +def generate_shape_files(path): + gdf = gpd.read_file(path + ".json") + if os.path.exists(path): + # Only replace the target shapefile directory. Removing the parent + # state/workspace directory here corrupts sibling outputs on reruns. + shutil.rmtree(path) + + os.makedirs(os.path.dirname(path), exist_ok=True) + gdf.to_file( + path, + driver="ESRI Shapefile", + ) + return path + + +def convert_to_zip(dir_name, file_type): + if file_type == "gpkg": + with zipfile.ZipFile(dir_name + ".zip", "w", zipfile.ZIP_DEFLATED) as zipf: + zipf.write(dir_name + ".gpkg", arcname=os.path.basename(dir_name + ".gpkg")) + return dir_name + ".zip" + else: + return shutil.make_archive(dir_name, "zip", dir_name + "/") + + +def push_shape_to_geoserver( + path, store_name=None, workspace=None, layer_name=None, file_type="shp" +): + geo = Geoserver() + + print(f"layer_name: {layer_name}") + if layer_name: + try: + print(f"Attempting to delete store: {layer_name}") + geo.delete_vector_store(workspace=workspace, store=layer_name) + print(f"Successfully deleted store: {layer_name}") + except Exception as e: + print(f"Store does not exist or error deleting: {str(e)}") + + zip_path = convert_to_zip(path, file_type) + print(f"Zip path: {zip_path}") + print(f"Store name: {store_name}") + print(f"Workspace: {workspace}") + + response = geo.create_shp_datastore( + path=zip_path, + store_name=store_name, + workspace=workspace, + file_extension=file_type, + ) + print(f"Response: {response}") + return response + + +def kml_to_geojson(state_name, district_name, block_name, kml_path): + fiona.drvsupport.supported_drivers["kml"] = ( + "rw" # enable KML support which is disabled by default + ) + fiona.drvsupport.supported_drivers["KML"] = ( + "rw" # enable KML support which is disabled by default + ) + gdf = gpd.read_file(kml_path) + geometry_types = gdf.geometry.geometry.type.unique() + state_dir = os.path.join(ADMIN_BOUNDARY_OUTPUT_DIR, state_name) + + for gtype in geometry_types: + df = gdf.loc[gdf.geometry.geometry.type == gtype] + path = os.path.join(state_dir, f"{district_name}_{block_name}_{gtype}") + df.to_file(path + ".json", driver="GeoJSON") + generate_shape_files(path) + push_shape_to_geoserver(path, workspace="test_workspace") + + +def convert_kml_to_shapefile(kml_path, output_dir, shapefile_name): + if not os.path.exists(output_dir + "/" + shapefile_name): + os.makedirs(output_dir + "/" + shapefile_name) + + shapefile_path = os.path.join( + output_dir + "/" + shapefile_name, shapefile_name + ".shp" + ) + print("path path", shapefile_path) + cmd = f"ogr2ogr -f 'ESRI Shapefile' {shapefile_path} {kml_path}" # output.shp input.kml + os.system(command=cmd) + + return output_dir + "/" + shapefile_name + + +def kml_to_shp(state_name, district_name, block_name, kml_path): + shapefile_name = f"{district_name}_{block_name}" + shapefile_layer_path = convert_kml_to_shapefile( + kml_path, SHAPEFILE_DIR, shapefile_name + ) + + push_shape_to_geoserver(shapefile_layer_path, workspace="customkml") + + # os.remove(kml_path) + # shutil.rmtree(shapefile_layer_path) + os.remove(shapefile_layer_path + ".zip") + + +def sync_layer_to_geoserver(state_name, fc, layer_name, workspace): + state_dir = os.path.join("data/fc_to_shape", state_name) + if not os.path.exists(state_dir): + os.mkdir(state_dir) + path = os.path.join(state_dir, f"{layer_name}") + # Write the feature collection into json file + with open(path + ".json", "w") as f: + try: + f.write(f"{json.dumps(fc)}") + except Exception as e: + print(e) + + path = generate_shape_files(path) + return push_shape_to_geoserver(path, workspace=workspace, layer_name=layer_name) + + +def sync_fc_to_geoserver(fc, shp_folder, layer_name, workspace, style_name=None): + try: + geojson_fc = fc.getInfo() + except Exception as e: + print("Exception in getInfo()", e) + task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") + check_task_status([task_id]) + + geojson_fc = get_geojson_from_gcs(layer_name) + geo = Geoserver() + if len(geojson_fc["features"]) > 0: + state_dir = os.path.join("data/fc_to_shape", shp_folder) + if not os.path.exists(state_dir): + os.mkdir(state_dir) + path = os.path.join(state_dir, f"{layer_name}") + + # Convert to GeoDataFrame + gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) + + # Set CRS (Earth Engine uses EPSG:4326 by default) + gdf.crs = "EPSG:4326" + + gdf = fix_invalid_geometry_in_gdf(gdf) + + # Save as GeoPackage + gdf.to_file(path + ".gpkg", driver="GPKG") + res = push_shape_to_geoserver(path, workspace=workspace, file_type="gpkg") + if style_name: + style_res = geo.publish_style( + layer_name=layer_name, style_name=style_name, workspace=workspace + ) + print("Style response:", style_res) + return res + else: + return "No features in FeatureCollection" + + +def sync_project_fc_to_geoserver(fc, project_name, layer_name, workspace): + print("inside") + print(layer_name) + try: + geojson_fc = fc.getInfo() + except Exception as e: + print("Exception in getInfo()", e) + task_id = sync_vector_to_gcs(fc, layer_name, "GeoJSON") + check_task_status([task_id]) + + geojson_fc = get_geojson_from_gcs(layer_name) + print(len(geojson_fc["features"])) + if len(geojson_fc["features"]) > 0: + state_dir = os.path.join("data/fc_to_shape", project_name) + if not os.path.exists(state_dir): + os.mkdir(state_dir) + path = os.path.join(state_dir, f"{layer_name}") + + # Convert to GeoDataFrame + gdf = gpd.GeoDataFrame.from_features(geojson_fc["features"]) + + # Set CRS (Earth Engine uses EPSG:4326 by default) + gdf.crs = "EPSG:4326" + + gdf = fix_invalid_geometry_in_gdf(gdf) + + # Save as GeoPackage + gdf.to_file(path + ".gpkg", driver="GPKG") + print("pushed to geoserver") + return push_shape_to_geoserver( + path, workspace=workspace, layer_name=layer_name, file_type="gpkg" + ) + else: + print("no features found") + return + + +def to_camelcase(text): + words = text.split() + camelcase = words[0].lower() + for word in words[1:]: + camelcase += word.capitalize() + return camelcase + + +def create_chunk(aoi, description, chunk_size): + size = aoi.size().getInfo() + parts = size // chunk_size + # task_ids = [] + rois = [] + descs = [] + for part in range(parts + 1): + start = part * chunk_size + end = start + chunk_size + block_name_for_parts = description + "_" + str(start) + "-" + str(end) + roi = ee.FeatureCollection(aoi.toList(aoi.size()).slice(start, end)) + if roi.size().getInfo() > 0: + descs.append(block_name_for_parts) + rois.append(roi) + + return rois, descs + + +def merge_chunks( + aoi, + folder_list, + description, + chunk_size, + chunk_asset_path=GEE_HELPER_PATH, + merge_asset_path=GEE_ASSET_PATH, + merge_asset_id=None, +): + print("Merge Chunk task initiated") + ee_initialize() + size = aoi.size().getInfo() + parts = size // chunk_size + assets = [] + for part in range(parts + 1): + start = part * chunk_size + end = start + chunk_size + block_name_for_parts = description + "_" + str(start) + "-" + str(end) + src_asset_id = ( + get_gee_dir_path(folder_list, chunk_asset_path) + block_name_for_parts + ) + if is_gee_asset_exists(src_asset_id): + assets.append(ee.FeatureCollection(src_asset_id)) + + asset = ee.FeatureCollection(assets).flatten() + + asset_id = merge_asset_id or ( + get_gee_dir_path(folder_list, merge_asset_path) + description + ) + try: + # Export an ee.FeatureCollection as an Earth Engine asset. + task = ee.batch.Export.table.toAsset( + **{ + "collection": asset, + "description": description, + "assetId": asset_id, + } + ) + + task.start() + print("Successfully started the merge chunk", task.status()) + return task.status()["id"] + except Exception as e: + print(f"Error occurred in running merge task: {e}") + return None + + +def fix_invalid_geometry_in_gdf(gdf): + invalid = gdf[~gdf.is_valid] + if not invalid.empty: + print("Invalid geometries found:") + for idx, geom in invalid.geometry.items(): + print(f"Index {idx}: {explain_validity(geom)}") + gdf.loc[idx, "geometry"] = gdf.loc[idx, "geometry"].buffer(0) + + return gdf + + +def get_season_key(date): + """Return season key like 'rabi_2017-2018' based on Indian cropping seasons.""" + month = date.month + year = date.year + next_year = year + 1 + + if month in [1, 2]: + return f"rabi_{year - 1}-{year}" # Jan–Feb → Rabi of previous year + elif month in [11, 12]: + return f"rabi_{year}-{next_year}" # Nov–Dec → Rabi starting this year + elif month in [3, 4, 5, 6]: + return f"zaid_{year}-{next_year}" + elif month in [7, 8, 9, 10]: + return f"kharif_{year}-{next_year}" + else: + return None + + +def get_agri_year_key(season_key): + """Convert a season key to agricultural year key (e.g., rabi_2017-2018 → 2017-2018).""" + season, years = season_key.split("_") + start_year, end_year = map(int, years.split("-")) + + if season in ["kharif", "rabi"]: + return f"{start_year}-{end_year}" + elif season == "zaid": + return f"{start_year - 1}-{start_year}" # Zaid 2018-2019 → Agri year 2017-2018 + else: + return None + + +def calculate_precipitation_season( + geojson_filepath, draught_asset_id, start_year=2017, end_year=2024 +): + + # Load the GeoJSON file + with open(geojson_filepath, "r") as f: + feature_collection = json.load(f) + + features_ee = [] + + for feature in feature_collection["features"]: + original_props = feature["properties"] + new_props = {} + + # Copy UID + if "uid" in original_props: + new_props["uid"] = original_props["uid"] + + agri_year_totals = {} + + # Parse precipitation date keys + for key, val in original_props.items(): + try: + date = datetime.strptime(key, "%Y-%m-%d") + season_key = get_season_key(date) + if not season_key: + continue + + agri_key = get_agri_year_key(season_key) + if not agri_key: + continue + + agri_start = int(agri_key.split("-")[0]) + if not (start_year <= agri_start <= end_year): + continue + + season = season_key.split("_")[0] # kharif, rabi etc + full_key = f"{season}_{agri_key}" + + agri_year_totals[full_key] = agri_year_totals.get(full_key, 0) + float( + val + ) + + except Exception: + continue + + # Add all seasonal totals to new_props + for agri_key, total in agri_year_totals.items(): + new_props[f"precipitation_{agri_key}"] = total + + # Create EE Feature + geom_ee = ee.Geometry(feature["geometry"]) + feature_ee = ee.Feature(geom_ee, new_props) + features_ee.append(feature_ee) + + # Left side FC + mws_fc = ee.FeatureCollection(features_ee) + + return mws_fc + + +def generate_geojson_with_ci_and_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): + # Load project + proj_obj = Project.objects.get(pk=proj_id) + + # Build CI and NDVI asset paths + asset_path_ci = ( + get_gee_dir_path( + [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] + ) + + ci_asset + ) + + asset_path_ndvi = ( + get_gee_dir_path( + [proj_obj.name], asset_path=GEE_PATHS["WATER_REJ"]["GEE_ASSET_PATH"] + ) + + ndvi_asset + ) + + # Load FeatureCollections + zoi = ee.FeatureCollection(zoi_asset) + ci = ee.FeatureCollection(asset_path_ci) + ndvi = ee.FeatureCollection(asset_path_ndvi) + + # ------------------------- + # STEP 1: Join ZOI with Cropping Intensity + # ------------------------- + join = ee.Join.inner() + filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") + zoi_ci_joined = join.apply(zoi, ci, filter) + + def merge_zoi_ci(pair): + zoi_feat = ee.Feature(pair.get("primary")) + ci_feat = ee.Feature(pair.get("secondary")) + merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) + return ee.Feature(zoi_feat.geometry(), merged_props) + + zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) + + # ------------------------- + # STEP 2: Join ZOI+CI with NDVI + # ------------------------- + zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) + + def merge_zoi_ci_ndvi(pair): + ci_feat = ee.Feature(pair.get("primary")) + ndvi_feat = ee.Feature(pair.get("secondary")) + merged_props = ci_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) + return ee.Feature(ci_feat.geometry(), merged_props) + + final_merged = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) + + # ------------------------- + # STEP 3: Export or Push to GeoServer + # ------------------------- + layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" + sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") + + +def get_directory_size(path): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(path): + for filename in filenames: + file_path = os.path.join(dirpath, filename) + if os.path.isfile(file_path): + total_size += os.path.getsize(file_path) + return total_size + + +def generate_geojson_with_ci_ndvi_ndmi( + zoi_asset, ci_asset, ndvi_asset, ndmi_asset, proj_id +): + + # Load project + proj_obj = Project.objects.get(pk=proj_id) + + zoi = ee.FeatureCollection(zoi_asset) + print("Number of features zoi:", zoi.size().getInfo()) + + ci = ee.FeatureCollection(ci_asset) + print("Number of features zoi:", ci.size().getInfo()) + ndvi = ee.FeatureCollection(ndmi_asset) + print("Number of features zoi:", ndvi.size().getInfo()) + ndmi = ee.FeatureCollection(ndmi_asset) + print("Number of features zoi:", ndmi.size().getInfo()) + + # ------------------------- + # STEP 1: Join ZOI with CI + # ------------------------- + join = ee.Join.inner() + filter = ee.Filter.intersects(leftField=".geo", rightField=".geo") + zoi_ci_joined = join.apply(zoi, ci, filter) + + def merge_zoi_ci(pair): + zoi_feat = ee.Feature(pair.get("primary")) + ci_feat = ee.Feature(pair.get("secondary")) + merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) + return ee.Feature(zoi_feat.geometry(), merged_props) # ✅ keep ZOI geom + + zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) + + # ------------------------- + # STEP 2: Join with NDVI + # ------------------------- + zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, filter) + + def merge_zoi_ci_ndvi(pair): + prev_feat = ee.Feature(pair.get("primary")) + ndvi_feat = ee.Feature(pair.get("secondary")) + merged_props = prev_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) + return ee.Feature(prev_feat.geometry(), merged_props) # ✅ still ZOI geom + + zoi_ci_ndvi = ee.FeatureCollection(zoi_ndvi_joined.map(merge_zoi_ci_ndvi)) + + # ------------------------- + # STEP 3: Join with NDMI + # ------------------------- + zoi_ndmi_joined = join.apply(zoi_ci_ndvi, ndmi, filter) + + def merge_zoi_ci_ndvi_ndmi(pair): + prev_feat = ee.Feature(pair.get("primary")) + ndmi_feat = ee.Feature(pair.get("secondary")) + merged_props = prev_feat.toDictionary().combine(ndmi_feat.toDictionary(), True) + return ee.Feature(prev_feat.geometry(), merged_props) # ✅ keep ZOI geom + + final_merged = ee.FeatureCollection(zoi_ndmi_joined.map(merge_zoi_ci_ndvi_ndmi)) + + # ------------------------- + # STEP 4: Export or Push to GeoServer + # ------------------------- + layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" + print(layer_name) + sync_project_fc_to_geoserver(final_merged, proj_obj.name, layer_name, "waterrej") + + +def generate_geojson_with_ci_ndvi(zoi_asset, ci_asset, ndvi_asset, proj_id): + # Load project + proj_obj = Project.objects.get(pk=proj_id) + + # Initialize Earth Engine + ee_initialize(4) + + # Load FeatureCollections + zoi = ee.FeatureCollection(zoi_asset) + ci = ee.FeatureCollection(ci_asset) + ndvi = ee.FeatureCollection(ndvi_asset) + + print("ZOI:", zoi.size().getInfo()) + print("CI:", ci.size().getInfo()) + print("NDVI:", ndvi.size().getInfo()) + + # Common join logic on UID + join = ee.Join.inner() + uid_filter = ee.Filter.equals(leftField="UID", rightField="UID") + + # --- Join ZOI + CI --- + zoi_ci_joined = join.apply(zoi, ci, uid_filter) + + def merge_zoi_ci(pair): + zoi_feat = ee.Feature(pair.get("primary")) + ci_feat = ee.Feature(pair.get("secondary")) + merged_props = zoi_feat.toDictionary().combine(ci_feat.toDictionary(), True) + # Keep ZOI geometry only + return ee.Feature(zoi_feat.geometry(), merged_props) + + zoi_with_ci = ee.FeatureCollection(zoi_ci_joined.map(merge_zoi_ci)) + + # --- Join with NDVI --- + zoi_ndvi_joined = join.apply(zoi_with_ci, ndvi, uid_filter) + + def merge_with_ndvi(pair): + base_feat = ee.Feature(pair.get("primary")) + ndvi_feat = ee.Feature(pair.get("secondary")) + merged_props = base_feat.toDictionary().combine(ndvi_feat.toDictionary(), True) + # Always retain ZOI geometry + return ee.Feature(base_feat.geometry(), merged_props) + + merged_final = ee.FeatureCollection(zoi_ndvi_joined.map(merge_with_ndvi)) + + # --- Ensure ZOI geometry retained in all features --- + merged_final = merged_final.map( + lambda f: ee.Feature( + f.setGeometry( + ee.Feature( + zoi.filter(ee.Filter.eq("UID", f.get("UID"))).first() + ).geometry() + ) + ) + ) + + layer_name = f"WaterRejapp_zoi_{proj_obj.name}_{proj_obj.id}" + print(layer_name) + + sync_project_fc_to_geoserver(merged_final, proj_obj.name, layer_name, "waterrej") + + +def save_layer_info_to_db( + state, + district, + block, + layer_name, + asset_id, + dataset_name, + sync_to_geoserver=False, + layer_version="1.0", + algorithm=None, + algorithm_version="1.0", + misc=None, + is_override=False, +): + print("inside the save_layer_info_to_db function") + + dataset = Dataset.objects.get(name=dataset_name) + + try: + state_obj = StateSOI.objects.get(state_name__iexact=state) + district_obj = DistrictSOI.objects.get( + district_name__iexact=district, state=state_obj + ) + block_obj = TehsilSOI.objects.get( + tehsil_name__iexact=block, district=district_obj + ) + except Exception as e: + print("Error fetching in state district block:", e) + return + + is_public = is_asset_public(asset_id) + + # Check if there’s an existing layer + existing_layer = ( + Layer.objects.filter( + dataset=dataset, + layer_name=layer_name, + state=state_obj, + district=district_obj, + block=block_obj, + ) + .order_by("-layer_version") + .first() + ) + + if existing_layer: + if existing_layer.algorithm_version != algorithm_version: + # Algorithm version changed --> create new record with incremented layer_version + new_layer_version = str(float(existing_layer.layer_version) + 1) + print( + f"Algorithm version changed. Creating new layer version: {new_layer_version}" + ) + layer_obj = Layer.objects.create( + dataset=dataset, + layer_name=layer_name, + state=state_obj, + district=district_obj, + block=block_obj, + layer_version=new_layer_version, + algorithm=algorithm, + algorithm_version=algorithm_version, + is_sync_to_geoserver=sync_to_geoserver, + is_public_gee_asset=is_public, + is_override=is_override, + misc=misc, + gee_asset_path=asset_id, + ) + else: + # Algorithm version is same --> update existing layer + print("Algorithm version same. Updating existing layer.") + for field, value in { + "algorithm": algorithm, + "algorithm_version": algorithm_version, + "is_sync_to_geoserver": sync_to_geoserver, + "is_public_gee_asset": is_public, + "is_override": is_override, + "misc": misc, + "gee_asset_path": asset_id, + }.items(): + setattr(existing_layer, field, value) + existing_layer.save() + layer_obj = existing_layer + else: + # No existing record --> create a new one + print("No existing layer found. Creating new one.") + layer_obj = Layer.objects.create( + dataset=dataset, + layer_name=layer_name, + state=state_obj, + district=district_obj, + block=block_obj, + layer_version=layer_version, + algorithm=algorithm, + algorithm_version=algorithm_version, + is_sync_to_geoserver=sync_to_geoserver, + is_public_gee_asset=is_public, + is_override=is_override, + misc=misc, + gee_asset_path=asset_id, + ) + + print(f"Saved layer info (id={layer_obj.id}, version={layer_obj.layer_version})") + return layer_obj.id + + +def get_existing_end_year(dataset_name, layer_name): + """fetch objects from db on the basis of dataset name and layer_name""" + dataset = Dataset.objects.get(name=dataset_name) + layer_obj = Layer.objects.get(dataset=dataset, layer_name=layer_name) + existing_end_date = layer_obj.misc["end_year"] + print("existing_end_date", existing_end_date) + return existing_end_date + + +def get_layer_object(state, district, block, layer_name, dataset_name): + state_obj = StateSOI.objects.get(state_name__iexact=state) + district_obj = DistrictSOI.objects.get( + district_name__iexact=district, state=state_obj + ) + block_obj = TehsilSOI.objects.get(tehsil_name__iexact=block, district=district_obj) + layer_obj = ( + Layer.objects.filter( + state=state_obj, + district=district_obj, + block=block_obj, + layer_name=layer_name, + dataset__name=dataset_name, + ) + .order_by("-layer_version") + .first() + ) + return layer_obj + + +def update_dashboard_geojson( + state=None, + district=None, + block=None, + layer_name=None, + workspace_name=None, + proj_id=None, +): + if state and block and block: + print(f"🔄 Updating GeoJSON for {state}, {district}, {block}") + + # Get related objects + state_obj = StateSOI.objects.get(state_name=state) + district_obj = DistrictSOI.objects.get(district_name=district) + tehsil_obj = TehsilSOI.objects.get(tehsil_name=block) # fixed typo + + # Get or create main record + obj, created = State_Disritct_Block_Properties.objects.get_or_create( + state=state_obj, district=district_obj, tehsil=tehsil_obj + ) + else: + obj = Project.objects.get(pk=proj_id) + + # Map suffix to json_key + suffix_to_key = { + "wb": "wb_geojson", + "zoi": "zoi_geojson", + "mws": "mws_geojson", + } + + # Detect which key this layer corresponds to + json_key = None + for suffix, key in suffix_to_key.items(): + if layer_name == f"{state}_{district}_{block}_{suffix}": + json_key = key + break + + if not json_key: + print(f"⚠️ Layer name {layer_name} did not match any known type.") + return + + # Construct GeoServer URL + waterrej_url = ( + f"https://geoserver.core-stack.org:8443/geoserver/waterrej/ows?" + f"service=WFS&version=1.0.0&request=GetFeature&typeName={workspace_name}:{layer_name}" + f"&outputFormat=application%2Fjson" + ) + + # Load existing dashboard_geojson or create new + if proj_id: + misc = obj.dashboard_geojson or {} + else: + misc = obj.geojson_path or {} + + # Ensure waterrej section exists + if "waterrej" not in misc: + misc["waterrej"] = {} + + # Update or add this specific json_key + misc["waterrej"][json_key] = waterrej_url + + # Save the updated JSON field + obj.dashboard_geojson = misc + obj.save() + + print(f"✅ Added/Updated {json_key} for {state}, {district}, {block}") + + +def clean_geometry(geom): + """ + Clean geometry: + - Dissolve multipolygon → single polygon + - Remove holes automatically + - Fix invalid topology + - Buffer tiny polygons + """ + + # 1. Dissolve multi-polygons and remove holes + geom = geom.dissolve(maxError=1) + + # 2. Fix invalid rings by simplifying slightly (NEVER buffer(0)) + geom = geom.simplify(1) + + # 3. Buffer polygons smaller than 1 pixel (< 900 m²) + area = geom.area() + geom = ee.Algorithms.If( + area.lt(900), + geom.buffer(15), + geom, # ensure raster pixel center is captured + ) + + return ee.Geometry(geom) + + +def safe_reduce_max(image, geom, scale=30): + geom = clean_geometry(geom) + + val = ( + image.unmask(0) + .reduceRegion( + reducer=ee.Reducer.max(), + geometry=geom, + scale=scale, + maxPixels=1e13, + tileScale=4, + bestEffort=True, + ) + .get("b1") + ) + + return ee.Number(ee.Algorithms.If(val, val, 0)) + + +# ------------------------------------------------------ +# SAFE REDUCE MAX FUNCTION +# ------------------------------------------------------ +def safe_reduce_max(image, geom, scale=30): + geom = clean_geometry(geom) + + result = ( + image.unmask(0) + .reduceRegion( + reducer=ee.Reducer.max(), + geometry=geom, + scale=scale, + maxPixels=1e13, + tileScale=4, + bestEffort=True, + ) + .get("b1") + ) + + # Convert null → 0 + return ee.Number(ee.Algorithms.If(result, result, 0)) + + +# ------------------------------------------------------ +# MAIN FUNCTION TO PROCESS SWB LAYER +# ------------------------------------------------------ +def generate_swb_layer_with_max_so_catchment( + roi=None, + app_type="MWS", + asset_suffix=None, + asset_folder=None, + gee_account_id=None, +): + ee_initialize(gee_account_id) + + # Build asset paths + base_path = get_gee_dir_path( + asset_folder, asset_path=GEE_PATHS[app_type]["GEE_ASSET_PATH"] + ) + + so_asset = f"{base_path}stream_order_{asset_suffix}_raster" + ca_asset = f"{base_path}catchment_area_{asset_suffix}_raster" + + # Load rasters + stream_order_band = ee.Image(so_asset).select("b1") + catchment_band = ee.Image(ca_asset).select("b1") + + # Processing per waterbody + def compute_for_feature(feature): + geom = feature.geometry() + + max_so = safe_reduce_max(stream_order_band, geom, scale=30) + max_ca = safe_reduce_max(catchment_band, geom, scale=30) + + return feature.set( + { + "max_stream_order": max_so, + "max_catchment_area": max_ca, + } + ) + + # Map over the feature collection + return roi.map(compute_for_feature) + + +def _get_prod_backend_url(): + return getattr(settings, "PROD_BACKEND_URL", "").rstrip("/") + + +def _get_prod_api_key(): + return getattr(settings, "PROD_BACKEND_API_KEY", "") + + +def _sync_layer_to_prod_db(payload: dict): + prod_url = _get_prod_backend_url() + if not prod_url: + return None + + endpoint = prod_url + "/api/v1/sync_layer_remote/" + try: + response = requests.post( + endpoint, + json=payload, + headers={"X-Api-Key": _get_prod_api_key()}, + timeout=30, + ) + if response.status_code not in (200, 201): + logger.warning( + "Prod DB sync returned %s for layer %s: %s", + response.status_code, + payload.get("layer_name"), + response.text, + ) + return None + layer_id = response.json().get("layer_id") + logger.info( + "Layer %s synced to prod DB (id=%s).", payload.get("layer_name"), layer_id + ) + return layer_id + except requests.RequestException as e: + logger.error( + "Failed to sync layer %s to prod DB: %s", payload.get("layer_name"), e + ) + return None + + +def _update_layer_sync_remote( + layer_id, sync_to_geoserver=None, is_stac_specs_generated=None +): + prod_url = _get_prod_backend_url() + if not prod_url or layer_id is None: + return + + endpoint = prod_url + "/api/v1/update_layer_sync_remote/" + payload = { + "layer_id": layer_id, + "sync_to_geoserver": sync_to_geoserver, + "is_stac_specs_generated": is_stac_specs_generated, + } + try: + response = requests.post( + endpoint, + json=payload, + headers={"X-Api-Key": _get_prod_api_key()}, + timeout=30, + ) + if response.status_code not in (200, 201): + logger.warning( + "Prod layer sync status update returned %s for layer %s: %s", + response.status_code, + layer_id, + response.text, + ) + else: + logger.info("Layer sync status updated on prod DB for id=%s.", layer_id) + except requests.RequestException as e: + logger.error( + "Failed to update layer sync status on prod DB for id=%s: %s", layer_id, e + ) + + +def update_layer_sync_status( + layer_id, sync_to_geoserver=None, is_stac_specs_generated=None +): + if _get_prod_backend_url(): + _update_layer_sync_remote( + layer_id, + sync_to_geoserver=sync_to_geoserver, + is_stac_specs_generated=is_stac_specs_generated, + ) + return layer_id + + try: + layer_obj = Layer.objects.filter(id=layer_id).first() + if layer_obj is None: + return None + + update_fields = [] + if sync_to_geoserver is not None: + layer_obj.is_sync_to_geoserver = sync_to_geoserver + update_fields.append("is_sync_to_geoserver") + if is_stac_specs_generated is not None: + layer_obj.is_stac_specs_generated = is_stac_specs_generated + update_fields.append("is_stac_specs_generated") + + # `save(update_fields=...)` fires the post_save signal so the STAC + # auto-trigger handler in `computing.signals` can pick up the flip. + if update_fields: + layer_obj.save(update_fields=update_fields) + print( + f"Updated {update_fields} for layer ID: {layer_id} " + f"(sync={sync_to_geoserver}, stac={is_stac_specs_generated})" + ) + return layer_id + + except Exception as e: + print(f"Error updating layer sync status: {e}") + + +# send missing layer to recipient email +def send_missing_layers_report(result: dict, recipients: list = None) -> bool: + if recipients is None: + recipients = getattr(settings, "MISSING_LAYER_RECIPIENTS", []) + + if isinstance(recipients, str): + recipients = [recipients] + + if not recipients: + logger.error("No recipients configured for missing layers report.") + return False + + summary = [] + total_missing = 0 + + for layer, data in result.items(): + count = len(data.get("missing_layers", [])) + total_missing += count + summary.append(f"{layer}: {count}") + body = ( + "Missing Layers Report\n\n" + f"Total Missing: {total_missing}\n\n" + + "\n".join(summary) + + "\n\nDetailed report attached." + ) + + attachment_content = json.dumps(result, indent=4) + max_retries = 3 + + for attempt in range(max_retries): + connection = None + try: + connection = get_connection(timeout=120) + connection.open() + email = EmailMessage( + subject="Missing Layers Report", + body=body, + from_email=settings.EMAIL_HOST_USER, + to=recipients, + connection=connection, + ) + email.attach( + "missing_layers.json", + attachment_content, + "application/json", + ) + email.send() + logger.info(f"Missing layers report sent to {recipients}") + logger.info( + f"Attachment size: " + f"{len(attachment_content.encode('utf-8')) / 1024:.2f} KB" + ) + return True + except Exception as e: + logger.exception(f"Attempt {attempt + 1}/{max_retries} failed: {e}") + if attempt < max_retries - 1: + wait_time = 5 * (attempt + 1) + logger.info(f"Retrying after {wait_time} seconds...") + time.sleep(wait_time) + else: + logger.error("All attempts to send email failed.") + return False + finally: + if connection: + try: + connection.close() + except Exception: + pass + + +def _is_cache_valid(cache: dict, workspace: str) -> bool: + if workspace not in cache: + return False + age = time.time() - cache[workspace]["cached_at"] + if age > 3600: + logger.info(f"Cache expired for {workspace} (age: {int(age)}s)") + return False + return True + + +def _set_cache(cache: dict, workspace: str, data: set): + cache[workspace] = { + "data": data, + "cached_at": time.time(), + } diff --git a/computing/views.py b/computing/views.py index 1d22bb02..3f3595c8 100644 --- a/computing/views.py +++ b/computing/views.py @@ -1,479 +1,479 @@ -import requests -from nrm_app.settings import GEOSERVER_URL -from utilities.gee_utils import valid_gee_text -import xml.etree.ElementTree as ET -from lxml import etree as LET -from nrm_app.celery import app -from computing.models import * -from utilities.geoserver_utils import Geoserver -import json -from django.conf import settings -from pathlib import Path -from utilities.constants import GEOSERVER_BASE -from utilities.logger import setup_logger -from concurrent.futures import ThreadPoolExecutor, as_completed -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry -from rest_framework.response import Response -from rest_framework import status -from .utils import send_missing_layers_report, _is_cache_valid, _set_cache - -logger = setup_logger(__name__) - - -def get_url(geoserver_url, workspace, layer_name): - return ( - f"{geoserver_url}/{workspace}/ows" - f"?service=WFS" - f"&version=1.1.0" - f"&request=GetFeature" - f"&typeName={workspace}:{layer_name}" - f"&resultType=hits" - ) - - -def load_workspace_config(): - """ - Load workspace configuration from JSON file. - """ - config_path = ( - Path(settings.BASE_DIR) - / "data" - / "layers" - / "layer_status" - / "layer_mapping.json" - ) - with open(config_path, "r") as f: - return json.load(f) - - -@app.task(bind=True) -def layer_status(self, state, district, block): - """ - Check the status of all layers for a particular location. - - Args: - self: Instance reference - state: State name - district: District name - block: Block name - - Returns: - Dictionary with status of all workspace layers - """ - print(f"{state=}") - all_workspace_statuses = {} - capabilities_cache = {} - district = valid_gee_text(district.lower()) - block = valid_gee_text(block.lower()) - - # Load workspace configuration from JSON - workspace_config = load_workspace_config() - - for workspace_display, config in workspace_config.items(): - workspace = config.get("name") - suffix = config.get("suffix", "") - prefix = config.get("prefix", "") - layer_type = config.get("type", "") - - # constructing layer name - layer_name_parts = [prefix, district, block, suffix] - layer_name = "_".join(part for part in layer_name_parts if part) - - total_features = 0 - end_date = None - start_date = None - status_code = 400 - # checking for vector layer - if layer_type == "vector": - layer_url = get_url(GEOSERVER_URL, workspace, layer_name) - res_layer_url = requests.get(layer_url) - - if res_layer_url.status_code == 200: - try: - root = ET.fromstring(res_layer_url.text) - # Extract feature count from WFS hits response - total_features = int(root.attrib.get("numberOfFeatures", 0)) - status_code = 200 if total_features > 0 else 400 - layer = ( - Layer.objects.filter(layer_name=layer_name) - .order_by("-layer_version") - .first() - ) - if layer and layer.misc: - start_date = layer.misc.get("start_date") - end_date = layer.misc.get("end_date") - - except ET.ParseError: - print(f"Invalid XML for layer: {layer_name}") - status_code = 400 - else: - if workspace not in capabilities_cache: - capabilities_url = f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" - try: - response = requests.get(capabilities_url, timeout=30) - if response.status_code == 200: - parser = LET.XMLParser(recover=True, encoding="utf-8") - root = LET.fromstring(response.content, parser=parser) - ns = {"wms": root.tag.split("}")[0].strip("{")} - layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) - capabilities_cache[workspace] = { - layer.text for layer in layers if layer.text - } - else: - capabilities_cache[workspace] = set() - except Exception as e: - print( - f"Failed to fetch capabilities for workspace {workspace}: {e}" - ) - capabilities_cache[workspace] = set() - - if layer_name in capabilities_cache.get(workspace, set()): - status_code = 200 - all_workspace_statuses[workspace_display] = { - "workspace": workspace, - "layer_name": layer_name, - "status_code": status_code, - "totalFeature": total_features, - "endDate": end_date, - "startDate": start_date, - } - - return all_workspace_statuses - - -def load_workspace_types(): - """ - Load workspace types configuration from JSON file. - """ - config_path = ( - Path(settings.BASE_DIR) - / "data" - / "layers" - / "workspace_layers" - / "layers_in_workspace.json" - ) - with open(config_path, "r") as f: - return json.load(f) - - -@app.task(bind=True) -def get_layers_of_workspace(self, workspace): - """ - It will take workspace as argument and returns all the layers which is present on geoserver. - """ - # Load workspace types from JSON - workspace_types = load_workspace_types() - raster_workspace = workspace_types["raster_workspace"] - vector_workspace = workspace_types["vector_workspace"] - raster_and_vector_workspace = workspace_types["raster_and_vector_workspace"] - - geo = Geoserver() - layers = geo.get_layers(workspace) - layer_names = [layer["name"] for layer in layers["layers"]["layer"]] - if workspace in raster_workspace: - print("you passed raster workspace") - available_layers = valid_raster_layers_for_workspace(workspace) - valid_layers = [ln for ln in layer_names if ln in available_layers] - invalid_layers = [ln for ln in layer_names if ln not in available_layers] - return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} - elif workspace in vector_workspace: - print("you passed vector workspace") - valid_layers = [] - invalid_layers = [] - for layer_name in layer_names: - if is_valid_vector_layer(workspace, layer_name): - valid_layers.append(layer_name) - else: - invalid_layers.append(layer_name) - return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} - elif workspace in raster_and_vector_workspace: - print("you passed workspace which contain both layers(raster and vector)") - valid_layers = [] - invalid_layers = [] - for layer_name in layer_names: - if "vector" in layer_name.lower(): - if is_valid_vector_layer(workspace, layer_name): - valid_layers.append(layer_name) - else: - invalid_layers.append(layer_name) - elif "raster" in layer_name.lower(): - available_layers = valid_raster_layers_for_workspace(workspace) - if layer_name in available_layers: - valid_layers.append(layer_name) - else: - invalid_layers.append(layer_name) - return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} - else: - print("you passed wrong workspace") - return [] - - -_raster_cache = {} -_vector_cache = {} - - -def valid_raster_layers_for_workspace(workspace: str) -> set: - if _is_cache_valid(_raster_cache, workspace): - logger.info(f"Cache hit for raster: {workspace}") - return _raster_cache[workspace]["data"] - - session = get_session_with_retry() - capabilities_url = ( - f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" - ) - try: - response = session.get(capabilities_url, timeout=(5, 15)) - response.raise_for_status() - except requests.exceptions.Timeout: - logger.warning( - f"Timeout fetching raster capabilities for {workspace} — not caching" - ) - return set() - except requests.exceptions.RequestException as e: - logger.error(f"Failed raster capabilities for {workspace}: {e} — not caching") - return set() - - root = ET.fromstring(response.content) - ns = {"wms": root.tag.split("}")[0].strip("{")} - layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) - result = {layer.text for layer in layers} - - _set_cache(_raster_cache, workspace, result) # cache with timestamp - logger.info(f"Cached raster layers for {workspace}: {len(result)} layers") - return result - - -def valid_vector_layers_for_workspace(workspace: str) -> set: - if _is_cache_valid(_vector_cache, workspace): - logger.info(f"Cache hit for vector: {workspace}") - return _vector_cache[workspace]["data"] - - session = get_session_with_retry() - capabilities_url = ( - f"{GEOSERVER_BASE}{workspace}/wfs" - f"?service=WFS&version=2.0.0&request=GetCapabilities" - ) - try: - response = session.get(capabilities_url, timeout=(5, 15)) - response.raise_for_status() - except requests.exceptions.Timeout: - logger.warning( - f"Timeout fetching vector capabilities for {workspace} — not caching" - ) - return set() - except requests.exceptions.RequestException as e: - logger.error(f"Failed vector capabilities for {workspace}: {e} — not caching") - return set() - - root = ET.fromstring(response.content) - ns = {"wfs": "http://www.opengis.net/wfs/2.0"} - names = root.findall(".//wfs:FeatureType/wfs:Name", namespaces=ns) - result = {name.text.split(":")[-1] for name in names} - - _set_cache(_vector_cache, workspace, result) # cache with timestamp - logger.info(f"Cached vector layers for {workspace}: {len(result)} layers") - return result - - -def is_valid_vector_layer(workspace: str, layer_name: str) -> bool: - """Used in parallel fallback only.""" - session = get_session_with_retry() - try: - layer_url = get_url(GEOSERVER_URL, workspace, layer_name) - res = session.get(layer_url, timeout=(5, 10)) # shorter timeout per layer - if res.status_code == 200: - root = ET.fromstring(res.text) - return int(root.attrib.get("numberOfFeatures", 0)) > 0 - except requests.exceptions.Timeout: - logger.warning(f"Timeout checking vector layer: {layer_name}") - except Exception as e: - logger.warning(f"Vector check failed for {layer_name}: {e}") - return False - - -def bulk_check_vector_layers( - workspace: str, - layer_names: list[str], - max_workers: int = 20, -) -> dict[str, bool]: - """ - Checks multiple vector layers concurrently using a thread pool. - Returns {layer_name: is_valid} mapping. - """ - results = {} - with ThreadPoolExecutor(max_workers=max_workers) as executor: - future_to_layer = { - executor.submit(is_valid_vector_layer, workspace, name): name - for name in layer_names - } - for future in as_completed(future_to_layer): - layer_name = future_to_layer[future] - try: - results[layer_name] = future.result() - except Exception as e: - logger.warning(f"Failed check for {layer_name}: {e}") - results[layer_name] = False - return results - - -@app.task(bind=True) -def missing_layer_for_all_workspace(self): - workspaces = ( - Dataset.objects.filter(workspace__isnull=False) - .values_list("workspace", flat=True) - .distinct() - ) - workspaces = [w.strip() for w in workspaces if w and w.strip()] - result = {} - for workspace in workspaces: - result[workspace] = check_missing_layers(workspace) - send_missing_layers_report(result) - return result - - -def check_missing_layers(workspace: str) -> dict: - logger.info(f"{workspace=}") - workspace_config = load_workspace_config() - workspace_types = get_workspace_types(workspace) - logger.info(f"Found types: {workspace_types}") - - if not workspace_types: - logger.critical(f"No config found for workspace: {workspace}") - return {"no config found": []} - - layer_config = get_layer_config_by_type( - workspace_config, workspace, workspace_types - ) - - # ── Fetch all available layers ONCE (bulk, cached) ────────────────────── - available_raster_layers = valid_raster_layers_for_workspace(workspace) - available_vector_layers = valid_vector_layers_for_workspace(workspace) # bulk WFS` - use_bulk_vector = bool(available_vector_layers) # fallback if WFS unavailable - - # ── Pre-build all (tehsil, layer_type, layer_name, state) combos ──────── - active_tehsils = TehsilSOI.objects.select_related( - "district__state" # eliminates N+1 DB queries - ).filter(active_status=True) - - tasks = [] # (state, layer_type, layer_name) - for tehsil_obj in active_tehsils: - state = tehsil_obj.district.state.state_name - district_name = valid_gee_text(tehsil_obj.district.district_name.lower()) - tehsil_name = valid_gee_text(tehsil_obj.tehsil_name.lower()) - - for layer_type, configs in layer_config.items(): - for config in configs: - prefix = config.get("prefix") - suffix = config.get("suffix") - layer_name = "_".join( - p for p in [prefix, district_name, tehsil_name, suffix] if p - ) - tasks.append( - (state, district_name, tehsil_name, layer_type, layer_name) - ) - - # ── Check raster layers (pure set lookup, no HTTP) ─────────────────────── - missing_layers = [] - vector_tasks = [] # collect for batch/parallel processing - - for state, district_name, tehsil_name, layer_type, layer_name in tasks: - if layer_type == "raster": - if layer_name not in available_raster_layers: - missing_layers.append(f"{state}, {district_name}, {tehsil_name}") - - elif layer_type == "vector": - if use_bulk_vector: - # O(1) set lookup — no HTTP call needed - if layer_name not in available_vector_layers: - missing_layers.append(f"{state}, {district_name}, {tehsil_name}") - else: - vector_tasks.append( - (state, district_name, tehsil_name, layer_name) - ) # queue for parallel check - - # ── Parallel fallback for vector layers (if WFS bulk fetch failed) ─────── - if vector_tasks: - layer_names = [ln for _, _, _, ln in vector_tasks] - info_by_name = {ln: (st, dn, tn) for st, dn, tn, ln in vector_tasks} - validity = bulk_check_vector_layers(workspace, layer_names) - - for layer_name, is_valid in validity.items(): - if not is_valid: - state, district_name, tehsil_name = info_by_name[layer_name] - missing_layers.append(f"{state}, {district_name}, {tehsil_name}") - - return {"missing_layers": missing_layers} - - -def get_workspace_types(workspace_name): - """ - Return all layer types for a given workspace name from DB. - Example: ['raster', 'vector'] - """ - return list( - Dataset.objects.filter(workspace=workspace_name) - .values_list("layer_type", flat=True) - .distinct() - ) - - -def get_layer_config_by_type(workspace_config, workspace_name, layer_types): - """ - Return list of prefix/suffix configs for each layer type. - - Args: - workspace_config (dict): config JSON - workspace_name (str): dataset name - layer_types (list): ['raster', 'vector'] - - Returns: - dict: {'raster': [{'prefix': ..., 'suffix': ...}, ...], 'vector': [...]} - """ - result = {} - - for config in workspace_config.values(): - if config.get("name") == workspace_name and config.get("type") in layer_types: - layer_type = config["type"] - - if layer_type not in result: - result[layer_type] = [] - - result[layer_type].append( - { - "prefix": config.get("prefix"), - "suffix": config.get("suffix"), - } - ) - - return result - - -def get_session_with_retry(): - """Creates a requests session with retry and timeout handling.""" - session = requests.Session() - retry = Retry( - total=3, - backoff_factor=1, # waits 1s, 2s, 4s between retries - status_forcelist=[500, 502, 503, 504], - ) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - return session - - -def clear_layer_cache(workspace: str = None): - if workspace: - _raster_cache.pop(workspace, None) - _vector_cache.pop(workspace, None) - logger.info(f"Cleared cache for {workspace}") - else: - _raster_cache.clear() - _vector_cache.clear() - logger.info("Cleared all layer caches") - - -def refresh_layer_cache(request, workspace=None): - clear_layer_cache(workspace) - return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) +import requests +from nrm_app.settings import GEOSERVER_URL +from utilities.gee_utils import valid_gee_text +import xml.etree.ElementTree as ET +from lxml import etree as LET +from nrm_app.celery import app +from computing.models import * +from utilities.geoserver_utils import Geoserver +import json +from django.conf import settings +from pathlib import Path +from utilities.constants import GEOSERVER_BASE +from utilities.logger import setup_logger +from concurrent.futures import ThreadPoolExecutor, as_completed +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +from rest_framework.response import Response +from rest_framework import status +from .utils import send_missing_layers_report, _is_cache_valid, _set_cache + +logger = setup_logger(__name__) + + +def get_url(geoserver_url, workspace, layer_name): + return ( + f"{geoserver_url}/{workspace}/ows" + f"?service=WFS" + f"&version=1.1.0" + f"&request=GetFeature" + f"&typeName={workspace}:{layer_name}" + f"&resultType=hits" + ) + + +def load_workspace_config(): + """ + Load workspace configuration from JSON file. + """ + config_path = ( + Path(settings.BASE_DIR) + / "data" + / "layers" + / "layer_status" + / "layer_mapping.json" + ) + with open(config_path, "r") as f: + return json.load(f) + + +@app.task(bind=True) +def layer_status(self, state, district, block): + """ + Check the status of all layers for a particular location. + + Args: + self: Instance reference + state: State name + district: District name + block: Block name + + Returns: + Dictionary with status of all workspace layers + """ + print(f"{state=}") + all_workspace_statuses = {} + capabilities_cache = {} + district = valid_gee_text(district.lower()) + block = valid_gee_text(block.lower()) + + # Load workspace configuration from JSON + workspace_config = load_workspace_config() + + for workspace_display, config in workspace_config.items(): + workspace = config.get("name") + suffix = config.get("suffix", "") + prefix = config.get("prefix", "") + layer_type = config.get("type", "") + + # constructing layer name + layer_name_parts = [prefix, district, block, suffix] + layer_name = "_".join(part for part in layer_name_parts if part) + + total_features = 0 + end_date = None + start_date = None + status_code = 400 + # checking for vector layer + if layer_type == "vector": + layer_url = get_url(GEOSERVER_URL, workspace, layer_name) + res_layer_url = requests.get(layer_url) + + if res_layer_url.status_code == 200: + try: + root = ET.fromstring(res_layer_url.text) + # Extract feature count from WFS hits response + total_features = int(root.attrib.get("numberOfFeatures", 0)) + status_code = 200 if total_features > 0 else 400 + layer = ( + Layer.objects.filter(layer_name=layer_name) + .order_by("-layer_version") + .first() + ) + if layer and layer.misc: + start_date = layer.misc.get("start_date") + end_date = layer.misc.get("end_date") + + except ET.ParseError: + print(f"Invalid XML for layer: {layer_name}") + status_code = 400 + else: + if workspace not in capabilities_cache: + capabilities_url = f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" + try: + response = requests.get(capabilities_url, timeout=30) + if response.status_code == 200: + parser = LET.XMLParser(recover=True, encoding="utf-8") + root = LET.fromstring(response.content, parser=parser) + ns = {"wms": root.tag.split("}")[0].strip("{")} + layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) + capabilities_cache[workspace] = { + layer.text for layer in layers if layer.text + } + else: + capabilities_cache[workspace] = set() + except Exception as e: + print( + f"Failed to fetch capabilities for workspace {workspace}: {e}" + ) + capabilities_cache[workspace] = set() + + if layer_name in capabilities_cache.get(workspace, set()): + status_code = 200 + all_workspace_statuses[workspace_display] = { + "workspace": workspace, + "layer_name": layer_name, + "status_code": status_code, + "totalFeature": total_features, + "endDate": end_date, + "startDate": start_date, + } + + return all_workspace_statuses + + +def load_workspace_types(): + """ + Load workspace types configuration from JSON file. + """ + config_path = ( + Path(settings.BASE_DIR) + / "data" + / "layers" + / "workspace_layers" + / "layers_in_workspace.json" + ) + with open(config_path, "r") as f: + return json.load(f) + + +@app.task(bind=True) +def get_layers_of_workspace(self, workspace): + """ + It will take workspace as argument and returns all the layers which is present on geoserver. + """ + # Load workspace types from JSON + workspace_types = load_workspace_types() + raster_workspace = workspace_types["raster_workspace"] + vector_workspace = workspace_types["vector_workspace"] + raster_and_vector_workspace = workspace_types["raster_and_vector_workspace"] + + geo = Geoserver() + layers = geo.get_layers(workspace) + layer_names = [layer["name"] for layer in layers["layers"]["layer"]] + if workspace in raster_workspace: + print("you passed raster workspace") + available_layers = valid_raster_layers_for_workspace(workspace) + valid_layers = [ln for ln in layer_names if ln in available_layers] + invalid_layers = [ln for ln in layer_names if ln not in available_layers] + return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} + elif workspace in vector_workspace: + print("you passed vector workspace") + valid_layers = [] + invalid_layers = [] + for layer_name in layer_names: + if is_valid_vector_layer(workspace, layer_name): + valid_layers.append(layer_name) + else: + invalid_layers.append(layer_name) + return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} + elif workspace in raster_and_vector_workspace: + print("you passed workspace which contain both layers(raster and vector)") + valid_layers = [] + invalid_layers = [] + for layer_name in layer_names: + if "vector" in layer_name.lower(): + if is_valid_vector_layer(workspace, layer_name): + valid_layers.append(layer_name) + else: + invalid_layers.append(layer_name) + elif "raster" in layer_name.lower(): + available_layers = valid_raster_layers_for_workspace(workspace) + if layer_name in available_layers: + valid_layers.append(layer_name) + else: + invalid_layers.append(layer_name) + return {"valid_layer": valid_layers, "invalid_layers": invalid_layers} + else: + print("you passed wrong workspace") + return [] + + +_raster_cache = {} +_vector_cache = {} + + +def valid_raster_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_raster_cache, workspace): + logger.info(f"Cache hit for raster: {workspace}") + return _raster_cache[workspace]["data"] + + session = get_session_with_retry() + capabilities_url = ( + f"{GEOSERVER_BASE}{workspace}/wms?service=WMS&request=GetCapabilities" + ) + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching raster capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed raster capabilities for {workspace}: {e} — not caching") + return set() + + root = ET.fromstring(response.content) + ns = {"wms": root.tag.split("}")[0].strip("{")} + layers = root.findall(".//wms:Layer/wms:Name", namespaces=ns) + result = {layer.text for layer in layers} + + _set_cache(_raster_cache, workspace, result) # cache with timestamp + logger.info(f"Cached raster layers for {workspace}: {len(result)} layers") + return result + + +def valid_vector_layers_for_workspace(workspace: str) -> set: + if _is_cache_valid(_vector_cache, workspace): + logger.info(f"Cache hit for vector: {workspace}") + return _vector_cache[workspace]["data"] + + session = get_session_with_retry() + capabilities_url = ( + f"{GEOSERVER_BASE}{workspace}/wfs" + f"?service=WFS&version=2.0.0&request=GetCapabilities" + ) + try: + response = session.get(capabilities_url, timeout=(5, 15)) + response.raise_for_status() + except requests.exceptions.Timeout: + logger.warning( + f"Timeout fetching vector capabilities for {workspace} — not caching" + ) + return set() + except requests.exceptions.RequestException as e: + logger.error(f"Failed vector capabilities for {workspace}: {e} — not caching") + return set() + + root = ET.fromstring(response.content) + ns = {"wfs": "http://www.opengis.net/wfs/2.0"} + names = root.findall(".//wfs:FeatureType/wfs:Name", namespaces=ns) + result = {name.text.split(":")[-1] for name in names} + + _set_cache(_vector_cache, workspace, result) # cache with timestamp + logger.info(f"Cached vector layers for {workspace}: {len(result)} layers") + return result + + +def is_valid_vector_layer(workspace: str, layer_name: str) -> bool: + """Used in parallel fallback only.""" + session = get_session_with_retry() + try: + layer_url = get_url(GEOSERVER_URL, workspace, layer_name) + res = session.get(layer_url, timeout=(5, 10)) # shorter timeout per layer + if res.status_code == 200: + root = ET.fromstring(res.text) + return int(root.attrib.get("numberOfFeatures", 0)) > 0 + except requests.exceptions.Timeout: + logger.warning(f"Timeout checking vector layer: {layer_name}") + except Exception as e: + logger.warning(f"Vector check failed for {layer_name}: {e}") + return False + + +def bulk_check_vector_layers( + workspace: str, + layer_names: list[str], + max_workers: int = 20, +) -> dict[str, bool]: + """ + Checks multiple vector layers concurrently using a thread pool. + Returns {layer_name: is_valid} mapping. + """ + results = {} + with ThreadPoolExecutor(max_workers=max_workers) as executor: + future_to_layer = { + executor.submit(is_valid_vector_layer, workspace, name): name + for name in layer_names + } + for future in as_completed(future_to_layer): + layer_name = future_to_layer[future] + try: + results[layer_name] = future.result() + except Exception as e: + logger.warning(f"Failed check for {layer_name}: {e}") + results[layer_name] = False + return results + + +@app.task(bind=True) +def missing_layer_for_all_workspace(self): + workspaces = ( + Dataset.objects.filter(workspace__isnull=False) + .values_list("workspace", flat=True) + .distinct() + ) + workspaces = [w.strip() for w in workspaces if w and w.strip()] + result = {} + for workspace in workspaces: + result[workspace] = check_missing_layers(workspace) + send_missing_layers_report(result) + return result + + +def check_missing_layers(workspace: str) -> dict: + logger.info(f"{workspace=}") + workspace_config = load_workspace_config() + workspace_types = get_workspace_types(workspace) + logger.info(f"Found types: {workspace_types}") + + if not workspace_types: + logger.critical(f"No config found for workspace: {workspace}") + return {"no config found": []} + + layer_config = get_layer_config_by_type( + workspace_config, workspace, workspace_types + ) + + # ── Fetch all available layers ONCE (bulk, cached) ────────────────────── + available_raster_layers = valid_raster_layers_for_workspace(workspace) + available_vector_layers = valid_vector_layers_for_workspace(workspace) # bulk WFS` + use_bulk_vector = bool(available_vector_layers) # fallback if WFS unavailable + + # ── Pre-build all (tehsil, layer_type, layer_name, state) combos ──────── + active_tehsils = TehsilSOI.objects.select_related( + "district__state" # eliminates N+1 DB queries + ).filter(active_status=True) + + tasks = [] # (state, layer_type, layer_name) + for tehsil_obj in active_tehsils: + state = tehsil_obj.district.state.state_name + district_name = valid_gee_text(tehsil_obj.district.district_name.lower()) + tehsil_name = valid_gee_text(tehsil_obj.tehsil_name.lower()) + + for layer_type, configs in layer_config.items(): + for config in configs: + prefix = config.get("prefix") + suffix = config.get("suffix") + layer_name = "_".join( + p for p in [prefix, district_name, tehsil_name, suffix] if p + ) + tasks.append( + (state, district_name, tehsil_name, layer_type, layer_name) + ) + + # ── Check raster layers (pure set lookup, no HTTP) ─────────────────────── + missing_layers = [] + vector_tasks = [] # collect for batch/parallel processing + + for state, district_name, tehsil_name, layer_type, layer_name in tasks: + if layer_type == "raster": + if layer_name not in available_raster_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + elif layer_type == "vector": + if use_bulk_vector: + # O(1) set lookup — no HTTP call needed + if layer_name not in available_vector_layers: + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + else: + vector_tasks.append( + (state, district_name, tehsil_name, layer_name) + ) # queue for parallel check + + # ── Parallel fallback for vector layers (if WFS bulk fetch failed) ─────── + if vector_tasks: + layer_names = [ln for _, _, _, ln in vector_tasks] + info_by_name = {ln: (st, dn, tn) for st, dn, tn, ln in vector_tasks} + validity = bulk_check_vector_layers(workspace, layer_names) + + for layer_name, is_valid in validity.items(): + if not is_valid: + state, district_name, tehsil_name = info_by_name[layer_name] + missing_layers.append(f"{state}, {district_name}, {tehsil_name}") + + return {"missing_layers": missing_layers} + + +def get_workspace_types(workspace_name): + """ + Return all layer types for a given workspace name from DB. + Example: ['raster', 'vector'] + """ + return list( + Dataset.objects.filter(workspace=workspace_name) + .values_list("layer_type", flat=True) + .distinct() + ) + + +def get_layer_config_by_type(workspace_config, workspace_name, layer_types): + """ + Return list of prefix/suffix configs for each layer type. + + Args: + workspace_config (dict): config JSON + workspace_name (str): dataset name + layer_types (list): ['raster', 'vector'] + + Returns: + dict: {'raster': [{'prefix': ..., 'suffix': ...}, ...], 'vector': [...]} + """ + result = {} + + for config in workspace_config.values(): + if config.get("name") == workspace_name and config.get("type") in layer_types: + layer_type = config["type"] + + if layer_type not in result: + result[layer_type] = [] + + result[layer_type].append( + { + "prefix": config.get("prefix"), + "suffix": config.get("suffix"), + } + ) + + return result + + +def get_session_with_retry(): + """Creates a requests session with retry and timeout handling.""" + session = requests.Session() + retry = Retry( + total=3, + backoff_factor=1, # waits 1s, 2s, 4s between retries + status_forcelist=[500, 502, 503, 504], + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + + +def clear_layer_cache(workspace: str = None): + if workspace: + _raster_cache.pop(workspace, None) + _vector_cache.pop(workspace, None) + logger.info(f"Cleared cache for {workspace}") + else: + _raster_cache.clear() + _vector_cache.clear() + logger.info("Cleared all layer caches") + + +def refresh_layer_cache(request, workspace=None): + clear_layer_cache(workspace) + return Response({"message": f"Cache cleared for: {workspace or 'all workspaces'}"}) diff --git a/dpr/tasks.py b/dpr/tasks.py index d894df34..a22ac1d0 100644 --- a/dpr/tasks.py +++ b/dpr/tasks.py @@ -1,123 +1,123 @@ -from django.templatetags.i18n import language -from django.utils import timezone -from nrm_app.celery import app -from utilities.logger import setup_logger -import requests - -from .gen_dpr import ( - create_dpr_document, - get_mws_ids_for_report, - get_plan_details, -) -from .models import DPR_Report -from .utils import ( - transform_name, - send_dpr_email, - upload_dpr_to_s3, - check_dpr_exists_on_s3, -) - -# from .views import generate_dpr_pdf - -logger = setup_logger(__name__) - - -def get_or_generate_dpr(plan, regenerate=False): - dpr_report, created = DPR_Report.objects.get_or_create( - plan_id=plan, defaults={"plan_name": plan.plan, "status": "PENDING"} - ) - - if not regenerate: - s3_exists = check_dpr_exists_on_s3(dpr_report.dpr_report_s3_url) - if not created and not dpr_report.needs_regeneration() and s3_exists: - logger.info( - f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}" - ) - return dpr_report, False - - logger.info(f"Generating new DPR for plan {plan.id}") - dpr_report.status = "GENERATING" - dpr_report.save(update_fields=["status"]) - - doc = create_dpr_document(plan) - s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan) - - dpr_report.dpr_report_s3_url = s3_url - dpr_report.dpr_generated_at = timezone.now() - dpr_report.status = "COMPLETED" - dpr_report.last_updated_at = timezone.now() - dpr_report.save( - update_fields=[ - "dpr_report_s3_url", - "dpr_generated_at", - "status", - "last_updated_at", - ] - ) - - logger.info(f"DPR generated and saved to S3: {s3_url}") - return dpr_report, True - - -@app.task(bind=True, name="dpr.generate_dpr_task") -def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = False): - plan = get_plan_details(plan_id) - if plan is None: - logger.error(f"Plan not found for ID: {plan_id}") - return {"error": "Plan not found"} - - dpr_report, was_regenerated = get_or_generate_dpr(plan, regenerate=regenerate) - mws_Ids = get_mws_ids_for_report(plan) - - mws_reports = [] - successful_mws_ids = [] - - state = transform_name(str(plan.state_soi.state_name)) - district = transform_name(str(plan.district_soi.district_name)) - block = transform_name(str(plan.tehsil_soi.tehsil_name)) - - for ids in mws_Ids: - try: - report_url = ( - f"https://geoserver.core-stack.org/api/v1/download_report/" - f"?report_type=mws&state={state}&district={district}&block={block}&uid={ids}" - ) - mws_reports.append(report_url) - successful_mws_ids.append(ids) - except Exception as e: - logger.error(f"Failed to generate MWS report for ID {ids}: {e}") - - # Fetch Resource Report PDF - resource_report_url = ( - f"https://geoserver.core-stack.org/api/v1/download_report/" - f"?report_type=resource&district={district}&block={block}&plan_id={plan_id}&plan_name={plan.plan}" - ) - - resource_report = None - try: - response = requests.get(resource_report_url, timeout=30) - response.raise_for_status() - resource_report = response.content - except Exception as e: - logger.error(f"Failed to fetch resource report: {e}") - - send_dpr_email( - email_id=email_id, - plan_name=plan.plan, - mws_reports=mws_reports, - mws_Ids=successful_mws_ids, - resource_report=resource_report, - resource_report_url=resource_report_url, - dpr_s3_url=dpr_report.dpr_report_s3_url, - state_name=plan.state_soi.state_name, - district_name=plan.district_soi.district_name, - tehsil_name=plan.tehsil_soi.tehsil_name, - ) - - return { - "status": "success", - "email_id": email_id, - "plan_id": plan_id, - "s3_url": dpr_report.dpr_report_s3_url, - "was_regenerated": was_regenerated, - } +from django.templatetags.i18n import language +from django.utils import timezone +from nrm_app.celery import app +from utilities.logger import setup_logger +import requests + +from .gen_dpr import ( + create_dpr_document, + get_mws_ids_for_report, + get_plan_details, +) +from .models import DPR_Report +from .utils import ( + transform_name, + send_dpr_email, + upload_dpr_to_s3, + check_dpr_exists_on_s3, +) + +# from .views import generate_dpr_pdf + +logger = setup_logger(__name__) + + +def get_or_generate_dpr(plan, regenerate=False): + dpr_report, created = DPR_Report.objects.get_or_create( + plan_id=plan, defaults={"plan_name": plan.plan, "status": "PENDING"} + ) + + if not regenerate: + s3_exists = check_dpr_exists_on_s3(dpr_report.dpr_report_s3_url) + if not created and not dpr_report.needs_regeneration() and s3_exists: + logger.info( + f"Using cached DPR for plan {plan.id} from S3: {dpr_report.dpr_report_s3_url}" + ) + return dpr_report, False + + logger.info(f"Generating new DPR for plan {plan.id}") + dpr_report.status = "GENERATING" + dpr_report.save(update_fields=["status"]) + + doc = create_dpr_document(plan) + s3_url = upload_dpr_to_s3(doc, plan.id, plan.plan) + + dpr_report.dpr_report_s3_url = s3_url + dpr_report.dpr_generated_at = timezone.now() + dpr_report.status = "COMPLETED" + dpr_report.last_updated_at = timezone.now() + dpr_report.save( + update_fields=[ + "dpr_report_s3_url", + "dpr_generated_at", + "status", + "last_updated_at", + ] + ) + + logger.info(f"DPR generated and saved to S3: {s3_url}") + return dpr_report, True + + +@app.task(bind=True, name="dpr.generate_dpr_task") +def generate_dpr_task(self, plan_id: int, email_id: str, regenerate: bool = False): + plan = get_plan_details(plan_id) + if plan is None: + logger.error(f"Plan not found for ID: {plan_id}") + return {"error": "Plan not found"} + + dpr_report, was_regenerated = get_or_generate_dpr(plan, regenerate=regenerate) + mws_Ids = get_mws_ids_for_report(plan) + + mws_reports = [] + successful_mws_ids = [] + + state = transform_name(str(plan.state_soi.state_name)) + district = transform_name(str(plan.district_soi.district_name)) + block = transform_name(str(plan.tehsil_soi.tehsil_name)) + + for ids in mws_Ids: + try: + report_url = ( + f"https://geoserver.core-stack.org/api/v1/download_report/" + f"?report_type=mws&state={state}&district={district}&block={block}&uid={ids}" + ) + mws_reports.append(report_url) + successful_mws_ids.append(ids) + except Exception as e: + logger.error(f"Failed to generate MWS report for ID {ids}: {e}") + + # Fetch Resource Report PDF + resource_report_url = ( + f"https://geoserver.core-stack.org/api/v1/download_report/" + f"?report_type=resource&district={district}&block={block}&plan_id={plan_id}&plan_name={plan.plan}" + ) + + resource_report = None + try: + response = requests.get(resource_report_url, timeout=30) + response.raise_for_status() + resource_report = response.content + except Exception as e: + logger.error(f"Failed to fetch resource report: {e}") + + send_dpr_email( + email_id=email_id, + plan_name=plan.plan, + mws_reports=mws_reports, + mws_Ids=successful_mws_ids, + resource_report=resource_report, + resource_report_url=resource_report_url, + dpr_s3_url=dpr_report.dpr_report_s3_url, + state_name=plan.state_soi.state_name, + district_name=plan.district_soi.district_name, + tehsil_name=plan.tehsil_soi.tehsil_name, + ) + + return { + "status": "success", + "email_id": email_id, + "plan_id": plan_id, + "s3_url": dpr_report.dpr_report_s3_url, + "was_regenerated": was_regenerated, + } diff --git a/dpr/utils.py b/dpr/utils.py index 98a3083e..669e9ee3 100644 --- a/dpr/utils.py +++ b/dpr/utils.py @@ -1,540 +1,540 @@ -import json -import re -import ssl -import socket -from io import BytesIO -import warnings -import time -from urllib.parse import urlparse -import pytz -import requests -from django.db.models import Max -from django.utils import timezone -from django.core.mail import EmailMessage -from django.core.mail.backends.smtp import EmailBackend -from docx import Document - -from nrm_app.settings import ( - EMAIL_HOST, - EMAIL_HOST_PASSWORD, - EMAIL_HOST_USER, - EMAIL_PORT, - EMAIL_TIMEOUT, - EMAIL_USE_SSL, - ODK_PASSWORD, - ODK_USERNAME, -) -from utilities.constants import ( - ODK_URL_AGRI_MAINTENANCE, - ODK_URL_GW_MAINTENANCE, - ODK_URL_RS_WATERBODY_MAINTENANCE, - ODK_URL_WATERBODY_MAINTENANCE, - ODK_URL_agri, - ODK_URL_crop, - ODK_URL_gw, - ODK_URL_livelihood, - ODK_URL_settlement, - ODK_URL_waterbody, - ODK_URL_well, -) -from utilities.logger import setup_logger - -from .models import ( - Agri_maintenance, - GW_maintenance, - ODK_agri, - ODK_crop, - ODK_groundwater, - ODK_livelihood, - ODK_settlement, - ODK_waterbody, - ODK_well, - SWB_maintenance, - SWB_RS_maintenance, -) - -import boto3 -from nrm_app.settings import ( - DPR_S3_ACCESS_KEY, - DPR_S3_SECRET_KEY, - DPR_S3_REGION, - DPR_S3_BUCKET, - DPR_S3_FOLDER, -) -from botocore.exceptions import ClientError - -warnings.filterwarnings("ignore") - -logger = setup_logger(__name__) - - -def get_url(geoserver_url, 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 get_vector_layer_geoserver(geoserver_url, workspace, layer_name): - """Fetch vector layer data from GeoServer and return as GeoJSON.""" - url = get_url(geoserver_url, workspace, layer_name) - try: - response = requests.get(url) - response.raise_for_status() - - # Check if the response content is not empty and is valid JSON - if response.content: - return response.json() - else: - print(f"Empty response for layer '{layer_name}'.") - return None - - except requests.exceptions.RequestException as e: - print(f"Failed to fetch the vector layer '{layer_name}' from GeoServer: {e}") - print(f"Request URL: {url}") - if response is not None: - print(f"Response status code: {response.status_code}") - # print(f"Response content: {response.text}") - return None - - -def determine_caste_fields(record): - """ - Determine caste group whether it's a Single Caste Group or Mixed Caste Group - """ - count_sc = record.get("count_sc") - count_st = record.get("count_st") - count_obc = record.get("count_obc") - count_general = record.get("count_general") - - caste_counts_mapping = { - "SC": count_sc, - "ST": count_st, - "OBC": count_obc, - "GENERAL": count_general, - } - - valid_castes = [] - for caste, count in caste_counts_mapping.items(): - if count is not None and count != "": - try: - count_value = float(count) if isinstance(count, str) else count - if count_value > 0: - valid_castes.append(caste) - except (ValueError, TypeError): - if count: - valid_castes.append(caste) - - if not valid_castes: - return None, None, None, True - - if len(valid_castes) == 1: - largest_caste = "Single Caste Group" - smallest_caste = valid_castes[0] - settlement_status = "NA" - return largest_caste, smallest_caste, settlement_status, False - - else: - largest_caste = "Mixed Caste Group" - smallest_caste = "NA" - settlement_status = ", ".join(sorted(valid_castes)) - return largest_caste, smallest_caste, settlement_status, False - - -def validate_email(emailid): - email_regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" - if not re.match(email_regex, emailid): - return False - else: - return True - - -def check_submission_time(record, model): - submission_date = timezone.datetime.strptime( - record.get("__system", {}).get("submissionDate", ""), "%Y-%m-%dT%H:%M:%S.%fZ" - ) - submission_date = timezone.make_aware(submission_date, pytz.UTC) - latest_submission_time = model.objects.aggregate(Max("submission_time"))[ - "submission_time__max" - ] - - if latest_submission_time and submission_date <= latest_submission_time: - return False, True - return True, False - - -def extract_coordinates(record): - try: - gps_point = record.get("GPS_point", {}) - if not gps_point: - return None, None - - # Check for both possible key names - maps_appearance = gps_point.get("point_mapsappearance") or gps_point.get( - "point_mapappearance" - ) - if not maps_appearance: - return None, None - - coordinates = maps_appearance.get("coordinates", []) - if len(coordinates) < 2: - return None, None - - return coordinates[1], coordinates[0] # latitude, longitude - except (AttributeError, IndexError, TypeError): - return None, None - - -def format_text_demands(text): - """ - Helps in converting demands in proper text - """ - if not text: - return "" - - items = text.split() - formatted_items = [] - - for item in items: - item_with_spaces = item.replace("_", " ") - formatted_item = " ".join( - word.capitalize() for word in item_with_spaces.split() - ) - formatted_items.append(formatted_item) - - formatted_text = "\n".join(formatted_items) - return formatted_text - - -def ensure_str(value): - """Normalize a value that may be a list (from Kobo multi-select fields) into a string.""" - if isinstance(value, list): - return " ".join(str(v) for v in value) - return value if value is not None else "" - - -def format_text(text): - """ - Converts text with underscores to properly formatted text. - Example: 'Delayed_payments_for_works' -> 'Delayed Payments For Works' - """ - if not text: - return "" - - text = ensure_str(text) - formatted_text = text.replace("_", " ") - return formatted_text.capitalize() + "\n\n" - - -def get_waterbody_repair_activities(data_waterbody, water_structure_type): - """ - Extract repair activities based on water structure type from data_waterbody. - Handles 'other' cases where the specific repair activity is in a separate field. - - Args: - data_waterbody (dict): The nested waterbody data dictionary - water_structure_type (str): The type of water structure - - Returns: - str: The repair activities or "NA" if none found - """ - if not data_waterbody or not water_structure_type: - return "NA" - - structure_type_mapping = { - "canal": "Repair_of_canal", - "bunding": "Repair_of_bunding", - "check dam": "Repair_of_check_dam", - "farm bund": "Repair_of_farm_bund", - "farm pond": "Repair_of_farm_ponds", - "soakage pits": "Repair_of_soakage_pits", - "recharge pits": "Repair_of_recharge_pits", - "rock fill dam": "Repair_of_rock_fill_dam", - "stone bunding": "Repair_of_stone_bunding", - "community pond": "Repair_of_community_pond", - "diversion drains": "Repair_of_diversion_drains", - "large water body": "Repair_of_large_water_body", - "model5 structure": "Repair_of_model5_structure", - "percolation tank": "Repair_of_percolation_tank", - "earthen gully plug": "Repair_of_earthen_gully_plug", - "30-40 model structure": "Repair_of_30_40_model_structure", - "loose boulder structure": "Repair_of_loose_boulder_structure", - "trench cum bund network": "Repair_of_trench_cum_bund_network", - "water absorption trenches": "Repair_of_Water_absorption_trenches", - "drainage soakage channels": "Repair_of_drainage_soakage_channels", - "staggered contour trenches": "Repair_of_Staggered_contour_trenches", - "continuous contour trenches": "Repair_of_Continuous_contour_trenches", - } - - structure_type_lower = water_structure_type.lower().strip() - if structure_type_lower.startswith("other:"): - repair_fields = [ - key for key in data_waterbody.keys() if key.startswith("Repair_of_") - ] - for field in repair_fields: - if data_waterbody.get(field): - repair_value = ensure_str(data_waterbody.get(field)) - other_field = field + "_other" - if ( - repair_value - and repair_value.lower() == "other" - and data_waterbody.get(other_field) - ): - return f"Other: {data_waterbody.get(other_field)}" - # elif repair_value: - # return repair_value.replace("_", " ").title() - return "NA" - - repair_field = structure_type_mapping.get(structure_type_lower) - - if not repair_field: - return "NA" - - repair_activity = ensure_str(data_waterbody.get(repair_field)) - - if not repair_activity: - return "NA" - - if repair_activity.lower() == "other": - other_field = repair_field + "_other" - other_value = data_waterbody.get(other_field) - if other_value: - return f"Other: {other_value}" - else: - return "Other" - - return repair_activity - - -def sort_key(settlement): - return (settlement == "NA", settlement.lower() if settlement != "NA" else "") - - -def transform_name(name): - if not name: - return name - - name = re.sub(r"[()]", "", name) - name = re.sub(r"[-\s]+", "_", name) - name = re.sub(r"_+", "_", name) - name = re.sub(r"^_|_$", "", name) - return name.lower() - - -def to_utf8(value): - """Ensure value is a properly encoded UTF-8 string for Word document. - - Handles cases where UTF-8 text was incorrectly decoded as Latin-1, - resulting in garbled characters like 'ಪಾà²...' for Kannada/Hindi text. - """ - if value is None: - return "NA" - if isinstance(value, list): - value = " ".join(str(v) for v in value) - if isinstance(value, bytes): - try: - return value.decode("utf-8") - except UnicodeDecodeError: - return value.decode("latin-1") - if not isinstance(value, str): - value = str(value) - try: - return value.encode("latin-1").decode("utf-8") - except (UnicodeDecodeError, UnicodeEncodeError): - return value - - -def send_dpr_email( - email_id, - plan_name, - mws_reports, - mws_Ids, - resource_report, - resource_report_url, - dpr_s3_url, - state_name="", - district_name="", - tehsil_name="", -): - try: - mws_table_html = "" - if mws_reports and mws_Ids: - mws_rows = "".join( - f'{mws_id}' - f'' - f'View Report' - for mws_id, report_url in zip(mws_Ids, mws_reports) - ) - mws_table_html = f""" -

        -

        MWS Reports

        - - - - - - - - {mws_rows} -
        MWS IDReport
        -
        - """ - - email_body = f""" - - - - -
        -
        -
        -

        Detailed Project Report

        -

        {to_utf8(plan_name)}

        -

        {to_utf8(tehsil_name)} · {to_utf8(district_name)} · {to_utf8(state_name)}

        -
        -
        -

        - Hi,

        - Your Detailed Project Report for {to_utf8(plan_name)} is ready. -

        -
        -

        Download DPR Report

        - Download DPR → -
        - {mws_table_html} -
        -

        Resource Report

        - View Report → -
        -
        -
        -

        - Thanks and Regards,
        - CoRE Stack Team -

        -
        -
        -

        - This is an automated email from CoRE Stack. -

        -
        - - - """ - - backend = EmailBackend( - host=EMAIL_HOST, - port=EMAIL_PORT, - username=EMAIL_HOST_USER, - password=EMAIL_HOST_PASSWORD, - use_ssl=EMAIL_USE_SSL, - timeout=EMAIL_TIMEOUT, - ssl_context=ssl.create_default_context(), - ) - - email = EmailMessage( - subject=f"DPR of plan: {plan_name}", - body=email_body, - from_email=EMAIL_HOST_USER, - to=[email_id], - connection=backend, - ) - - email.content_subtype = "html" - - if resource_report is not None: - email.attach( - f"Resource Report_{plan_name}.pdf", resource_report, "application/pdf" - ) - - logger.info("Sending DPR email to %s", email_id) - email.send(fail_silently=False) - logger.info("DPR email sent.") - backend.close() - - except socket.error as e: - logger.error(f"Socket error: {e}") - except ssl.SSLError as e: - logger.error(f"SSL error: {e}") - except Exception as e: - logger.error(f"Failed to send email: {e}") - - -def upload_dpr_to_s3(doc, plan_id, plan_name): - doc_bytes = BytesIO() - doc.save(doc_bytes) - doc_bytes.seek(0) - - safe_plan_name = transform_name(plan_name) - s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}.docx" - - s3_client = boto3.client( - "s3", - aws_access_key_id=DPR_S3_ACCESS_KEY, - aws_secret_access_key=DPR_S3_SECRET_KEY, - region_name=DPR_S3_REGION, - ) - - s3_client.upload_fileobj( - doc_bytes, - DPR_S3_BUCKET, - s3_key, - ExtraArgs={ - "ContentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.docx"', - "CacheControl": "no-cache, no-store, must-revalidate", - }, - ) - - ts = int(time.time()) - s3_url = f"https://{DPR_S3_BUCKET}.s3.{DPR_S3_REGION}.amazonaws.com/{s3_key}?v={ts}" - logger.info(f"DPR uploaded to S3: {s3_url}") - return s3_url - - -def _extract_s3_key(s3_url): - - parsed = urlparse(s3_url) - return parsed.path.lstrip("/") - - -def check_dpr_exists_on_s3(s3_url): - if not s3_url: - return False - - try: - s3_key = _extract_s3_key(s3_url) - except (IndexError, AttributeError): - return False - - s3_client = boto3.client( - "s3", - aws_access_key_id=DPR_S3_ACCESS_KEY, - aws_secret_access_key=DPR_S3_SECRET_KEY, - region_name=DPR_S3_REGION, - ) - - try: - s3_client.head_object(Bucket=DPR_S3_BUCKET, Key=s3_key) - return True - except ClientError: - logger.warning(f"DPR not found on S3: {s3_url}") - return False - - -def download_dpr_from_s3(s3_url): - s3_key = _extract_s3_key(s3_url) - - s3_client = boto3.client( - "s3", - aws_access_key_id=DPR_S3_ACCESS_KEY, - aws_secret_access_key=DPR_S3_SECRET_KEY, - region_name=DPR_S3_REGION, - ) - - doc_bytes = BytesIO() - s3_client.download_fileobj(DPR_S3_BUCKET, s3_key, doc_bytes) - doc_bytes.seek(0) - - doc = Document(doc_bytes) - logger.info(f"DPR downloaded from S3: {s3_url}") - return doc +import json +import re +import ssl +import socket +from io import BytesIO +import warnings +import time +from urllib.parse import urlparse +import pytz +import requests +from django.db.models import Max +from django.utils import timezone +from django.core.mail import EmailMessage +from django.core.mail.backends.smtp import EmailBackend +from docx import Document + +from nrm_app.settings import ( + EMAIL_HOST, + EMAIL_HOST_PASSWORD, + EMAIL_HOST_USER, + EMAIL_PORT, + EMAIL_TIMEOUT, + EMAIL_USE_SSL, + ODK_PASSWORD, + ODK_USERNAME, +) +from utilities.constants import ( + ODK_URL_AGRI_MAINTENANCE, + ODK_URL_GW_MAINTENANCE, + ODK_URL_RS_WATERBODY_MAINTENANCE, + ODK_URL_WATERBODY_MAINTENANCE, + ODK_URL_agri, + ODK_URL_crop, + ODK_URL_gw, + ODK_URL_livelihood, + ODK_URL_settlement, + ODK_URL_waterbody, + ODK_URL_well, +) +from utilities.logger import setup_logger + +from .models import ( + Agri_maintenance, + GW_maintenance, + ODK_agri, + ODK_crop, + ODK_groundwater, + ODK_livelihood, + ODK_settlement, + ODK_waterbody, + ODK_well, + SWB_maintenance, + SWB_RS_maintenance, +) + +import boto3 +from nrm_app.settings import ( + DPR_S3_ACCESS_KEY, + DPR_S3_SECRET_KEY, + DPR_S3_REGION, + DPR_S3_BUCKET, + DPR_S3_FOLDER, +) +from botocore.exceptions import ClientError + +warnings.filterwarnings("ignore") + +logger = setup_logger(__name__) + + +def get_url(geoserver_url, 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 get_vector_layer_geoserver(geoserver_url, workspace, layer_name): + """Fetch vector layer data from GeoServer and return as GeoJSON.""" + url = get_url(geoserver_url, workspace, layer_name) + try: + response = requests.get(url) + response.raise_for_status() + + # Check if the response content is not empty and is valid JSON + if response.content: + return response.json() + else: + print(f"Empty response for layer '{layer_name}'.") + return None + + except requests.exceptions.RequestException as e: + print(f"Failed to fetch the vector layer '{layer_name}' from GeoServer: {e}") + print(f"Request URL: {url}") + if response is not None: + print(f"Response status code: {response.status_code}") + # print(f"Response content: {response.text}") + return None + + +def determine_caste_fields(record): + """ + Determine caste group whether it's a Single Caste Group or Mixed Caste Group + """ + count_sc = record.get("count_sc") + count_st = record.get("count_st") + count_obc = record.get("count_obc") + count_general = record.get("count_general") + + caste_counts_mapping = { + "SC": count_sc, + "ST": count_st, + "OBC": count_obc, + "GENERAL": count_general, + } + + valid_castes = [] + for caste, count in caste_counts_mapping.items(): + if count is not None and count != "": + try: + count_value = float(count) if isinstance(count, str) else count + if count_value > 0: + valid_castes.append(caste) + except (ValueError, TypeError): + if count: + valid_castes.append(caste) + + if not valid_castes: + return None, None, None, True + + if len(valid_castes) == 1: + largest_caste = "Single Caste Group" + smallest_caste = valid_castes[0] + settlement_status = "NA" + return largest_caste, smallest_caste, settlement_status, False + + else: + largest_caste = "Mixed Caste Group" + smallest_caste = "NA" + settlement_status = ", ".join(sorted(valid_castes)) + return largest_caste, smallest_caste, settlement_status, False + + +def validate_email(emailid): + email_regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" + if not re.match(email_regex, emailid): + return False + else: + return True + + +def check_submission_time(record, model): + submission_date = timezone.datetime.strptime( + record.get("__system", {}).get("submissionDate", ""), "%Y-%m-%dT%H:%M:%S.%fZ" + ) + submission_date = timezone.make_aware(submission_date, pytz.UTC) + latest_submission_time = model.objects.aggregate(Max("submission_time"))[ + "submission_time__max" + ] + + if latest_submission_time and submission_date <= latest_submission_time: + return False, True + return True, False + + +def extract_coordinates(record): + try: + gps_point = record.get("GPS_point", {}) + if not gps_point: + return None, None + + # Check for both possible key names + maps_appearance = gps_point.get("point_mapsappearance") or gps_point.get( + "point_mapappearance" + ) + if not maps_appearance: + return None, None + + coordinates = maps_appearance.get("coordinates", []) + if len(coordinates) < 2: + return None, None + + return coordinates[1], coordinates[0] # latitude, longitude + except (AttributeError, IndexError, TypeError): + return None, None + + +def format_text_demands(text): + """ + Helps in converting demands in proper text + """ + if not text: + return "" + + items = text.split() + formatted_items = [] + + for item in items: + item_with_spaces = item.replace("_", " ") + formatted_item = " ".join( + word.capitalize() for word in item_with_spaces.split() + ) + formatted_items.append(formatted_item) + + formatted_text = "\n".join(formatted_items) + return formatted_text + + +def ensure_str(value): + """Normalize a value that may be a list (from Kobo multi-select fields) into a string.""" + if isinstance(value, list): + return " ".join(str(v) for v in value) + return value if value is not None else "" + + +def format_text(text): + """ + Converts text with underscores to properly formatted text. + Example: 'Delayed_payments_for_works' -> 'Delayed Payments For Works' + """ + if not text: + return "" + + text = ensure_str(text) + formatted_text = text.replace("_", " ") + return formatted_text.capitalize() + "\n\n" + + +def get_waterbody_repair_activities(data_waterbody, water_structure_type): + """ + Extract repair activities based on water structure type from data_waterbody. + Handles 'other' cases where the specific repair activity is in a separate field. + + Args: + data_waterbody (dict): The nested waterbody data dictionary + water_structure_type (str): The type of water structure + + Returns: + str: The repair activities or "NA" if none found + """ + if not data_waterbody or not water_structure_type: + return "NA" + + structure_type_mapping = { + "canal": "Repair_of_canal", + "bunding": "Repair_of_bunding", + "check dam": "Repair_of_check_dam", + "farm bund": "Repair_of_farm_bund", + "farm pond": "Repair_of_farm_ponds", + "soakage pits": "Repair_of_soakage_pits", + "recharge pits": "Repair_of_recharge_pits", + "rock fill dam": "Repair_of_rock_fill_dam", + "stone bunding": "Repair_of_stone_bunding", + "community pond": "Repair_of_community_pond", + "diversion drains": "Repair_of_diversion_drains", + "large water body": "Repair_of_large_water_body", + "model5 structure": "Repair_of_model5_structure", + "percolation tank": "Repair_of_percolation_tank", + "earthen gully plug": "Repair_of_earthen_gully_plug", + "30-40 model structure": "Repair_of_30_40_model_structure", + "loose boulder structure": "Repair_of_loose_boulder_structure", + "trench cum bund network": "Repair_of_trench_cum_bund_network", + "water absorption trenches": "Repair_of_Water_absorption_trenches", + "drainage soakage channels": "Repair_of_drainage_soakage_channels", + "staggered contour trenches": "Repair_of_Staggered_contour_trenches", + "continuous contour trenches": "Repair_of_Continuous_contour_trenches", + } + + structure_type_lower = water_structure_type.lower().strip() + if structure_type_lower.startswith("other:"): + repair_fields = [ + key for key in data_waterbody.keys() if key.startswith("Repair_of_") + ] + for field in repair_fields: + if data_waterbody.get(field): + repair_value = ensure_str(data_waterbody.get(field)) + other_field = field + "_other" + if ( + repair_value + and repair_value.lower() == "other" + and data_waterbody.get(other_field) + ): + return f"Other: {data_waterbody.get(other_field)}" + # elif repair_value: + # return repair_value.replace("_", " ").title() + return "NA" + + repair_field = structure_type_mapping.get(structure_type_lower) + + if not repair_field: + return "NA" + + repair_activity = ensure_str(data_waterbody.get(repair_field)) + + if not repair_activity: + return "NA" + + if repair_activity.lower() == "other": + other_field = repair_field + "_other" + other_value = data_waterbody.get(other_field) + if other_value: + return f"Other: {other_value}" + else: + return "Other" + + return repair_activity + + +def sort_key(settlement): + return (settlement == "NA", settlement.lower() if settlement != "NA" else "") + + +def transform_name(name): + if not name: + return name + + name = re.sub(r"[()]", "", name) + name = re.sub(r"[-\s]+", "_", name) + name = re.sub(r"_+", "_", name) + name = re.sub(r"^_|_$", "", name) + return name.lower() + + +def to_utf8(value): + """Ensure value is a properly encoded UTF-8 string for Word document. + + Handles cases where UTF-8 text was incorrectly decoded as Latin-1, + resulting in garbled characters like 'ಪಾà²...' for Kannada/Hindi text. + """ + if value is None: + return "NA" + if isinstance(value, list): + value = " ".join(str(v) for v in value) + if isinstance(value, bytes): + try: + return value.decode("utf-8") + except UnicodeDecodeError: + return value.decode("latin-1") + if not isinstance(value, str): + value = str(value) + try: + return value.encode("latin-1").decode("utf-8") + except (UnicodeDecodeError, UnicodeEncodeError): + return value + + +def send_dpr_email( + email_id, + plan_name, + mws_reports, + mws_Ids, + resource_report, + resource_report_url, + dpr_s3_url, + state_name="", + district_name="", + tehsil_name="", +): + try: + mws_table_html = "" + if mws_reports and mws_Ids: + mws_rows = "".join( + f'{mws_id}' + f'' + f'View Report' + for mws_id, report_url in zip(mws_Ids, mws_reports) + ) + mws_table_html = f""" +
        +

        MWS Reports

        + + + + + + + + {mws_rows} +
        MWS IDReport
        +
        + """ + + email_body = f""" + + + + +
        +
        +
        +

        Detailed Project Report

        +

        {to_utf8(plan_name)}

        +

        {to_utf8(tehsil_name)} · {to_utf8(district_name)} · {to_utf8(state_name)}

        +
        +
        +

        + Hi,

        + Your Detailed Project Report for {to_utf8(plan_name)} is ready. +

        +
        +

        Download DPR Report

        + Download DPR → +
        + {mws_table_html} +
        +

        Resource Report

        + View Report → +
        +
        +
        +

        + Thanks and Regards,
        + CoRE Stack Team +

        +
        +
        +

        + This is an automated email from CoRE Stack. +

        +
        + + + """ + + backend = EmailBackend( + host=EMAIL_HOST, + port=EMAIL_PORT, + username=EMAIL_HOST_USER, + password=EMAIL_HOST_PASSWORD, + use_ssl=EMAIL_USE_SSL, + timeout=EMAIL_TIMEOUT, + ssl_context=ssl.create_default_context(), + ) + + email = EmailMessage( + subject=f"DPR of plan: {plan_name}", + body=email_body, + from_email=EMAIL_HOST_USER, + to=[email_id], + connection=backend, + ) + + email.content_subtype = "html" + + if resource_report is not None: + email.attach( + f"Resource Report_{plan_name}.pdf", resource_report, "application/pdf" + ) + + logger.info("Sending DPR email to %s", email_id) + email.send(fail_silently=False) + logger.info("DPR email sent.") + backend.close() + + except socket.error as e: + logger.error(f"Socket error: {e}") + except ssl.SSLError as e: + logger.error(f"SSL error: {e}") + except Exception as e: + logger.error(f"Failed to send email: {e}") + + +def upload_dpr_to_s3(doc, plan_id, plan_name): + doc_bytes = BytesIO() + doc.save(doc_bytes) + doc_bytes.seek(0) + + safe_plan_name = transform_name(plan_name) + s3_key = f"{DPR_S3_FOLDER}/{plan_id}_{safe_plan_name}.docx" + + s3_client = boto3.client( + "s3", + aws_access_key_id=DPR_S3_ACCESS_KEY, + aws_secret_access_key=DPR_S3_SECRET_KEY, + region_name=DPR_S3_REGION, + ) + + s3_client.upload_fileobj( + doc_bytes, + DPR_S3_BUCKET, + s3_key, + ExtraArgs={ + "ContentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "ContentDisposition": f'attachment; filename="DPR_{safe_plan_name}.docx"', + "CacheControl": "no-cache, no-store, must-revalidate", + }, + ) + + ts = int(time.time()) + s3_url = f"https://{DPR_S3_BUCKET}.s3.{DPR_S3_REGION}.amazonaws.com/{s3_key}?v={ts}" + logger.info(f"DPR uploaded to S3: {s3_url}") + return s3_url + + +def _extract_s3_key(s3_url): + + parsed = urlparse(s3_url) + return parsed.path.lstrip("/") + + +def check_dpr_exists_on_s3(s3_url): + if not s3_url: + return False + + try: + s3_key = _extract_s3_key(s3_url) + except (IndexError, AttributeError): + return False + + s3_client = boto3.client( + "s3", + aws_access_key_id=DPR_S3_ACCESS_KEY, + aws_secret_access_key=DPR_S3_SECRET_KEY, + region_name=DPR_S3_REGION, + ) + + try: + s3_client.head_object(Bucket=DPR_S3_BUCKET, Key=s3_key) + return True + except ClientError: + logger.warning(f"DPR not found on S3: {s3_url}") + return False + + +def download_dpr_from_s3(s3_url): + s3_key = _extract_s3_key(s3_url) + + s3_client = boto3.client( + "s3", + aws_access_key_id=DPR_S3_ACCESS_KEY, + aws_secret_access_key=DPR_S3_SECRET_KEY, + region_name=DPR_S3_REGION, + ) + + doc_bytes = BytesIO() + s3_client.download_fileobj(DPR_S3_BUCKET, s3_key, doc_bytes) + doc_bytes.seek(0) + + doc = Document(doc_bytes) + logger.info(f"DPR downloaded from S3: {s3_url}") + return doc diff --git a/dpr/views.py b/dpr/views.py index 53a22a08..08a663ff 100644 --- a/dpr/views.py +++ b/dpr/views.py @@ -1,64 +1,64 @@ -from datetime import date -from django.template.loader import render_to_string -from dpr.service.translation_service import load_translations - -# from weasyprint import HTML -from .gen_dpr import get_settlement_count_for_plan -from .utils import get_vector_layer_geoserver, transform_name -from nrm_app.settings import GEOSERVER_URL -from .get_dpr_sectionwise_data import ( - get_section_b_data, - get_section_c_data, - get_section_d_data, - get_section_e_data, - get_section_f_data, - get_section_g_data, -) -from .service.form_download_service import sync_odk_forms - - -def generate_dpr_html(plan, language="en"): - translations = load_translations(language) - total_settlements = get_settlement_count_for_plan(plan.id) - mws_fortnight = get_vector_layer_geoserver( - geoserver_url=GEOSERVER_URL, - workspace="mws_layers", - layer_name="deltaG_fortnight_" - + transform_name(str(plan.district_soi.district_name)) - + "_" - + transform_name(str(plan.tehsil_soi.tehsil_name)), - ) - section_b_data, settlement_mws_ids, mws_gdf = get_section_b_data( - plan, total_settlements, mws_fortnight - ) - section_c_data = get_section_c_data(plan, language) - section_d_data = get_section_d_data(plan, settlement_mws_ids, mws_gdf, language) - section_e_data = get_section_e_data(plan, language) - section_f_data = get_section_f_data(plan, language) - section_g_data = get_section_g_data(plan, language) - html = render_to_string( - "dpr/base.html", - { - "t": translations, - "current_date": date.today().strftime("%B %d, %Y"), - "section_a": plan, - "section_b": section_b_data, - "section_c": section_c_data, - "section_d": section_d_data, - "section_e": section_e_data, - "section_f": section_f_data, - "section_g": section_g_data, - "footnote": f"DPR supported by {plan.organization.name} in {date.today().year}", - }, - ) - - return html - - -# def generate_dpr_pdf(plan, language="en"): -# sync_odk_forms() -# html = generate_dpr_html(plan, language) -# -# pdf = HTML(string=html).write_pdf() -# -# return pdf +from datetime import date +from django.template.loader import render_to_string +from dpr.service.translation_service import load_translations + +# from weasyprint import HTML +from .gen_dpr import get_settlement_count_for_plan +from .utils import get_vector_layer_geoserver, transform_name +from nrm_app.settings import GEOSERVER_URL +from .get_dpr_sectionwise_data import ( + get_section_b_data, + get_section_c_data, + get_section_d_data, + get_section_e_data, + get_section_f_data, + get_section_g_data, +) +from .service.form_download_service import sync_odk_forms + + +def generate_dpr_html(plan, language="en"): + translations = load_translations(language) + total_settlements = get_settlement_count_for_plan(plan.id) + mws_fortnight = get_vector_layer_geoserver( + geoserver_url=GEOSERVER_URL, + workspace="mws_layers", + layer_name="deltaG_fortnight_" + + transform_name(str(plan.district_soi.district_name)) + + "_" + + transform_name(str(plan.tehsil_soi.tehsil_name)), + ) + section_b_data, settlement_mws_ids, mws_gdf = get_section_b_data( + plan, total_settlements, mws_fortnight + ) + section_c_data = get_section_c_data(plan, language) + section_d_data = get_section_d_data(plan, settlement_mws_ids, mws_gdf, language) + section_e_data = get_section_e_data(plan, language) + section_f_data = get_section_f_data(plan, language) + section_g_data = get_section_g_data(plan, language) + html = render_to_string( + "dpr/base.html", + { + "t": translations, + "current_date": date.today().strftime("%B %d, %Y"), + "section_a": plan, + "section_b": section_b_data, + "section_c": section_c_data, + "section_d": section_d_data, + "section_e": section_e_data, + "section_f": section_f_data, + "section_g": section_g_data, + "footnote": f"DPR supported by {plan.organization.name} in {date.today().year}", + }, + ) + + return html + + +# def generate_dpr_pdf(plan, language="en"): +# sync_odk_forms() +# html = generate_dpr_html(plan, language) +# +# pdf = HTML(string=html).write_pdf() +# +# return pdf diff --git a/nrm_app/settings.py b/nrm_app/settings.py index 725c6040..f08fe9d6 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -1,436 +1,436 @@ -""" -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") + +# 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/plans/api.py b/plans/api.py index 58bd4fc1..dbd8eca2 100644 --- a/plans/api.py +++ b/plans/api.py @@ -1,789 +1,789 @@ -import logging -import os -import time -import uuid -from typing import Any, Dict, List, Optional, Tuple - -import requests -from django.views.decorators.csrf import csrf_exempt -from rest_framework import status -from rest_framework.decorators import api_view, schema -from rest_framework.response import Response - -from dpr.utils import transform_name -from moderation.utils.update_csdb import sync_form_type -from nrm_app.settings import ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC, TMP_LOCATION -from utilities.auth_check_decorator import api_security_check -from utilities.auth_utils import auth_free -from utilities.constants import ( - ODK_SYNC_URL_AGRI_FEEDBACK, - ODK_SYNC_URL_AGRI_MAINTENANCE, - ODK_SYNC_URL_AGROHORTICULTURE, - ODK_SYNC_URL_CROP, - ODK_SYNC_URL_GW_FEEDBACK, - ODK_SYNC_URL_GW_MAINTENANCE, - ODK_SYNC_URL_IRRIGATION_STRUCTURE, - ODK_SYNC_URL_LIVELIHOOD, - ODK_SYNC_URL_RECHARGE_STRUCTURE, - ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, - ODK_SYNC_URL_SETTLEMENT, - ODK_SYNC_URL_SWB_FEEDBACK, - ODK_SYNC_URL_WATER_STRUCTURES, - ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, - ODK_SYNC_URL_WELL, -) - -logger = logging.getLogger(__name__) - -from .build_layer import build_layer -from .models import ODKSyncLog, PlanApp, Plan -from .serializers import PlanAppSerializer -from .utils import fetch_bearer_token, fetch_db_data -from geoadmin.models import GramPanchayat -from django.db.models import Q - -_COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( - "layer_name", - "plan_id", - "plan_name", - "district_name", - "block_name", -) - -_LAYER_KIND_CONFIG: Dict[str, Dict[str, str]] = { - "resources": {"type_field": "resource_type", "singular": "resource"}, - "works": {"type_field": "work_type", "singular": "work"}, -} - - -# MARK: Get Plans API -@api_security_check(auth_type="Auth_free") -@schema(None) -def get_plans(request): - """ - Get Plans API - - Args: - block_id (str, optional): Block ID. Defaults to None. - - Returns: - Response: JSON response containing a list of plans of a block or all the plans - """ - try: - block_id = request.query_params.get("block_id", None) - if block_id is not None: - plans = Plan.objects.filter(block=block_id) - else: - plans = Plan.objects.all() - serializer = PlanAppSerializer(plans, many=True) - response = {"plans": serializer.data} - - return Response(response, status=status.HTTP_200_OK) - except Exception as e: - print("Exception in get_plans api :: ", e) - return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -@api_view(["POST"]) -@auth_free -@schema(None) -def add_plan(request): - if request.method == "POST": - serializer = PlanAppSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() # Save the new Plan instance if validation passes - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {"error": "Method not allowed"}, status=status.HTTP_405_METHOD_NOT_ALLOWED - ) - - -# MARK: Build Layer Helpers (shared by /add_resources and /add_works) -def _extract_payload(request, kind: str) -> Tuple[Optional[Dict[str, Any]], List[str]]: - """Pull and normalize the request payload. Returns (payload, missing_fields).""" - type_field = _LAYER_KIND_CONFIG[kind]["type_field"] - required = (*_COMMON_REQUIRED_FIELDS, type_field) - - missing = [f for f in required if not request.data.get(f)] - if missing: - return None, missing - - def _lower(value: Any) -> Any: - return value.lower() if isinstance(value, str) else value - - return { - "layer_name": _lower(request.data.get("layer_name")), - "item_type": _lower(request.data.get(type_field)), - "plan_id": request.data.get("plan_id"), - "plan_name": _lower(request.data.get("plan_name")), - "district": _lower(request.data.get("district_name")), - "block": _lower(request.data.get("block_name")), - }, [] - - -def _expected_layer_store_name( - item_type: str, plan_id: Any, district: str, block: str -) -> str: - """Mirror the naming convention used by build_layer.build_layer for transparency.""" - return f"{item_type}_{plan_id}_{district}_{transform_name(name=block)}" - - -def _safe_unlink(csv_path: str, request_id: str, kind: str) -> None: - try: - if os.path.exists(csv_path): - os.remove(csv_path) - logger.info( - f"[{request_id}] {kind}.build: cleaned up temp CSV at {csv_path}" - ) - except OSError as exc: - logger.warning( - f"[{request_id}] {kind}.build: failed to remove temp CSV at " - f"{csv_path}: {exc}" - ) - - -def _error_response( - request_id: str, - code: str, - message: str, - http_status: int, - extra: Optional[Dict[str, Any]] = None, -) -> Response: - data: Dict[str, Any] = {"request_id": request_id} - if extra: - data.update(extra) - return Response( - { - "status": "error", - "code": code, - "error": message, - "data": data, - }, - status=http_status, - ) - - -def _build_layer_for_kind(request, kind: str) -> Response: - """ - Shared workflow for /add_resources and /add_works: - 1. validate payload - 2. trigger incremental ODK -> DB sync (best-effort) - 3. fetch source records from DB and stage a CSV - 4. publish the layer to GeoServer - 5. clean up the temp CSV and return a structured response - """ - request_id = uuid.uuid4().hex[:12] - type_field = _LAYER_KIND_CONFIG[kind]["type_field"] - singular = _LAYER_KIND_CONFIG[kind]["singular"] - started_at = time.perf_counter() - - logger.info( - f"[{request_id}] {kind}.build: request received " - f"(content_type={request.content_type}, keys={list(request.data.keys())})" - ) - - payload, missing = _extract_payload(request, kind) - if missing: - logger.warning( - f"[{request_id}] {kind}.build: rejecting request — " - f"missing/empty fields: {missing}" - ) - return _error_response( - request_id, - code="missing_fields", - message=f"Missing required field(s): {', '.join(missing)}.", - http_status=status.HTTP_400_BAD_REQUEST, - extra={"missing_fields": missing}, - ) - - item_type = payload["item_type"] - plan_id = payload["plan_id"] - plan_name = payload["plan_name"] - district = payload["district"] - block = payload["block"] - layer_name = payload["layer_name"] - - context = { - type_field: item_type, - "plan_id": plan_id, - "plan_name": plan_name, - "district": district, - "block": block, - "layer_name": layer_name, - } - logger.info(f"[{request_id}] {kind}.build: payload normalized — {context}") - - csv_path = os.path.join(TMP_LOCATION, f"{item_type}_{plan_id}_{block}.csv") - logger.info(f"[{request_id}] {kind}.build: temp CSV path resolved to {csv_path}") - - sync_started = time.perf_counter() - logger.info( - f"[{request_id}] {kind}.build: triggering incremental ODK sync for " - f"{type_field}={item_type}" - ) - sync_ok = sync_form_type(item_type) - sync_ms = int((time.perf_counter() - sync_started) * 1000) - if sync_ok: - logger.info( - f"[{request_id}] {kind}.build: ODK sync completed for " - f"{type_field}={item_type} in {sync_ms}ms" - ) - else: - logger.warning( - f"[{request_id}] {kind}.build: ODK sync FAILED for " - f"{type_field}={item_type} in {sync_ms}ms; proceeding with existing DB data" - ) - - fetch_started = time.perf_counter() - logger.info( - f"[{request_id}] {kind}.build: fetching DB data for {type_field}={item_type}, " - f"plan_id={plan_id}, block={block}" - ) - try: - record_count = fetch_db_data(csv_path, item_type, block, plan_id) - except Exception as exc: - fetch_ms = int((time.perf_counter() - fetch_started) * 1000) - logger.exception( - f"[{request_id}] {kind}.build: unexpected error during fetch_db_data " - f"for {type_field}={item_type}, plan_id={plan_id} " - f"(fetch_ms={fetch_ms}): {exc}" - ) - _safe_unlink(csv_path, request_id, kind) - return _error_response( - request_id, - code="db_fetch_failed", - message="Failed to fetch source data from the database.", - http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, - extra={ - **context, - "details": str(exc), - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - }, - ) - fetch_ms = int((time.perf_counter() - fetch_started) * 1000) - - if not record_count: - total_ms = int((time.perf_counter() - started_at) * 1000) - logger.warning( - f"[{request_id}] {kind}.build: no DB data found for " - f"{type_field}={item_type}, plan_id={plan_id}, block={block} " - f"(sync_ok={sync_ok}, fetch_ms={fetch_ms}, total_ms={total_ms})" - ) - return _error_response( - request_id, - code="no_data_found", - message=( - f"No records found for {type_field}='{item_type}', " - f"plan_id='{plan_id}', block='{block}'." - ), - http_status=status.HTTP_404_NOT_FOUND, - extra={ - **context, - "record_count": 0, - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "total_duration_ms": total_ms, - }, - ) - logger.info( - f"[{request_id}] {kind}.build: DB fetch staged {record_count} row(s) " - f"in {fetch_ms}ms" - ) - - layer_store_name = _expected_layer_store_name(item_type, plan_id, district, block) - build_started = time.perf_counter() - logger.info( - f"[{request_id}] {kind}.build: publishing GeoServer layer " - f"workspace='{kind}', store='{layer_store_name}'" - ) - try: - success = build_layer( - layer_type=kind, - item_type=item_type, - plan_id=plan_id, - district=district, - block=block, - csv_path=csv_path, - ) - except Exception as exc: - build_ms = int((time.perf_counter() - build_started) * 1000) - total_ms = int((time.perf_counter() - started_at) * 1000) - logger.exception( - f"[{request_id}] {kind}.build: unexpected error during build_layer for " - f"{type_field}={item_type}, plan_id={plan_id} " - f"(build_ms={build_ms}, total_ms={total_ms}): {exc}" - ) - _safe_unlink(csv_path, request_id, kind) - return _error_response( - request_id, - code="internal_error", - message="An unexpected error occurred while building the layer.", - http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, - extra={ - **context, - "layer_store_name": layer_store_name, - "details": str(exc), - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "build_duration_ms": build_ms, - "total_duration_ms": total_ms, - }, - ) - finally: - _safe_unlink(csv_path, request_id, kind) - - build_ms = int((time.perf_counter() - build_started) * 1000) - total_ms = int((time.perf_counter() - started_at) * 1000) - - if not success: - logger.error( - f"[{request_id}] {kind}.build: build_layer returned False for " - f"{type_field}={item_type}, plan_id={plan_id} " - f"(build_ms={build_ms}, total_ms={total_ms})" - ) - return _error_response( - request_id, - code="layer_build_failed", - message=( - f"Failed to publish GeoServer layer '{layer_store_name}'. " - "See server logs for details." - ), - http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, - extra={ - **context, - "layer_store_name": layer_store_name, - "record_count": record_count, - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "build_duration_ms": build_ms, - "total_duration_ms": total_ms, - }, - ) - - logger.info( - f"[{request_id}] {kind}.build: SUCCESS — published layer " - f"'{layer_store_name}' ({record_count} row(s)) in workspace='{kind}' " - f"(sync={sync_ms}ms, fetch={fetch_ms}ms, build={build_ms}ms, total={total_ms}ms)" - ) - return Response( - { - "status": "success", - "code": "layer_published", - "message": ( - f"Successfully published {singular} layer " - f"'{layer_store_name}' to GeoServer with {record_count} record(s)." - ), - "data": { - "request_id": request_id, - "layer_type": kind, - "workspace": kind, - "layer_store_name": layer_store_name, - "record_count": record_count, - **context, - "sync_status": "success" if sync_ok else "failed", - "sync_duration_ms": sync_ms, - "fetch_duration_ms": fetch_ms, - "build_duration_ms": build_ms, - "total_duration_ms": total_ms, - }, - }, - status=status.HTTP_201_CREATED, - ) - - -# api's for add settlement, add well, add waterbody | add work [new, maintenance] -@api_view(["POST"]) -@auth_free -@schema(None) -def add_resources(request): - """ - Build and publish a GeoServer 'resources' layer for the given plan/block. - - Supported resource_type values: settlement, well, waterbody, cropping. - Layer naming convention: ___. - """ - return _build_layer_for_kind(request, kind="resources") - - -@api_view(["POST"]) -@auth_free -@schema(None) -def add_works(request): - """ - Build and publish a GeoServer 'works' layer for the given plan/block. - - Supported work_type values: - plan_gw — new recharge structures (groundwater) - main_gw — maintenance of recharge structures - plan_agri — new irrigation structures - main_agri — maintenance of irrigation structures - main_swb — surface water body maintenance - main_swb_rs — remote-sensed surface water body maintenance - livelihood — livelihood - agrohorticulture — agrohorticulture - - Layer naming convention: ___. - """ - return _build_layer_for_kind(request, kind="works") - - -# MARK: SYNC OFFLINE DATA HELPER FUNCTIONS -def _get_resource_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping for different resource types.""" - return { - "settlement": { - "url": ODK_SYNC_URL_SETTLEMENT, - "success_message": "Settlement data synced successfully", - }, - "well": { - "url": ODK_SYNC_URL_WELL, - "success_message": "Well data synced successfully", - }, - "water_structures": { - "url": ODK_SYNC_URL_WATER_STRUCTURES, - "success_message": "Water structures data synced successfully", - }, - "cropping_pattern": { - "url": ODK_SYNC_URL_CROP, - "success_message": "Cropping pattern data synced successfully", - }, - } - - -def _get_work_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping for different work types.""" - return { - "recharge_st": { - "url": ODK_SYNC_URL_RECHARGE_STRUCTURE, - "success_message": "Recharge structure data synced successfully", - }, - "irrigation_st": { - "url": ODK_SYNC_URL_IRRIGATION_STRUCTURE, - "success_message": "Irrigation structure data synced successfully", - }, - "propose_maintenance_recharge_st": { - "url": ODK_SYNC_URL_GW_MAINTENANCE, - "success_message": "Recharge structure maintenance data synced successfully", - }, - "propose_maintenance_rs_swb": { - "url": ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, - "success_message": "Surface water body maintenance data synced successfully", - }, - "propose_maintenance_ws_swb": { - "url": ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, - "success_message": "Water structures maintenance data synced successfully", - }, - "propose_maintenance_irrigation_st": { - "url": ODK_SYNC_URL_AGRI_MAINTENANCE, - "success_message": "Irrigation structure maintenance data synced successfully", - }, - "livelihood": { - "url": ODK_SYNC_URL_LIVELIHOOD, - "success_message": "Livelihood data synced successfully", - }, - "agrohorticulture": { - "url": ODK_SYNC_URL_AGROHORTICULTURE, - "success_message": "Agrohorticulture data synced successfully", - }, - } - - -def _get_feedback_config() -> Dict[str, Dict[str, Any]]: - """Configuration mapping of different feedback types""" - return { - "gw_feedback": { - "url": ODK_SYNC_URL_GW_FEEDBACK, - "success_message": "Groundwater feedback data synced successfully", - }, - "swb_feedback": { - "url": ODK_SYNC_URL_SWB_FEEDBACK, - "success_message": "Surface water body feedback data synced successfully", - }, - "agri_feedback": { - "url": ODK_SYNC_URL_AGRI_FEEDBACK, - "success_message": "Agriculture feedback data synced successfully", - }, - } - - -def _validate_sync_request( - request, resource_type: str = None, work_type: str = None, feedback_type: str = None -) -> Optional[Response]: - """Validate the sync request parameters and content type.""" - - if not resource_type and not work_type and not feedback_type: - return Response( - { - "error": "Must specify either resource_type or work_type or feedback_type" - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - if resource_type: - valid_resources = ["settlement", "well", "water_structures", "cropping_pattern"] - if resource_type not in valid_resources: - return Response( - {"error": f"Invalid resource type. Must be one of {valid_resources}"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if work_type: - valid_work_types = [ - "recharge_st", - "irrigation_st", - "propose_maintenance_recharge_st", - "propose_maintenance_rs_swb", - "propose_maintenance_ws_swb", - "propose_maintenance_irrigation_st", - "livelihood", - "agrohorticulture", - ] - if work_type not in valid_work_types: - return Response( - {"error": f"Invalid work type. Must be one of {valid_work_types}"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if feedback_type: - valid_feedback_types = ["gw_feedback", "swb_feedback", "agri_feedback"] - if feedback_type not in valid_feedback_types: - return Response( - { - "error": f"Invalid feedback type. Must be one of {valid_feedback_types}" - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - if request.content_type != "application/xml": - return Response( - {"error": "Content-Type must be application/xml"}, - status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, - ) - - return None - - -def _sync_to_odk( - xml_string: str, - config: Dict[str, Any], - bearer_token: str, - category: str, - sync_type: str, -) -> Response: - """Handle the actual sync to ODK for a specific resource or work type.""" - sync_log = ODKSyncLog.objects.create( - category=category, - sync_type=sync_type, - xml_content=xml_string, - odk_url=config["url"], - status=ODKSyncLog.SyncStatus.PENDING, - ) - - try: - response = requests.post( - config["url"], - headers={ - "Content-Type": "application/xml", - "Authorization": f"Bearer {bearer_token}", - }, - data=xml_string, - ) - response.raise_for_status() - - odk_response = response.json() if response.content else None - sync_log.status = ODKSyncLog.SyncStatus.SUCCESS - sync_log.odk_response = odk_response - sync_log.save(update_fields=["status", "odk_response"]) - - return Response( - { - "sync_status": True, - "message": config["success_message"], - "odk_response": odk_response, - }, - status=status.HTTP_201_CREATED, - ) - - except requests.exceptions.RequestException as e: - item_name = config["success_message"].split()[0].lower() - print(f"Error syncing {item_name} data to ODK: {str(e)}") - - sync_log.status = ODKSyncLog.SyncStatus.FAILED - sync_log.error_details = str(e) - sync_log.save(update_fields=["status", "error_details"]) - - return Response( - { - "sync_status": False, - "error": f"Failed to sync {item_name} data to ODK", - "details": str(e), - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - -# MARK: SYNC OFFLINE DATA -# API to sync offline data coming from CC app -@api_view(["POST"]) -@csrf_exempt -@auth_free -@schema(None) -def sync_offline_data(request, resource_type=None, work_type=None, feedback_type=None): - """ - Sync data to ODK based on resource type or work type - Resource types: settlement, well, water_structures, cropping_pattern - Work types: "recharge_st", "irrigation_st", "propose_maintenance_recharge_st", "propose_maintenance_rs_swb", - "propose_maintenance_ws_swb", "propose_maintenance_irrigation_st", "livelihood", - Feedback types: "gw_feedback", "swb_feedback", "agri_feedback" - - fetch Bearer Token from ODK - - send xmlString to ODK - """ - print( - f"Inside sync_offline_data API for resource type: {resource_type}, work type: {work_type}, feedback type: {feedback_type}" - ) - - # Validate request - validation_error = _validate_sync_request( - request, resource_type, work_type, feedback_type - ) - if validation_error: - return validation_error - - if resource_type: - configs = _get_resource_config() - config = configs[resource_type] - category = ODKSyncLog.SyncCategory.RESOURCE - item_type = resource_type - elif work_type: - configs = _get_work_config() - config = configs[work_type] - category = ODKSyncLog.SyncCategory.WORK - item_type = work_type - elif feedback_type: - configs = _get_feedback_config() - config = configs[feedback_type] - category = ODKSyncLog.SyncCategory.FEEDBACK - item_type = feedback_type - else: - return Response( - {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST - ) - - xml_string = request.body.decode("utf-8") - print(f"Sync Category: {category}, Type: {item_type}") - - try: - bearer_token = fetch_bearer_token(ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC) - print("Bearer Token: ", bearer_token) - - return _sync_to_odk(xml_string, config, bearer_token, category, item_type) - - except Exception as e: - print("Exception in sync_offline_data api :: ", e) - return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - -# map plan to gp api -@api_view(["PATCh"]) -@schema(None) -def map_plan_to_gp(request): - - plan_id = request.data.get("plan_id") - gp_id = request.data.get("gp_id") - - if not plan_id or not gp_id: - return Response( - { - "success": False, - "message": "plan_id and gp_id are required", - }, - status=400, - ) - - try: - plan = PlanApp.objects.get(id=plan_id) - - except PlanApp.DoesNotExist: - return Response( - { - "success": False, - "message": "Plan not found", - }, - status=404, - ) - - try: - gp = GramPanchayat.objects.get(gram_panchayat_code=gp_id) - - except GramPanchayat.DoesNotExist: - return Response( - { - "success": False, - "message": "Gram Panchayat not found", - }, - status=404, - ) - - # GP should belong to same tehsil - - if plan.tehsil_soi_id != gp.tehsil_id: - return Response( - { - "success": False, - "message": "Selected GP does not belong to plan tehsil", - }, - status=400, - ) - - plan.gp = gp - plan.updated_by = request.user - - plan.save(update_fields=["gp", "updated_by", "updated_at"]) - - return Response( - { - "success": True, - "message": "Plan mapped with GP successfully", - "data": { - "plan_id": plan.id, - "gp_id": gp.gram_panchayat_code, - "gp_name": gp.gram_panchayat_name, - }, - } - ) - - -@api_view(["GET"]) -@schema(None) -def plan_count(request): - """ - gives plan count on the basis of org_id or project_id and filter - """ - org_id = request.query_params.get("org_id") - project_id = request.query_params.get("project_id") - is_completed = request.query_params.get("is_completed") - - queryset = PlanApp.objects.filter(enabled=True).exclude( - Q(plan__icontains="test") | Q(plan__icontains="demo") - ) - - if is_completed: - queryset = queryset.filter(is_completed=True).exclude( - Q(plan__icontains="test") | Q(plan__icontains="demo") - ) - - if org_id: - plan_count = queryset.filter(organization=org_id).count() - elif project_id: - plan_count = queryset.filter(project=project_id).count() - else: - plan_count = 0 - - return Response({"plan_count": plan_count}) +import logging +import os +import time +import uuid +from typing import Any, Dict, List, Optional, Tuple + +import requests +from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.decorators import api_view, schema +from rest_framework.response import Response + +from dpr.utils import transform_name +from moderation.utils.update_csdb import sync_form_type +from nrm_app.settings import ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC, TMP_LOCATION +from utilities.auth_check_decorator import api_security_check +from utilities.auth_utils import auth_free +from utilities.constants import ( + ODK_SYNC_URL_AGRI_FEEDBACK, + ODK_SYNC_URL_AGRI_MAINTENANCE, + ODK_SYNC_URL_AGROHORTICULTURE, + ODK_SYNC_URL_CROP, + ODK_SYNC_URL_GW_FEEDBACK, + ODK_SYNC_URL_GW_MAINTENANCE, + ODK_SYNC_URL_IRRIGATION_STRUCTURE, + ODK_SYNC_URL_LIVELIHOOD, + ODK_SYNC_URL_RECHARGE_STRUCTURE, + ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, + ODK_SYNC_URL_SETTLEMENT, + ODK_SYNC_URL_SWB_FEEDBACK, + ODK_SYNC_URL_WATER_STRUCTURES, + ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, + ODK_SYNC_URL_WELL, +) + +logger = logging.getLogger(__name__) + +from .build_layer import build_layer +from .models import ODKSyncLog, PlanApp, Plan +from .serializers import PlanAppSerializer +from .utils import fetch_bearer_token, fetch_db_data +from geoadmin.models import GramPanchayat +from django.db.models import Q + +_COMMON_REQUIRED_FIELDS: Tuple[str, ...] = ( + "layer_name", + "plan_id", + "plan_name", + "district_name", + "block_name", +) + +_LAYER_KIND_CONFIG: Dict[str, Dict[str, str]] = { + "resources": {"type_field": "resource_type", "singular": "resource"}, + "works": {"type_field": "work_type", "singular": "work"}, +} + + +# MARK: Get Plans API +@api_security_check(auth_type="Auth_free") +@schema(None) +def get_plans(request): + """ + Get Plans API + + Args: + block_id (str, optional): Block ID. Defaults to None. + + Returns: + Response: JSON response containing a list of plans of a block or all the plans + """ + try: + block_id = request.query_params.get("block_id", None) + if block_id is not None: + plans = Plan.objects.filter(block=block_id) + else: + plans = Plan.objects.all() + serializer = PlanAppSerializer(plans, many=True) + response = {"plans": serializer.data} + + return Response(response, status=status.HTTP_200_OK) + except Exception as e: + print("Exception in get_plans api :: ", e) + return Response({"Exception": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +@api_view(["POST"]) +@auth_free +@schema(None) +def add_plan(request): + if request.method == "POST": + serializer = PlanAppSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() # Save the new Plan instance if validation passes + return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Method not allowed"}, status=status.HTTP_405_METHOD_NOT_ALLOWED + ) + + +# MARK: Build Layer Helpers (shared by /add_resources and /add_works) +def _extract_payload(request, kind: str) -> Tuple[Optional[Dict[str, Any]], List[str]]: + """Pull and normalize the request payload. Returns (payload, missing_fields).""" + type_field = _LAYER_KIND_CONFIG[kind]["type_field"] + required = (*_COMMON_REQUIRED_FIELDS, type_field) + + missing = [f for f in required if not request.data.get(f)] + if missing: + return None, missing + + def _lower(value: Any) -> Any: + return value.lower() if isinstance(value, str) else value + + return { + "layer_name": _lower(request.data.get("layer_name")), + "item_type": _lower(request.data.get(type_field)), + "plan_id": request.data.get("plan_id"), + "plan_name": _lower(request.data.get("plan_name")), + "district": _lower(request.data.get("district_name")), + "block": _lower(request.data.get("block_name")), + }, [] + + +def _expected_layer_store_name( + item_type: str, plan_id: Any, district: str, block: str +) -> str: + """Mirror the naming convention used by build_layer.build_layer for transparency.""" + return f"{item_type}_{plan_id}_{district}_{transform_name(name=block)}" + + +def _safe_unlink(csv_path: str, request_id: str, kind: str) -> None: + try: + if os.path.exists(csv_path): + os.remove(csv_path) + logger.info( + f"[{request_id}] {kind}.build: cleaned up temp CSV at {csv_path}" + ) + except OSError as exc: + logger.warning( + f"[{request_id}] {kind}.build: failed to remove temp CSV at " + f"{csv_path}: {exc}" + ) + + +def _error_response( + request_id: str, + code: str, + message: str, + http_status: int, + extra: Optional[Dict[str, Any]] = None, +) -> Response: + data: Dict[str, Any] = {"request_id": request_id} + if extra: + data.update(extra) + return Response( + { + "status": "error", + "code": code, + "error": message, + "data": data, + }, + status=http_status, + ) + + +def _build_layer_for_kind(request, kind: str) -> Response: + """ + Shared workflow for /add_resources and /add_works: + 1. validate payload + 2. trigger incremental ODK -> DB sync (best-effort) + 3. fetch source records from DB and stage a CSV + 4. publish the layer to GeoServer + 5. clean up the temp CSV and return a structured response + """ + request_id = uuid.uuid4().hex[:12] + type_field = _LAYER_KIND_CONFIG[kind]["type_field"] + singular = _LAYER_KIND_CONFIG[kind]["singular"] + started_at = time.perf_counter() + + logger.info( + f"[{request_id}] {kind}.build: request received " + f"(content_type={request.content_type}, keys={list(request.data.keys())})" + ) + + payload, missing = _extract_payload(request, kind) + if missing: + logger.warning( + f"[{request_id}] {kind}.build: rejecting request — " + f"missing/empty fields: {missing}" + ) + return _error_response( + request_id, + code="missing_fields", + message=f"Missing required field(s): {', '.join(missing)}.", + http_status=status.HTTP_400_BAD_REQUEST, + extra={"missing_fields": missing}, + ) + + item_type = payload["item_type"] + plan_id = payload["plan_id"] + plan_name = payload["plan_name"] + district = payload["district"] + block = payload["block"] + layer_name = payload["layer_name"] + + context = { + type_field: item_type, + "plan_id": plan_id, + "plan_name": plan_name, + "district": district, + "block": block, + "layer_name": layer_name, + } + logger.info(f"[{request_id}] {kind}.build: payload normalized — {context}") + + csv_path = os.path.join(TMP_LOCATION, f"{item_type}_{plan_id}_{block}.csv") + logger.info(f"[{request_id}] {kind}.build: temp CSV path resolved to {csv_path}") + + sync_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: triggering incremental ODK sync for " + f"{type_field}={item_type}" + ) + sync_ok = sync_form_type(item_type) + sync_ms = int((time.perf_counter() - sync_started) * 1000) + if sync_ok: + logger.info( + f"[{request_id}] {kind}.build: ODK sync completed for " + f"{type_field}={item_type} in {sync_ms}ms" + ) + else: + logger.warning( + f"[{request_id}] {kind}.build: ODK sync FAILED for " + f"{type_field}={item_type} in {sync_ms}ms; proceeding with existing DB data" + ) + + fetch_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: fetching DB data for {type_field}={item_type}, " + f"plan_id={plan_id}, block={block}" + ) + try: + record_count = fetch_db_data(csv_path, item_type, block, plan_id) + except Exception as exc: + fetch_ms = int((time.perf_counter() - fetch_started) * 1000) + logger.exception( + f"[{request_id}] {kind}.build: unexpected error during fetch_db_data " + f"for {type_field}={item_type}, plan_id={plan_id} " + f"(fetch_ms={fetch_ms}): {exc}" + ) + _safe_unlink(csv_path, request_id, kind) + return _error_response( + request_id, + code="db_fetch_failed", + message="Failed to fetch source data from the database.", + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "details": str(exc), + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + }, + ) + fetch_ms = int((time.perf_counter() - fetch_started) * 1000) + + if not record_count: + total_ms = int((time.perf_counter() - started_at) * 1000) + logger.warning( + f"[{request_id}] {kind}.build: no DB data found for " + f"{type_field}={item_type}, plan_id={plan_id}, block={block} " + f"(sync_ok={sync_ok}, fetch_ms={fetch_ms}, total_ms={total_ms})" + ) + return _error_response( + request_id, + code="no_data_found", + message=( + f"No records found for {type_field}='{item_type}', " + f"plan_id='{plan_id}', block='{block}'." + ), + http_status=status.HTTP_404_NOT_FOUND, + extra={ + **context, + "record_count": 0, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "total_duration_ms": total_ms, + }, + ) + logger.info( + f"[{request_id}] {kind}.build: DB fetch staged {record_count} row(s) " + f"in {fetch_ms}ms" + ) + + layer_store_name = _expected_layer_store_name(item_type, plan_id, district, block) + build_started = time.perf_counter() + logger.info( + f"[{request_id}] {kind}.build: publishing GeoServer layer " + f"workspace='{kind}', store='{layer_store_name}'" + ) + try: + success = build_layer( + layer_type=kind, + item_type=item_type, + plan_id=plan_id, + district=district, + block=block, + csv_path=csv_path, + ) + except Exception as exc: + build_ms = int((time.perf_counter() - build_started) * 1000) + total_ms = int((time.perf_counter() - started_at) * 1000) + logger.exception( + f"[{request_id}] {kind}.build: unexpected error during build_layer for " + f"{type_field}={item_type}, plan_id={plan_id} " + f"(build_ms={build_ms}, total_ms={total_ms}): {exc}" + ) + _safe_unlink(csv_path, request_id, kind) + return _error_response( + request_id, + code="internal_error", + message="An unexpected error occurred while building the layer.", + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "layer_store_name": layer_store_name, + "details": str(exc), + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + ) + finally: + _safe_unlink(csv_path, request_id, kind) + + build_ms = int((time.perf_counter() - build_started) * 1000) + total_ms = int((time.perf_counter() - started_at) * 1000) + + if not success: + logger.error( + f"[{request_id}] {kind}.build: build_layer returned False for " + f"{type_field}={item_type}, plan_id={plan_id} " + f"(build_ms={build_ms}, total_ms={total_ms})" + ) + return _error_response( + request_id, + code="layer_build_failed", + message=( + f"Failed to publish GeoServer layer '{layer_store_name}'. " + "See server logs for details." + ), + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + extra={ + **context, + "layer_store_name": layer_store_name, + "record_count": record_count, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + ) + + logger.info( + f"[{request_id}] {kind}.build: SUCCESS — published layer " + f"'{layer_store_name}' ({record_count} row(s)) in workspace='{kind}' " + f"(sync={sync_ms}ms, fetch={fetch_ms}ms, build={build_ms}ms, total={total_ms}ms)" + ) + return Response( + { + "status": "success", + "code": "layer_published", + "message": ( + f"Successfully published {singular} layer " + f"'{layer_store_name}' to GeoServer with {record_count} record(s)." + ), + "data": { + "request_id": request_id, + "layer_type": kind, + "workspace": kind, + "layer_store_name": layer_store_name, + "record_count": record_count, + **context, + "sync_status": "success" if sync_ok else "failed", + "sync_duration_ms": sync_ms, + "fetch_duration_ms": fetch_ms, + "build_duration_ms": build_ms, + "total_duration_ms": total_ms, + }, + }, + status=status.HTTP_201_CREATED, + ) + + +# api's for add settlement, add well, add waterbody | add work [new, maintenance] +@api_view(["POST"]) +@auth_free +@schema(None) +def add_resources(request): + """ + Build and publish a GeoServer 'resources' layer for the given plan/block. + + Supported resource_type values: settlement, well, waterbody, cropping. + Layer naming convention: ___. + """ + return _build_layer_for_kind(request, kind="resources") + + +@api_view(["POST"]) +@auth_free +@schema(None) +def add_works(request): + """ + Build and publish a GeoServer 'works' layer for the given plan/block. + + Supported work_type values: + plan_gw — new recharge structures (groundwater) + main_gw — maintenance of recharge structures + plan_agri — new irrigation structures + main_agri — maintenance of irrigation structures + main_swb — surface water body maintenance + main_swb_rs — remote-sensed surface water body maintenance + livelihood — livelihood + agrohorticulture — agrohorticulture + + Layer naming convention: ___. + """ + return _build_layer_for_kind(request, kind="works") + + +# MARK: SYNC OFFLINE DATA HELPER FUNCTIONS +def _get_resource_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping for different resource types.""" + return { + "settlement": { + "url": ODK_SYNC_URL_SETTLEMENT, + "success_message": "Settlement data synced successfully", + }, + "well": { + "url": ODK_SYNC_URL_WELL, + "success_message": "Well data synced successfully", + }, + "water_structures": { + "url": ODK_SYNC_URL_WATER_STRUCTURES, + "success_message": "Water structures data synced successfully", + }, + "cropping_pattern": { + "url": ODK_SYNC_URL_CROP, + "success_message": "Cropping pattern data synced successfully", + }, + } + + +def _get_work_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping for different work types.""" + return { + "recharge_st": { + "url": ODK_SYNC_URL_RECHARGE_STRUCTURE, + "success_message": "Recharge structure data synced successfully", + }, + "irrigation_st": { + "url": ODK_SYNC_URL_IRRIGATION_STRUCTURE, + "success_message": "Irrigation structure data synced successfully", + }, + "propose_maintenance_recharge_st": { + "url": ODK_SYNC_URL_GW_MAINTENANCE, + "success_message": "Recharge structure maintenance data synced successfully", + }, + "propose_maintenance_rs_swb": { + "url": ODK_SYNC_URL_RS_WATERBODY_MAINTENANCE, + "success_message": "Surface water body maintenance data synced successfully", + }, + "propose_maintenance_ws_swb": { + "url": ODK_SYNC_URL_WATER_STRUCTURES_MAINTENANCE, + "success_message": "Water structures maintenance data synced successfully", + }, + "propose_maintenance_irrigation_st": { + "url": ODK_SYNC_URL_AGRI_MAINTENANCE, + "success_message": "Irrigation structure maintenance data synced successfully", + }, + "livelihood": { + "url": ODK_SYNC_URL_LIVELIHOOD, + "success_message": "Livelihood data synced successfully", + }, + "agrohorticulture": { + "url": ODK_SYNC_URL_AGROHORTICULTURE, + "success_message": "Agrohorticulture data synced successfully", + }, + } + + +def _get_feedback_config() -> Dict[str, Dict[str, Any]]: + """Configuration mapping of different feedback types""" + return { + "gw_feedback": { + "url": ODK_SYNC_URL_GW_FEEDBACK, + "success_message": "Groundwater feedback data synced successfully", + }, + "swb_feedback": { + "url": ODK_SYNC_URL_SWB_FEEDBACK, + "success_message": "Surface water body feedback data synced successfully", + }, + "agri_feedback": { + "url": ODK_SYNC_URL_AGRI_FEEDBACK, + "success_message": "Agriculture feedback data synced successfully", + }, + } + + +def _validate_sync_request( + request, resource_type: str = None, work_type: str = None, feedback_type: str = None +) -> Optional[Response]: + """Validate the sync request parameters and content type.""" + + if not resource_type and not work_type and not feedback_type: + return Response( + { + "error": "Must specify either resource_type or work_type or feedback_type" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + if resource_type: + valid_resources = ["settlement", "well", "water_structures", "cropping_pattern"] + if resource_type not in valid_resources: + return Response( + {"error": f"Invalid resource type. Must be one of {valid_resources}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if work_type: + valid_work_types = [ + "recharge_st", + "irrigation_st", + "propose_maintenance_recharge_st", + "propose_maintenance_rs_swb", + "propose_maintenance_ws_swb", + "propose_maintenance_irrigation_st", + "livelihood", + "agrohorticulture", + ] + if work_type not in valid_work_types: + return Response( + {"error": f"Invalid work type. Must be one of {valid_work_types}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if feedback_type: + valid_feedback_types = ["gw_feedback", "swb_feedback", "agri_feedback"] + if feedback_type not in valid_feedback_types: + return Response( + { + "error": f"Invalid feedback type. Must be one of {valid_feedback_types}" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + if request.content_type != "application/xml": + return Response( + {"error": "Content-Type must be application/xml"}, + status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, + ) + + return None + + +def _sync_to_odk( + xml_string: str, + config: Dict[str, Any], + bearer_token: str, + category: str, + sync_type: str, +) -> Response: + """Handle the actual sync to ODK for a specific resource or work type.""" + sync_log = ODKSyncLog.objects.create( + category=category, + sync_type=sync_type, + xml_content=xml_string, + odk_url=config["url"], + status=ODKSyncLog.SyncStatus.PENDING, + ) + + try: + response = requests.post( + config["url"], + headers={ + "Content-Type": "application/xml", + "Authorization": f"Bearer {bearer_token}", + }, + data=xml_string, + ) + response.raise_for_status() + + odk_response = response.json() if response.content else None + sync_log.status = ODKSyncLog.SyncStatus.SUCCESS + sync_log.odk_response = odk_response + sync_log.save(update_fields=["status", "odk_response"]) + + return Response( + { + "sync_status": True, + "message": config["success_message"], + "odk_response": odk_response, + }, + status=status.HTTP_201_CREATED, + ) + + except requests.exceptions.RequestException as e: + item_name = config["success_message"].split()[0].lower() + print(f"Error syncing {item_name} data to ODK: {str(e)}") + + sync_log.status = ODKSyncLog.SyncStatus.FAILED + sync_log.error_details = str(e) + sync_log.save(update_fields=["status", "error_details"]) + + return Response( + { + "sync_status": False, + "error": f"Failed to sync {item_name} data to ODK", + "details": str(e), + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + +# MARK: SYNC OFFLINE DATA +# API to sync offline data coming from CC app +@api_view(["POST"]) +@csrf_exempt +@auth_free +@schema(None) +def sync_offline_data(request, resource_type=None, work_type=None, feedback_type=None): + """ + Sync data to ODK based on resource type or work type + Resource types: settlement, well, water_structures, cropping_pattern + Work types: "recharge_st", "irrigation_st", "propose_maintenance_recharge_st", "propose_maintenance_rs_swb", + "propose_maintenance_ws_swb", "propose_maintenance_irrigation_st", "livelihood", + Feedback types: "gw_feedback", "swb_feedback", "agri_feedback" + - fetch Bearer Token from ODK + - send xmlString to ODK + """ + print( + f"Inside sync_offline_data API for resource type: {resource_type}, work type: {work_type}, feedback type: {feedback_type}" + ) + + # Validate request + validation_error = _validate_sync_request( + request, resource_type, work_type, feedback_type + ) + if validation_error: + return validation_error + + if resource_type: + configs = _get_resource_config() + config = configs[resource_type] + category = ODKSyncLog.SyncCategory.RESOURCE + item_type = resource_type + elif work_type: + configs = _get_work_config() + config = configs[work_type] + category = ODKSyncLog.SyncCategory.WORK + item_type = work_type + elif feedback_type: + configs = _get_feedback_config() + config = configs[feedback_type] + category = ODKSyncLog.SyncCategory.FEEDBACK + item_type = feedback_type + else: + return Response( + {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + ) + + xml_string = request.body.decode("utf-8") + print(f"Sync Category: {category}, Type: {item_type}") + + try: + bearer_token = fetch_bearer_token(ODK_USER_EMAIL_SYNC, ODK_USER_PASSWORD_SYNC) + print("Bearer Token: ", bearer_token) + + return _sync_to_odk(xml_string, config, bearer_token, category, item_type) + + except Exception as e: + print("Exception in sync_offline_data api :: ", e) + return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +# map plan to gp api +@api_view(["PATCh"]) +@schema(None) +def map_plan_to_gp(request): + + plan_id = request.data.get("plan_id") + gp_id = request.data.get("gp_id") + + if not plan_id or not gp_id: + return Response( + { + "success": False, + "message": "plan_id and gp_id are required", + }, + status=400, + ) + + try: + plan = PlanApp.objects.get(id=plan_id) + + except PlanApp.DoesNotExist: + return Response( + { + "success": False, + "message": "Plan not found", + }, + status=404, + ) + + try: + gp = GramPanchayat.objects.get(gram_panchayat_code=gp_id) + + except GramPanchayat.DoesNotExist: + return Response( + { + "success": False, + "message": "Gram Panchayat not found", + }, + status=404, + ) + + # GP should belong to same tehsil + + if plan.tehsil_soi_id != gp.tehsil_id: + return Response( + { + "success": False, + "message": "Selected GP does not belong to plan tehsil", + }, + status=400, + ) + + plan.gp = gp + plan.updated_by = request.user + + plan.save(update_fields=["gp", "updated_by", "updated_at"]) + + return Response( + { + "success": True, + "message": "Plan mapped with GP successfully", + "data": { + "plan_id": plan.id, + "gp_id": gp.gram_panchayat_code, + "gp_name": gp.gram_panchayat_name, + }, + } + ) + + +@api_view(["GET"]) +@schema(None) +def plan_count(request): + """ + gives plan count on the basis of org_id or project_id and filter + """ + org_id = request.query_params.get("org_id") + project_id = request.query_params.get("project_id") + is_completed = request.query_params.get("is_completed") + + queryset = PlanApp.objects.filter(enabled=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if is_completed: + queryset = queryset.filter(is_completed=True).exclude( + Q(plan__icontains="test") | Q(plan__icontains="demo") + ) + + if org_id: + plan_count = queryset.filter(organization=org_id).count() + elif project_id: + plan_count = queryset.filter(project=project_id).count() + else: + plan_count = 0 + + return Response({"plan_count": plan_count}) diff --git a/plans/tests.py b/plans/tests.py index bb68993b..71fe76ea 100755 --- a/plans/tests.py +++ b/plans/tests.py @@ -1,556 +1,556 @@ -# plans/tests.py -import csv -import os -import tempfile -from datetime import datetime, timezone -from unittest.mock import patch - -from django.test import TestCase -from django.urls import reverse -from rest_framework.test import APITestCase, APIClient -from rest_framework import status - -from dpr.models import ODK_settlement -from .models import Plan -from .utils import fetch_db_data -from projects.models import Project, AppType -from organization.models import Organization -from users.models import User, UserProjectGroup -from django.contrib.auth.models import Group, Permission - - -class PlanModelTest(TestCase): - def setUp(self): - # Create organization - self.organization = Organization.objects.create(name="Test Organization") - - # Create project with app_type - self.project = Project.objects.create( - name="Test Project", - organization=self.organization, - app_type=AppType.WATERSHED, - enabled=True, - ) - - # Create user - self.user = User.objects.create_user( - username="testuser", - email="test@example.com", - password="password123", - organization=self.organization, - ) - - def test_plan_creation(self): - plan = Plan.objects.create( - name="Test Watershed Plan", - project=self.project, - organization=self.organization, - state="Test State", - district="Test District", - block="Test Block", - village="Test Village", - gram_panchayat="Test GP", - created_by=self.user, - ) - - # Check model attributes - self.assertEqual(plan.name, "Test Watershed Plan") - self.assertEqual(plan.project, self.project) - self.assertEqual(plan.organization, self.organization) - self.assertEqual(plan.state, "Test State") - self.assertEqual(plan.district, "Test District") - self.assertEqual(plan.created_by, self.user) - - -class PlanAPITest(APITestCase): - def setUp(self): - self.client = APIClient() - - # Create organization - self.organization = Organization.objects.create(name="Test Organization") - - # Create project with app_type - self.project = Project.objects.create( - name="Test Project", - organization=self.organization, - app_type=AppType.WATERSHED, - enabled=True, - ) - - # Create admin user - self.admin_user = User.objects.create_user( - username="admin", - email="admin@example.com", - password="password123", - organization=self.organization, - is_superadmin=True, - ) - - # Create edit user - self.edit_user = User.objects.create_user( - username="editor", - email="editor@example.com", - password="password123", - organization=self.organization, - ) - - # Create view user - self.view_user = User.objects.create_user( - username="viewer", - email="viewer@example.com", - password="password123", - organization=self.organization, - ) - - # Create groups and permissions - self.admin_group = Group.objects.create(name="Project Admin") - self.editor_group = Group.objects.create(name="Project Editor") - self.viewer_group = Group.objects.create(name="Project Viewer") - - # Create permissions - Permission.objects.get_or_create( - codename="view_watershed", - name="Can view watershed planning data", - content_type_id=1, # This would typically be correct content type ID - ) - - Permission.objects.get_or_create( - codename="add_watershed", - name="Can add watershed planning data", - content_type_id=1, - ) - - Permission.objects.get_or_create( - codename="change_watershed", - name="Can change watershed planning data", - content_type_id=1, - ) - - Permission.objects.get_or_create( - codename="delete_watershed", - name="Can delete watershed planning data", - content_type_id=1, - ) - - # Assign permissions to groups - view_perm = Permission.objects.get(codename="view_watershed") - add_perm = Permission.objects.get(codename="add_watershed") - change_perm = Permission.objects.get(codename="change_watershed") - delete_perm = Permission.objects.get(codename="delete_watershed") - - self.admin_group.permissions.add(view_perm, add_perm, change_perm, delete_perm) - self.editor_group.permissions.add(view_perm, add_perm, change_perm) - self.viewer_group.permissions.add(view_perm) - - # Assign users to project roles - UserProjectGroup.objects.create( - user=self.edit_user, project=self.project, group=self.editor_group - ) - - UserProjectGroup.objects.create( - user=self.view_user, project=self.project, group=self.viewer_group - ) - - # Create a test plan - self.plan = Plan.objects.create( - name="Test Watershed Plan", - project=self.project, - organization=self.organization, - state="Test State", - district="Test District", - block="Test Block", - village="Test Village", - gram_panchayat="Test GP", - created_by=self.admin_user, - ) - - # URLs - self.plans_list_url = reverse( - "project-plan-list", kwargs={"project_pk": self.project.pk} - ) - self.plan_detail_url = reverse( - "project-plan-detail", - kwargs={"project_pk": self.project.pk, "pk": self.plan.pk}, - ) - - def test_list_plans_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - response = self.client.get(self.plans_list_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - def test_list_plans_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - response = self.client.get(self.plans_list_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - def test_list_plans_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - response = self.client.get(self.plans_list_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - def test_create_plan_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - data = { - "name": "New Watershed Plan", - "state": "New State", - "district": "New District", - "block": "New Block", - "village": "New Village", - "gram_panchayat": "New GP", - } - - response = self.client.post(self.plans_list_url, data) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data["name"], "New Watershed Plan") - self.assertEqual(response.data["state"], "New State") - - # Check plan was created in database - self.assertEqual(Plan.objects.count(), 2) - - def test_create_plan_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - data = { - "name": "Editor Plan", - "state": "Editor State", - "district": "Editor District", - "block": "Editor Block", - "village": "Editor Village", - "gram_panchayat": "Editor GP", - } - - response = self.client.post(self.plans_list_url, data) - - # Editor should be able to create plans - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data["name"], "Editor Plan") - - def test_create_plan_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - data = { - "name": "Viewer Plan", - "state": "Viewer State", - "district": "Viewer District", - "block": "Viewer Block", - "village": "Viewer Village", - "gram_panchayat": "Viewer GP", - } - - response = self.client.post(self.plans_list_url, data) - - # Viewer should not be able to create plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_update_plan_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - data = { - "name": "Updated Plan", - "state": "Updated State", - "district": "Updated District", - "block": "Updated Block", - "village": "Updated Village", - "gram_panchayat": "Updated GP", - } - - response = self.client.put(self.plan_detail_url, data) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["name"], "Updated Plan") - - # Verify database was updated - updated_plan = Plan.objects.get(pk=self.plan.pk) - self.assertEqual(updated_plan.name, "Updated Plan") - - def test_update_plan_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - data = { - "name": "Editor Updated", - "state": self.plan.state, - "district": self.plan.district, - "block": self.plan.block, - "village": self.plan.village, - "gram_panchayat": self.plan.gram_panchayat, - } - - response = self.client.patch(self.plan_detail_url, {"name": "Editor Updated"}) - - # Editor should be able to update plans - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["name"], "Editor Updated") - - def test_update_plan_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - data = {"name": "Viewer Updated"} - - response = self.client.patch(self.plan_detail_url, data) - - # Viewer should not be able to update plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_delete_plan_as_admin(self): - self.client.force_authenticate(user=self.admin_user) - response = self.client.delete(self.plan_detail_url) - - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - # Verify plan was deleted - self.assertEqual(Plan.objects.count(), 0) - - def test_delete_plan_as_editor(self): - self.client.force_authenticate(user=self.edit_user) - response = self.client.delete(self.plan_detail_url) - - # Editor should not be able to delete plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Verify plan was not deleted - self.assertEqual(Plan.objects.count(), 1) - - def test_delete_plan_as_viewer(self): - self.client.force_authenticate(user=self.view_user) - response = self.client.delete(self.plan_detail_url) - - # Viewer should not be able to delete plans - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Verify plan was not deleted - self.assertEqual(Plan.objects.count(), 1) - - -# Minimal valid ODK settlement JSON (same structure as ODK submissions stored in data_settlement) -def _make_settlement_json(plan_id, block_name, settlement_id="SETT001", review_state="hasIssues"): - return { - "__id": f"uuid:{settlement_id}", - "__system": {"reviewState": review_state, "submissionDate": "2024-01-01T00:00:00Z"}, - "block_name": block_name, - "plan_id": str(plan_id), - "GPS_point": { - "point_mapsappearance": { - "coordinates": [78.5, 20.5] - } - }, - "Settlements_id": settlement_id, - "Settlements_name": "Test Settlement", - "MNREGA_INFORMATION": { - "NREGA_aware": 10, - "NREGA_applied": 5, - "NREGA_job_card": 3, - "total_household": 2, - "NREGA_work_days": 100, - "q1": "yes", - "select_one_Y_N": "yes", - "select_one_demands": "wages", - "select_multiple_issues": "delayed_payment", - "select_one_contributions": "labour", - }, - } - - -def _create_settlement(plan_id, block_name, settlement_id="SETT001", - is_deleted=False, is_moderated=False, review_state="hasIssues", - data_override=None): - data = data_override or _make_settlement_json(plan_id, block_name, settlement_id, review_state) - return ODK_settlement.objects.create( - settlement_id=settlement_id, - settlement_name="Test Settlement", - submission_time=datetime(2024, 1, 1, tzinfo=timezone.utc), - submitted_by="test_user", - status_re=review_state, - latitude=20.5, - longitude=78.5, - block_name=block_name, - number_of_households=10, - largest_caste="General", - smallest_caste="SC", - settlement_status="active", - plan_id=str(plan_id), - plan_name="Test Plan", - uuid=f"uuid:{settlement_id}", - farmer_family={}, - livestock_census={}, - nrega_job_aware=10, - nrega_job_applied=5, - nrega_past_work="yes", - nrega_raise_demand="yes", - nrega_demand="wages", - nrega_issues="delayed_payment", - nrega_community="labour", - data_settlement=data, - is_deleted=is_deleted, - is_moderated=is_moderated, - ) - - -class FetchDbDataTest(TestCase): - - def setUp(self): - self.tmp_dir = tempfile.mkdtemp() - - def _csv_path(self, name="test.csv"): - return os.path.join(self.tmp_dir, name) - - def test_returns_true_and_writes_csv_for_valid_settlement(self): - _create_settlement(plan_id="42", block_name="test block") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - self.assertTrue(os.path.exists(csv_path)) - with open(csv_path) as f: - reader = csv.DictReader(f) - rows = list(reader) - self.assertEqual(len(rows), 1) - self.assertIn("latitude", rows[0]) - self.assertIn("longitude", rows[0]) - self.assertEqual(rows[0]["sett_id"], "SETT001") - self.assertEqual(rows[0]["sett_name"], "Test Settlement") - - def test_moderated_record_uses_moderated_json(self): - moderated_data = _make_settlement_json("42", "test block") - moderated_data["Settlements_name"] = "Moderated Settlement Name" - _create_settlement( - plan_id="42", - block_name="test block", - settlement_id="SETT002", - is_moderated=True, - data_override=moderated_data, - ) - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_name"], "Moderated Settlement Name") - - def test_deleted_records_excluded(self): - _create_settlement(plan_id="42", block_name="test block", is_deleted=True) - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertFalse(result) - self.assertFalse(os.path.exists(csv_path)) - - def test_rejected_submissions_excluded_by_transform(self): - _create_settlement( - plan_id="42", block_name="test block", - settlement_id="SETT003", review_state="rejected" - ) - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertFalse(result) - - def test_returns_false_for_no_matching_records(self): - csv_path = self._csv_path() - result = fetch_db_data(csv_path, "settlement", "nonexistent_block", "99") - self.assertFalse(result) - - def test_returns_false_for_unknown_resource_type(self): - csv_path = self._csv_path() - result = fetch_db_data(csv_path, "unknown_type", "test_block", "42") - self.assertFalse(result) - - def test_block_name_with_spaces_matches_underscore_block_param(self): - _create_settlement(plan_id="42", block_name="test block") - csv_path = self._csv_path() - - # block param uses underscore; DB has spaces — should still match - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - - def test_only_matching_plan_id_returned(self): - _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") - _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") - csv_path = self._csv_path() - - result = fetch_db_data(csv_path, "settlement", "test_block", "42") - - self.assertTrue(result) - with open(csv_path) as f: - rows = list(csv.DictReader(f)) - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0]["sett_id"], "S1") - - -class AddResourcesAPITest(APITestCase): - - def setUp(self): - self.client = APIClient() - self.url = reverse("add_resources") - - @patch("plans.api.build_layer", return_value=True) - @patch("plans.api.sync_form_type", return_value=True) - def test_returns_201_when_db_data_exists(self, mock_sync, mock_build): - _create_settlement(plan_id="42", block_name="test block") - - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "42", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "test block", - }) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - mock_sync.assert_called_once_with("settlement") - mock_build.assert_called_once() - - @patch("plans.api.sync_form_type", return_value=True) - def test_returns_404_when_no_db_data(self, mock_sync): - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "99", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "no block", - }) - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - @patch("plans.api.build_layer", return_value=True) - @patch("plans.api.sync_form_type", return_value=False) - def test_proceeds_with_db_data_even_when_sync_fails(self, mock_sync, mock_build): - _create_settlement(plan_id="42", block_name="test block") - - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "42", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "test block", - }) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - mock_build.assert_called_once() - - @patch("plans.api.build_layer", return_value=False) - @patch("plans.api.sync_form_type", return_value=True) - def test_returns_500_when_build_layer_fails(self, mock_sync, mock_build): - _create_settlement(plan_id="42", block_name="test block") - - response = self.client.post(self.url, { - "layer_name": "test_layer", - "resource_type": "settlement", - "plan_id": "42", - "plan_name": "test plan", - "district_name": "test district", - "block_name": "test block", - }) - - self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) +# plans/tests.py +import csv +import os +import tempfile +from datetime import datetime, timezone +from unittest.mock import patch + +from django.test import TestCase +from django.urls import reverse +from rest_framework.test import APITestCase, APIClient +from rest_framework import status + +from dpr.models import ODK_settlement +from .models import Plan +from .utils import fetch_db_data +from projects.models import Project, AppType +from organization.models import Organization +from users.models import User, UserProjectGroup +from django.contrib.auth.models import Group, Permission + + +class PlanModelTest(TestCase): + def setUp(self): + # Create organization + self.organization = Organization.objects.create(name="Test Organization") + + # Create project with app_type + self.project = Project.objects.create( + name="Test Project", + organization=self.organization, + app_type=AppType.WATERSHED, + enabled=True, + ) + + # Create user + self.user = User.objects.create_user( + username="testuser", + email="test@example.com", + password="password123", + organization=self.organization, + ) + + def test_plan_creation(self): + plan = Plan.objects.create( + name="Test Watershed Plan", + project=self.project, + organization=self.organization, + state="Test State", + district="Test District", + block="Test Block", + village="Test Village", + gram_panchayat="Test GP", + created_by=self.user, + ) + + # Check model attributes + self.assertEqual(plan.name, "Test Watershed Plan") + self.assertEqual(plan.project, self.project) + self.assertEqual(plan.organization, self.organization) + self.assertEqual(plan.state, "Test State") + self.assertEqual(plan.district, "Test District") + self.assertEqual(plan.created_by, self.user) + + +class PlanAPITest(APITestCase): + def setUp(self): + self.client = APIClient() + + # Create organization + self.organization = Organization.objects.create(name="Test Organization") + + # Create project with app_type + self.project = Project.objects.create( + name="Test Project", + organization=self.organization, + app_type=AppType.WATERSHED, + enabled=True, + ) + + # Create admin user + self.admin_user = User.objects.create_user( + username="admin", + email="admin@example.com", + password="password123", + organization=self.organization, + is_superadmin=True, + ) + + # Create edit user + self.edit_user = User.objects.create_user( + username="editor", + email="editor@example.com", + password="password123", + organization=self.organization, + ) + + # Create view user + self.view_user = User.objects.create_user( + username="viewer", + email="viewer@example.com", + password="password123", + organization=self.organization, + ) + + # Create groups and permissions + self.admin_group = Group.objects.create(name="Project Admin") + self.editor_group = Group.objects.create(name="Project Editor") + self.viewer_group = Group.objects.create(name="Project Viewer") + + # Create permissions + Permission.objects.get_or_create( + codename="view_watershed", + name="Can view watershed planning data", + content_type_id=1, # This would typically be correct content type ID + ) + + Permission.objects.get_or_create( + codename="add_watershed", + name="Can add watershed planning data", + content_type_id=1, + ) + + Permission.objects.get_or_create( + codename="change_watershed", + name="Can change watershed planning data", + content_type_id=1, + ) + + Permission.objects.get_or_create( + codename="delete_watershed", + name="Can delete watershed planning data", + content_type_id=1, + ) + + # Assign permissions to groups + view_perm = Permission.objects.get(codename="view_watershed") + add_perm = Permission.objects.get(codename="add_watershed") + change_perm = Permission.objects.get(codename="change_watershed") + delete_perm = Permission.objects.get(codename="delete_watershed") + + self.admin_group.permissions.add(view_perm, add_perm, change_perm, delete_perm) + self.editor_group.permissions.add(view_perm, add_perm, change_perm) + self.viewer_group.permissions.add(view_perm) + + # Assign users to project roles + UserProjectGroup.objects.create( + user=self.edit_user, project=self.project, group=self.editor_group + ) + + UserProjectGroup.objects.create( + user=self.view_user, project=self.project, group=self.viewer_group + ) + + # Create a test plan + self.plan = Plan.objects.create( + name="Test Watershed Plan", + project=self.project, + organization=self.organization, + state="Test State", + district="Test District", + block="Test Block", + village="Test Village", + gram_panchayat="Test GP", + created_by=self.admin_user, + ) + + # URLs + self.plans_list_url = reverse( + "project-plan-list", kwargs={"project_pk": self.project.pk} + ) + self.plan_detail_url = reverse( + "project-plan-detail", + kwargs={"project_pk": self.project.pk, "pk": self.plan.pk}, + ) + + def test_list_plans_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + response = self.client.get(self.plans_list_url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_list_plans_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + response = self.client.get(self.plans_list_url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_list_plans_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + response = self.client.get(self.plans_list_url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_create_plan_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + data = { + "name": "New Watershed Plan", + "state": "New State", + "district": "New District", + "block": "New Block", + "village": "New Village", + "gram_panchayat": "New GP", + } + + response = self.client.post(self.plans_list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["name"], "New Watershed Plan") + self.assertEqual(response.data["state"], "New State") + + # Check plan was created in database + self.assertEqual(Plan.objects.count(), 2) + + def test_create_plan_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + data = { + "name": "Editor Plan", + "state": "Editor State", + "district": "Editor District", + "block": "Editor Block", + "village": "Editor Village", + "gram_panchayat": "Editor GP", + } + + response = self.client.post(self.plans_list_url, data) + + # Editor should be able to create plans + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["name"], "Editor Plan") + + def test_create_plan_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + data = { + "name": "Viewer Plan", + "state": "Viewer State", + "district": "Viewer District", + "block": "Viewer Block", + "village": "Viewer Village", + "gram_panchayat": "Viewer GP", + } + + response = self.client.post(self.plans_list_url, data) + + # Viewer should not be able to create plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_update_plan_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + data = { + "name": "Updated Plan", + "state": "Updated State", + "district": "Updated District", + "block": "Updated Block", + "village": "Updated Village", + "gram_panchayat": "Updated GP", + } + + response = self.client.put(self.plan_detail_url, data) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["name"], "Updated Plan") + + # Verify database was updated + updated_plan = Plan.objects.get(pk=self.plan.pk) + self.assertEqual(updated_plan.name, "Updated Plan") + + def test_update_plan_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + data = { + "name": "Editor Updated", + "state": self.plan.state, + "district": self.plan.district, + "block": self.plan.block, + "village": self.plan.village, + "gram_panchayat": self.plan.gram_panchayat, + } + + response = self.client.patch(self.plan_detail_url, {"name": "Editor Updated"}) + + # Editor should be able to update plans + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["name"], "Editor Updated") + + def test_update_plan_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + data = {"name": "Viewer Updated"} + + response = self.client.patch(self.plan_detail_url, data) + + # Viewer should not be able to update plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_plan_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + response = self.client.delete(self.plan_detail_url) + + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + # Verify plan was deleted + self.assertEqual(Plan.objects.count(), 0) + + def test_delete_plan_as_editor(self): + self.client.force_authenticate(user=self.edit_user) + response = self.client.delete(self.plan_detail_url) + + # Editor should not be able to delete plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Verify plan was not deleted + self.assertEqual(Plan.objects.count(), 1) + + def test_delete_plan_as_viewer(self): + self.client.force_authenticate(user=self.view_user) + response = self.client.delete(self.plan_detail_url) + + # Viewer should not be able to delete plans + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Verify plan was not deleted + self.assertEqual(Plan.objects.count(), 1) + + +# Minimal valid ODK settlement JSON (same structure as ODK submissions stored in data_settlement) +def _make_settlement_json(plan_id, block_name, settlement_id="SETT001", review_state="hasIssues"): + return { + "__id": f"uuid:{settlement_id}", + "__system": {"reviewState": review_state, "submissionDate": "2024-01-01T00:00:00Z"}, + "block_name": block_name, + "plan_id": str(plan_id), + "GPS_point": { + "point_mapsappearance": { + "coordinates": [78.5, 20.5] + } + }, + "Settlements_id": settlement_id, + "Settlements_name": "Test Settlement", + "MNREGA_INFORMATION": { + "NREGA_aware": 10, + "NREGA_applied": 5, + "NREGA_job_card": 3, + "total_household": 2, + "NREGA_work_days": 100, + "q1": "yes", + "select_one_Y_N": "yes", + "select_one_demands": "wages", + "select_multiple_issues": "delayed_payment", + "select_one_contributions": "labour", + }, + } + + +def _create_settlement(plan_id, block_name, settlement_id="SETT001", + is_deleted=False, is_moderated=False, review_state="hasIssues", + data_override=None): + data = data_override or _make_settlement_json(plan_id, block_name, settlement_id, review_state) + return ODK_settlement.objects.create( + settlement_id=settlement_id, + settlement_name="Test Settlement", + submission_time=datetime(2024, 1, 1, tzinfo=timezone.utc), + submitted_by="test_user", + status_re=review_state, + latitude=20.5, + longitude=78.5, + block_name=block_name, + number_of_households=10, + largest_caste="General", + smallest_caste="SC", + settlement_status="active", + plan_id=str(plan_id), + plan_name="Test Plan", + uuid=f"uuid:{settlement_id}", + farmer_family={}, + livestock_census={}, + nrega_job_aware=10, + nrega_job_applied=5, + nrega_past_work="yes", + nrega_raise_demand="yes", + nrega_demand="wages", + nrega_issues="delayed_payment", + nrega_community="labour", + data_settlement=data, + is_deleted=is_deleted, + is_moderated=is_moderated, + ) + + +class FetchDbDataTest(TestCase): + + def setUp(self): + self.tmp_dir = tempfile.mkdtemp() + + def _csv_path(self, name="test.csv"): + return os.path.join(self.tmp_dir, name) + + def test_returns_true_and_writes_csv_for_valid_settlement(self): + _create_settlement(plan_id="42", block_name="test block") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + self.assertTrue(os.path.exists(csv_path)) + with open(csv_path) as f: + reader = csv.DictReader(f) + rows = list(reader) + self.assertEqual(len(rows), 1) + self.assertIn("latitude", rows[0]) + self.assertIn("longitude", rows[0]) + self.assertEqual(rows[0]["sett_id"], "SETT001") + self.assertEqual(rows[0]["sett_name"], "Test Settlement") + + def test_moderated_record_uses_moderated_json(self): + moderated_data = _make_settlement_json("42", "test block") + moderated_data["Settlements_name"] = "Moderated Settlement Name" + _create_settlement( + plan_id="42", + block_name="test block", + settlement_id="SETT002", + is_moderated=True, + data_override=moderated_data, + ) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_name"], "Moderated Settlement Name") + + def test_deleted_records_excluded(self): + _create_settlement(plan_id="42", block_name="test block", is_deleted=True) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertFalse(result) + self.assertFalse(os.path.exists(csv_path)) + + def test_rejected_submissions_excluded_by_transform(self): + _create_settlement( + plan_id="42", block_name="test block", + settlement_id="SETT003", review_state="rejected" + ) + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertFalse(result) + + def test_returns_false_for_no_matching_records(self): + csv_path = self._csv_path() + result = fetch_db_data(csv_path, "settlement", "nonexistent_block", "99") + self.assertFalse(result) + + def test_returns_false_for_unknown_resource_type(self): + csv_path = self._csv_path() + result = fetch_db_data(csv_path, "unknown_type", "test_block", "42") + self.assertFalse(result) + + def test_block_name_with_spaces_matches_underscore_block_param(self): + _create_settlement(plan_id="42", block_name="test block") + csv_path = self._csv_path() + + # block param uses underscore; DB has spaces — should still match + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + + def test_only_matching_plan_id_returned(self): + _create_settlement(plan_id="42", block_name="test block", settlement_id="S1") + _create_settlement(plan_id="99", block_name="test block", settlement_id="S2") + csv_path = self._csv_path() + + result = fetch_db_data(csv_path, "settlement", "test_block", "42") + + self.assertTrue(result) + with open(csv_path) as f: + rows = list(csv.DictReader(f)) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["sett_id"], "S1") + + +class AddResourcesAPITest(APITestCase): + + def setUp(self): + self.client = APIClient() + self.url = reverse("add_resources") + + @patch("plans.api.build_layer", return_value=True) + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_201_when_db_data_exists(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + mock_sync.assert_called_once_with("settlement") + mock_build.assert_called_once() + + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_404_when_no_db_data(self, mock_sync): + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "99", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "no block", + }) + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + @patch("plans.api.build_layer", return_value=True) + @patch("plans.api.sync_form_type", return_value=False) + def test_proceeds_with_db_data_even_when_sync_fails(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + mock_build.assert_called_once() + + @patch("plans.api.build_layer", return_value=False) + @patch("plans.api.sync_form_type", return_value=True) + def test_returns_500_when_build_layer_fails(self, mock_sync, mock_build): + _create_settlement(plan_id="42", block_name="test block") + + response = self.client.post(self.url, { + "layer_name": "test_layer", + "resource_type": "settlement", + "plan_id": "42", + "plan_name": "test plan", + "district_name": "test district", + "block_name": "test block", + }) + + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/plans/utils.py b/plans/utils.py index 7e96cfcf..f4e0151f 100755 --- a/plans/utils.py +++ b/plans/utils.py @@ -1,739 +1,739 @@ -import csv -import logging -import re -from datetime import datetime, timezone - -import dateutil.parser -import requests - -from dpr.models import ( - ODK_settlement, ODK_well, ODK_waterbody, - ODK_groundwater, ODK_agri, ODK_livelihood, ODK_crop, - SWB_maintenance, SWB_RS_maintenance, GW_maintenance, Agri_maintenance, - ODK_agrohorticulture, -) -from moderation.utils.utils import ( - MODEL_FIELD_EXTRACTORS as _MODERATION_EXTRACTORS, - extract_lat_lon_from_gps, -) -from utilities.constants import ODK_URL_SESSION - -logger = logging.getLogger(__name__) - -_token_cache = { - "token": None, - "expires_at": None, -} - -# MARK: Helper -def normalize_name(name): - """ - Normalize names for comparison by: - - Converting to lowercase - - Replacing spaces with underscores - - Removing extra whitespace - """ - if not name: - return "" - return name.lower().replace(" ", "_").strip() - -_RESOURCE_TYPES_FLAT_HEADER = frozenset({ - "settlement", "well", "waterbody", "cropping", -}) -_RESOURCE_TYPES_UNION_HEADER = frozenset({ - "plan_gw", "plan_agri", "main_swb", "main_gw", "main_swb_rs", "main_agri", - "livelihood", "agrohorticulture", -}) - - -def _write_csv(resource_type, modified_response_list, all_keys, csv_path): - if resource_type in _RESOURCE_TYPES_FLAT_HEADER: - header_keys = modified_response_list[0].keys() - with open(csv_path, "w", encoding="utf-8") as output_file: - dict_writer = csv.DictWriter( - output_file, fieldnames=header_keys, extrasaction="ignore" - ) - dict_writer.writeheader() - dict_writer.writerows(modified_response_list) - elif resource_type in _RESOURCE_TYPES_UNION_HEADER: - with open(csv_path, "w", newline="", encoding="utf-8") as csvfile: - dict_writer = csv.DictWriter(csvfile, fieldnames=list(all_keys)) - dict_writer.writeheader() - for item in modified_response_list: - dict_writer.writerow(flatten_dict(item)) - logger.info(f"CSV generated for '{resource_type}' at {csv_path}") - - -# MARK: Modify ODK Settlement Data -def modify_response_list_settlement(res, block, plan_id): - res_list = [] - logger.info( - "modify_response_list_settlement: block=%s plan_id=%s input_records=%d", - block, plan_id, len(res) if res else 0, - ) - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["sett_id"] = result["Settlements_id"] - result["sett_name"] = result["Settlements_name"] - try: - mgnrega_info = result.get("MNREGA_INFORMATION", {}) - except Exception: - logger.exception("modify_response_list_settlement: failed reading MNREGA_INFORMATION") - continue - if mgnrega_info: - result["job_aware"] = mgnrega_info.get("NREGA_aware", "") or 0 - result["job_applied"] = mgnrega_info.get("NREGA_applied", "") or 0 - result["job_card"] = mgnrega_info.get("NREGA_job_card", "") or 0 - result["without_jc"] = mgnrega_info.get("total_household", "") or 0 - result["work_days"] = mgnrega_info.get("NREGA_work_days", "") or 0 - result["past_work"] = mgnrega_info.get("q1", "") or "0" - result["raise_demand"] = mgnrega_info.get("select_one_Y_N", "") or "0" - result["demand"] = mgnrega_info.get("select_one_demands", "") or "0" - result["issues"] = mgnrega_info.get("select_multiple_issues", "") or "0" - result["community"] = ( - mgnrega_info.get("select_one_contributions", "") or "0" - ) - res_list.append(result) - return res_list - - -# MARK: Modify ODK Well Data -def modify_response_list_well(res, block, plan_id): - res_list = [] - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["well_id"] = result["well_id"] - - well_usage_section = result.get("Well_usage", {}) - try: - result["ben_settlement"] = result.get("beneficiary_settlement", "") or "NA" - result["owner"] = result.get("select_one_owns", "") or "NA" - result["hh_benefitted"] = result.get("households_benefited", "") or "NA" - result["caste"] = result.get("select_multiple_caste_use", "") or "NA" - result["functional"] = ( - well_usage_section.get("select_one_Functional_Non_functional", "") - or "NA" - ) - result["need_maintenance"] = ( - well_usage_section.get("select_one_maintenance", "") or "NA" - ) - repair_value = well_usage_section.get("select_one_repairs_well") - if repair_value: - repair_value = str(repair_value).lower() - if repair_value == "other": - result["repair"] = ( - well_usage_section.get("select_one_repairs_well_other", "") - or "NA" - ) - else: - result["repair"] = repair_value - else: - result["repair"] = "NA" - except Exception: - logger.exception("modify_response_list_well: failed enriching record") - continue - res_list.append(result) - - return res_list - - -# MARK: Modify ODK Waterbody Data -def modify_response_list_waterbody(res, block, plan_id): - res_list = [] - logger.info( - "modify_response_list_waterbody: block=%s plan_id=%s input_records=%d", - block, plan_id, len(res) if res else 0, - ) - for result in res: - if result is None: - continue - if result.get("__system", {}).get("reviewState") == "rejected": - continue - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["wb_id"] = result["waterbodies_id"] - - # type_of_water_st = result["select_one_water_structure"] - # if type_of_water_st: - # type_of_water_st = str(type_of_water_st).lower() - # if type_of_water_st == "other": - # result["wbs_type"] = result.get("select_one_water_structure_other", "") or "" - # else: - # result["wbs_type"] = result.get("select_one_water_structure", "") or "" - # else: - # result["wbs_type"] = "0" - - result["wbs_type"] = result.get("select_one_water_structure", "") or "0" - - try: - manager = result["select_one_manages"] - if manager: - manager = str(manager).lower() - if manager == "other": - result["who_manages"] = result.get("text_one_manages", "") or "" - else: - result["who_manages"] = result.get("select_one_manages", "") or "" - else: - result["who_manages"] = "0" - - who_owns = result["select_one_owns"] - if who_owns: - who_owns = str(who_owns).lower() - if who_owns == "other" or who_owns == "any other": - result["owner"] = result.get("text_one_owns", "") or "" - else: - result["owner"] = result.get("select_one_owns", "") or "" - else: - result["owner"] = "0" - result["caste"] = result.get("select_multiple_caste_use", "") or "0" - result["hh_benefitted"] = result.get("households_benefited", "") or 0 - result["identified"] = result.get("select_one_identified", "") or "0" - result["need_maintenance"] = result.get("select_one_maintenance") or "0" - - # Handle the dynamic water structure dimensions - # water_structure_type = result.get("select_one_water_structure", "").lower().replace("_", " ") - # water_structure_dimension = {} - # for key, value in result.items(): - # if isinstance(value, dict): - # structure_type = key.lower().replace("_", " ") - # if structure_type == water_structure_type: - # water_structure_dimension = { - # "length": next((v for k, v in value.items() if k.startswith("Length")), None), - # "breadth": next((v for k, v in value.items() if k.startswith("Breadth")), None), - # "width": next((v for k, v in value.items() if k.startswith("Width")), None), - # "depth": next((v for k, v in value.items() if k.startswith("Depth")), None), - # "height": next((v for k, v in value.items() if k.startswith("Height")), None), - # } - # break - - # Add the dimensions to the result dictionary - # result.update(water_structure_dimension) - except Exception: - logger.exception("modify_response_list_waterbody: failed enriching record") - continue - res_list.append(result) - return res_list - - -# MARK: Modify ODK Cropping Data -def modify_reponse_list_cropping(res, block, plan_id): - res_list = [] - logger.info( - "modify_reponse_list_cropping: block=%s plan_id=%s input_records=%d", - block, plan_id, len(res) if res else 0, - ) - - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - - if str(result.get("plan_id")) != str(plan_id): - continue - - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["crop_id"] = result["__id"] - - # Settlement and land basic information (5 keys) - result["sett_name"] = result.get("beneficiary_settlement", "") or "" - result["uncropp_br"] = result.get("Uncropped_barren_land", "") or "" - result["irrigatn"] = result.get("select_multiple_widgets", "") or "" - result["land_cls"] = result.get("select_one_classified", "") or "" - result["crop_seas"] = result.get("select_one_practice", "") or "" - - # Kharif season information (3 keys) - result["crop_khrf"] = result.get("select_multiple_cropping_kharif", "") or "" - result["crop_kh_o"] = result.get("select_multiple_cropping_kharif_other", "") or "" - result["area_khrf"] = result.get("total_area_cultivation_kharif", "") or "" - - # Rabi season information (3 keys) - result["crops_rabi"] = result.get("select_multiple_cropping_Rabi", "") or "" - result["crop_rb_o"] = result.get("select_multiple_cropping_Rabi_other", "") or "" - result["area_rabi"] = result.get("total_area_cultivation_Rabi", "") or "" - - # Zaid season information (3 keys) - result["crops_zaid"] = result.get("select_multiple_cropping_Zaid", "") or "" - result["crop_zd_o"] = result.get("select_multiple_cropping_Zaid_other", "") or "" - result["area_zaid"] = result.get("total_area_cultivation_Zaid", "") or "" - - # Soil and productivity information (4 keys) - result["productiv"] = result.get("select_one_productivity", "") or "" - result["soil_deg"] = result.get("soil_degraded", "") or "" - result["deg_reas"] = result.get("select_one_reason_degradation", "") or "" - result["deg_reas2"] = result.get("select_one_reason_degradation_1", "") or "" - - res_list.append(result) - - return res_list - - -# MARK: Modify ODK Plan Data -def modify_response_list_plan(res, block, plan_id): - res_list = [] - for result in res: - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - result["work_id"] = result["work_id"] - - work_type = None - selected_work = None - - if "TYPE_OF_WORK" in result: - work_type = result["TYPE_OF_WORK"] - elif "TYPE_OF_WORK_ID" in result: - work_type = result["TYPE_OF_WORK_ID"] - - if work_type: - result["work_type"] = work_type - - work_type_key = re.sub(r"[^a-zA-Z0-9]+", "_", work_type) - - if work_type_key in result: - selected_work = result[work_type_key] - if selected_work: - result["selected_work"] = selected_work - else: - result["selected_work"] = work_type_key - elif work_type in result: - selected_work = result[work_type] - if selected_work: - result["selected_work"] = selected_work - else: - result["selected_work"] = work_type - else: - result["selected_work"] = work_type - - result["ben_settlement"] = result["beneficiary_settlement"] - result["ben_name"] = result["Beneficiary_Name"] - result["ben_contact"] = result["Beneficiary_Contact_Number"] - res_list.append(result) - - return res_list - - -# MARK: Modify ODK Livelihood Data -def modify_response_list_livelihood(res, block, plan_id): - res_list = [] - for result in res: - # if result["__system"]["reviewState"] != "rejected": - if result is None: - continue - - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - try: - if normalize_name(result.get("block_name").lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) - if latitude is not None and longitude is not None: - result["latitude"] = latitude - result["longitude"] = longitude - - result["status_re"] = result["__system"]["reviewState"] - res_list.append(result) - return res_list - - -# MARK: Modify ODK Maintenance / Agrohorticulture (Generic) -def modify_response_list_work(res, block, plan_id): - """ - Robust transform for maintenance and agrohorticulture submissions. - Unlike `modify_response_list_plan`, this avoids bracket-access on keys - that maintenance/agrohorticulture blobs may not carry (e.g. work_id, - Beneficiary_Name). Block filter is best-effort: applied only when the - blob actually has block_name (these models have no block_name DB column). - """ - res_list = [] - for result in res: - if result is None: - continue - if not isinstance(result, dict): - continue - if result.get("__system", {}).get("reviewState") == "rejected": - continue - - blob_block = result.get("block_name") - if blob_block: - try: - if normalize_name(str(blob_block).lower()) != normalize_name(block): - continue - except AttributeError: - continue - - if str(result.get("plan_id")) != str(plan_id): - continue - - lat, lon = extract_lat_lon_from_gps(result.get("GPS_point")) - if lat is not None and lon is not None: - result["latitude"] = lat - result["longitude"] = lon - - sys_info = result.get("__system") or {} - if isinstance(sys_info, dict): - review_state = sys_info.get("reviewState") - if review_state: - result["status_re"] = review_state - - res_list.append(result) - return res_list - - -# Layer-build source-of-truth registry. Key = resource_type / work_type the -# `/add_resources` and `/add_works` endpoints accept. Tuple = (Model, JSON -# blob field, model has block_name column for DB-level pre-filtering). -# -# Resources (workspace=resources): settlement, well, waterbody, cropping -# Works (workspace=works): -# plan_gw — new recharge structures (groundwater) -# main_gw — maintenance of recharge structures -# plan_agri — new irrigation structures -# main_agri — maintenance of irrigation structures -# main_swb — surface water body maintenance (water-structure form) -# main_swb_rs — remote-sensed surface water body maintenance -# livelihood — livelihood -# agrohorticulture — agrohorticulture -_DB_CONFIG = { - "settlement": (ODK_settlement, "data_settlement", True), - "well": (ODK_well, "data_well", True), - "waterbody": (ODK_waterbody, "data_waterbody", True), - "cropping": (ODK_crop, "data_crop", False), - "plan_gw": (ODK_groundwater, "data_groundwater", True), - "plan_agri": (ODK_agri, "data_agri", True), - "livelihood": (ODK_livelihood, "data_livelihood", True), - "main_swb": (SWB_maintenance, "data_swb_maintenance", False), - "main_gw": (GW_maintenance, "data_gw_maintenance", False), - "main_swb_rs": (SWB_RS_maintenance, "data_swb_rs_maintenance", False), - "main_agri": (Agri_maintenance, "data_agri_maintenance", False), - # `data_agohorticulture` is the actual model field name — there is a - # spelling typo in the schema. Respecting it here to avoid a migration. - "agrohorticulture": (ODK_agrohorticulture, "data_agohorticulture", False), -} - -# Fields never useful to project alongside the JSON blob: the blob itself, -# soft-delete metadata, and relational fields (FKs serialise poorly via values()). -_PROJECTION_EXCLUDED_FIELDS = frozenset({ - "data_before_moderation", - "is_deleted", - "deleted_at", - "deleted_by", - "moderated_by", -}) - - -def _scalar_projection_fields(model, json_blob_field: str) -> list: - """ - Concrete, non-relational fields on `model` safe to project via `.values()` - alongside the raw ODK JSON blob. Captures everything moderation can edit - (settlement_name, block_name, nrega_*, lat/lon, status_re, ...) so the - generated layer reflects the latest moderated values, not just the - original ODK submission stored in `data_`. - """ - skip = {json_blob_field, *_PROJECTION_EXCLUDED_FIELDS} - fields = [] - for f in model._meta.get_fields(): - if not getattr(f, "concrete", False): - continue - if f.many_to_one or f.one_to_one or f.many_to_many or f.one_to_many: - continue - if f.name in skip: - continue - fields.append(f.name) - return fields - - -def _merge_moderated(blob: dict, friendly: dict, friendly_canonical: bool) -> dict: - """ - Merge friendly DB column values into the raw ODK blob so the layer carries - both the original ODK keys (Settlements_name, GPS_point, ...) and the - moderated friendly columns (settlement_name, block_name, ...). - - `friendly_canonical=True` (model has a moderation extractor): friendly - columns are kept in sync with every moderation edit, so they win on - collision. `False` (no extractor, e.g. SWB_maintenance): moderation only - touches the blob, so the blob wins on collision. - - GeoPackage/SQLite (the downstream layer format) is case-insensitive on - column names, so a blob key like "GPS_point" and a friendly column - "gps_point" would collide on write. We dedupe case-insensitively in - favour of the canonical side; non-colliding keys (e.g. "Settlements_name" - vs "settlement_name") are both preserved. - """ - if friendly_canonical: - winner, loser = friendly, blob - else: - winner, loser = blob, friendly - - winner_lower = {k.lower() for k in winner} - loser_filtered = { - k: v for k, v in loser.items() - if k.lower() not in winner_lower - } - return {**loser_filtered, **winner} - - -# MARK: Fetch DB Data -def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: - """ - Build the CSV of records for the given (resource_type, plan_id, block) - by reading from our DB (post-moderation source of truth). - - Returns the number of rows actually written to the CSV; 0 means no - usable data was found and the caller should treat it as a soft 404. - """ - logger.info( - f"fetch_db_data: starting — resource_type={resource_type}, " - f"plan_id={plan_id}, block={block}, csv_path={csv_path}" - ) - - entry = _DB_CONFIG.get(resource_type) - if not entry: - logger.warning(f"fetch_db_data: unknown resource_type '{resource_type}'") - return 0 - - model, data_field, has_block_col = entry - projection_fields = _scalar_projection_fields(model, data_field) - friendly_canonical = model in _MODERATION_EXTRACTORS - logger.info( - f"fetch_db_data: querying {model.__name__}.{data_field} " - f"with plan_id={plan_id}, is_deleted=False" - + ( - f", block_name icontains '{block}'" - if has_block_col - else " (no block_name column, skipping DB block filter)" - ) - ) - logger.info( - f"fetch_db_data: projecting blob '{data_field}' + " - f"{len(projection_fields)} moderated column(s) " - f"(friendly_canonical={friendly_canonical}): {projection_fields}" - ) - - qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) - if has_block_col: - qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) - - raw_rows = list(qs.values(data_field, *projection_fields)) - logger.info( - f"fetch_db_data: DB returned {len(raw_rows)} record(s) for " - f"resource_type={resource_type}, plan_id={plan_id}" - ) - - response_list = [] - empty_blob_count = 0 - for row in raw_rows: - blob = row.get(data_field) or {} - if not blob: - empty_blob_count += 1 - continue - friendly = {k: v for k, v in row.items() if k != data_field} - response_list.append(_merge_moderated(blob, friendly, friendly_canonical)) - - if empty_blob_count: - logger.warning( - f"fetch_db_data: skipped {empty_blob_count} record(s) with empty " - f"{data_field}" - ) - - if not response_list: - logger.warning( - f"fetch_db_data: no usable records for resource_type={resource_type}, " - f"plan_id={plan_id}, block={block}" - ) - return 0 - - logger.info( - f"fetch_db_data: running transform for resource_type={resource_type} " - f"on {len(response_list)} record(s) (each enriched with " - f"{len(projection_fields)} moderated column(s))" - ) - - all_keys = set() - if resource_type == "settlement": - rows = modify_response_list_settlement(response_list, block, plan_id) - elif resource_type == "well": - rows = modify_response_list_well(response_list, block, plan_id) - elif resource_type == "waterbody": - rows = modify_response_list_waterbody(response_list, block, plan_id) - elif resource_type == "cropping": - rows = modify_reponse_list_cropping(response_list, block, plan_id) - elif resource_type in ["plan_gw", "main_swb", "plan_agri"]: - rows = modify_response_list_plan(response_list, block, plan_id) - for item in rows: - all_keys.update(extract_keys(item)) - elif resource_type == "livelihood": - rows = modify_response_list_livelihood(response_list, block, plan_id) - for item in rows: - all_keys.update(extract_keys(item)) - elif resource_type in [ - "main_gw", "main_swb_rs", "main_agri", "agrohorticulture", - ]: - rows = modify_response_list_work(response_list, block, plan_id) - for item in rows: - all_keys.update(extract_keys(item)) - else: - logger.warning( - f"fetch_db_data: no transform defined for resource_type='{resource_type}'" - ) - return 0 - - logger.info( - f"fetch_db_data: transform produced {len(rows)} row(s) " - f"(filtered from {len(response_list)}) for resource_type={resource_type}" - ) - - if not rows: - logger.warning( - f"fetch_db_data: transform returned empty list for " - f"resource_type={resource_type}, plan_id={plan_id}, block={block}" - ) - return 0 - - logger.info(f"fetch_db_data: writing CSV to {csv_path}") - _write_csv(resource_type, rows, all_keys, csv_path) - logger.info( - f"fetch_db_data: done — {len(rows)} row(s) written to {csv_path} " - f"(columns include {len(projection_fields)} moderated friendly field(s))" - ) - return len(rows) - - -def flatten_dict(d, parent_key="", sep="_"): - items = [] - for k, v in d.items(): - new_key = f"{parent_key}{sep}{k}" if parent_key else k - if isinstance(v, dict): - items.extend(flatten_dict(v, new_key, sep=sep).items()) - else: - items.append((new_key, v)) - return dict(items) - - -def extract_keys(d, parent_key="", sep="_"): - keys = [] - for k, v in d.items(): - new_key = f"{parent_key}{sep}{k}" if parent_key else k - keys.append(new_key) - if isinstance(v, dict): - keys.extend(extract_keys(v, new_key, sep=sep)) - return keys - - -# MARK: Bearer Token -def fetch_bearer_token(email: str, password: str) -> str: - try: - if _token_cache["token"] and _token_cache["expires_at"]: - now = datetime.now(timezone.utc) - if now < _token_cache["expires_at"]: - return _token_cache["token"] - - response = requests.post( - ODK_URL_SESSION, json={"email": email, "password": password} - ) - print("Response: ", response) - if response.status_code == 200: - response_data = response.json() - _token_cache["token"] = response_data.get("token") - _token_cache["expires_at"] = dateutil.parser.parse( - response_data.get("expiresAt") - ) - return _token_cache["token"] - else: - raise Exception( - f"Failed to fetch bearer token. Status code: {response.status_code}" - ) - except Exception as e: - print(f"An error occurred while fetching the bearer token: {str(e)}") - raise +import csv +import logging +import re +from datetime import datetime, timezone + +import dateutil.parser +import requests + +from dpr.models import ( + ODK_settlement, ODK_well, ODK_waterbody, + ODK_groundwater, ODK_agri, ODK_livelihood, ODK_crop, + SWB_maintenance, SWB_RS_maintenance, GW_maintenance, Agri_maintenance, + ODK_agrohorticulture, +) +from moderation.utils.utils import ( + MODEL_FIELD_EXTRACTORS as _MODERATION_EXTRACTORS, + extract_lat_lon_from_gps, +) +from utilities.constants import ODK_URL_SESSION + +logger = logging.getLogger(__name__) + +_token_cache = { + "token": None, + "expires_at": None, +} + +# MARK: Helper +def normalize_name(name): + """ + Normalize names for comparison by: + - Converting to lowercase + - Replacing spaces with underscores + - Removing extra whitespace + """ + if not name: + return "" + return name.lower().replace(" ", "_").strip() + +_RESOURCE_TYPES_FLAT_HEADER = frozenset({ + "settlement", "well", "waterbody", "cropping", +}) +_RESOURCE_TYPES_UNION_HEADER = frozenset({ + "plan_gw", "plan_agri", "main_swb", "main_gw", "main_swb_rs", "main_agri", + "livelihood", "agrohorticulture", +}) + + +def _write_csv(resource_type, modified_response_list, all_keys, csv_path): + if resource_type in _RESOURCE_TYPES_FLAT_HEADER: + header_keys = modified_response_list[0].keys() + with open(csv_path, "w", encoding="utf-8") as output_file: + dict_writer = csv.DictWriter( + output_file, fieldnames=header_keys, extrasaction="ignore" + ) + dict_writer.writeheader() + dict_writer.writerows(modified_response_list) + elif resource_type in _RESOURCE_TYPES_UNION_HEADER: + with open(csv_path, "w", newline="", encoding="utf-8") as csvfile: + dict_writer = csv.DictWriter(csvfile, fieldnames=list(all_keys)) + dict_writer.writeheader() + for item in modified_response_list: + dict_writer.writerow(flatten_dict(item)) + logger.info(f"CSV generated for '{resource_type}' at {csv_path}") + + +# MARK: Modify ODK Settlement Data +def modify_response_list_settlement(res, block, plan_id): + res_list = [] + logger.info( + "modify_response_list_settlement: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["sett_id"] = result["Settlements_id"] + result["sett_name"] = result["Settlements_name"] + try: + mgnrega_info = result.get("MNREGA_INFORMATION", {}) + except Exception: + logger.exception("modify_response_list_settlement: failed reading MNREGA_INFORMATION") + continue + if mgnrega_info: + result["job_aware"] = mgnrega_info.get("NREGA_aware", "") or 0 + result["job_applied"] = mgnrega_info.get("NREGA_applied", "") or 0 + result["job_card"] = mgnrega_info.get("NREGA_job_card", "") or 0 + result["without_jc"] = mgnrega_info.get("total_household", "") or 0 + result["work_days"] = mgnrega_info.get("NREGA_work_days", "") or 0 + result["past_work"] = mgnrega_info.get("q1", "") or "0" + result["raise_demand"] = mgnrega_info.get("select_one_Y_N", "") or "0" + result["demand"] = mgnrega_info.get("select_one_demands", "") or "0" + result["issues"] = mgnrega_info.get("select_multiple_issues", "") or "0" + result["community"] = ( + mgnrega_info.get("select_one_contributions", "") or "0" + ) + res_list.append(result) + return res_list + + +# MARK: Modify ODK Well Data +def modify_response_list_well(res, block, plan_id): + res_list = [] + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["well_id"] = result["well_id"] + + well_usage_section = result.get("Well_usage", {}) + try: + result["ben_settlement"] = result.get("beneficiary_settlement", "") or "NA" + result["owner"] = result.get("select_one_owns", "") or "NA" + result["hh_benefitted"] = result.get("households_benefited", "") or "NA" + result["caste"] = result.get("select_multiple_caste_use", "") or "NA" + result["functional"] = ( + well_usage_section.get("select_one_Functional_Non_functional", "") + or "NA" + ) + result["need_maintenance"] = ( + well_usage_section.get("select_one_maintenance", "") or "NA" + ) + repair_value = well_usage_section.get("select_one_repairs_well") + if repair_value: + repair_value = str(repair_value).lower() + if repair_value == "other": + result["repair"] = ( + well_usage_section.get("select_one_repairs_well_other", "") + or "NA" + ) + else: + result["repair"] = repair_value + else: + result["repair"] = "NA" + except Exception: + logger.exception("modify_response_list_well: failed enriching record") + continue + res_list.append(result) + + return res_list + + +# MARK: Modify ODK Waterbody Data +def modify_response_list_waterbody(res, block, plan_id): + res_list = [] + logger.info( + "modify_response_list_waterbody: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) + for result in res: + if result is None: + continue + if result.get("__system", {}).get("reviewState") == "rejected": + continue + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["wb_id"] = result["waterbodies_id"] + + # type_of_water_st = result["select_one_water_structure"] + # if type_of_water_st: + # type_of_water_st = str(type_of_water_st).lower() + # if type_of_water_st == "other": + # result["wbs_type"] = result.get("select_one_water_structure_other", "") or "" + # else: + # result["wbs_type"] = result.get("select_one_water_structure", "") or "" + # else: + # result["wbs_type"] = "0" + + result["wbs_type"] = result.get("select_one_water_structure", "") or "0" + + try: + manager = result["select_one_manages"] + if manager: + manager = str(manager).lower() + if manager == "other": + result["who_manages"] = result.get("text_one_manages", "") or "" + else: + result["who_manages"] = result.get("select_one_manages", "") or "" + else: + result["who_manages"] = "0" + + who_owns = result["select_one_owns"] + if who_owns: + who_owns = str(who_owns).lower() + if who_owns == "other" or who_owns == "any other": + result["owner"] = result.get("text_one_owns", "") or "" + else: + result["owner"] = result.get("select_one_owns", "") or "" + else: + result["owner"] = "0" + result["caste"] = result.get("select_multiple_caste_use", "") or "0" + result["hh_benefitted"] = result.get("households_benefited", "") or 0 + result["identified"] = result.get("select_one_identified", "") or "0" + result["need_maintenance"] = result.get("select_one_maintenance") or "0" + + # Handle the dynamic water structure dimensions + # water_structure_type = result.get("select_one_water_structure", "").lower().replace("_", " ") + # water_structure_dimension = {} + # for key, value in result.items(): + # if isinstance(value, dict): + # structure_type = key.lower().replace("_", " ") + # if structure_type == water_structure_type: + # water_structure_dimension = { + # "length": next((v for k, v in value.items() if k.startswith("Length")), None), + # "breadth": next((v for k, v in value.items() if k.startswith("Breadth")), None), + # "width": next((v for k, v in value.items() if k.startswith("Width")), None), + # "depth": next((v for k, v in value.items() if k.startswith("Depth")), None), + # "height": next((v for k, v in value.items() if k.startswith("Height")), None), + # } + # break + + # Add the dimensions to the result dictionary + # result.update(water_structure_dimension) + except Exception: + logger.exception("modify_response_list_waterbody: failed enriching record") + continue + res_list.append(result) + return res_list + + +# MARK: Modify ODK Cropping Data +def modify_reponse_list_cropping(res, block, plan_id): + res_list = [] + logger.info( + "modify_reponse_list_cropping: block=%s plan_id=%s input_records=%d", + block, plan_id, len(res) if res else 0, + ) + + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + + if str(result.get("plan_id")) != str(plan_id): + continue + + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["crop_id"] = result["__id"] + + # Settlement and land basic information (5 keys) + result["sett_name"] = result.get("beneficiary_settlement", "") or "" + result["uncropp_br"] = result.get("Uncropped_barren_land", "") or "" + result["irrigatn"] = result.get("select_multiple_widgets", "") or "" + result["land_cls"] = result.get("select_one_classified", "") or "" + result["crop_seas"] = result.get("select_one_practice", "") or "" + + # Kharif season information (3 keys) + result["crop_khrf"] = result.get("select_multiple_cropping_kharif", "") or "" + result["crop_kh_o"] = result.get("select_multiple_cropping_kharif_other", "") or "" + result["area_khrf"] = result.get("total_area_cultivation_kharif", "") or "" + + # Rabi season information (3 keys) + result["crops_rabi"] = result.get("select_multiple_cropping_Rabi", "") or "" + result["crop_rb_o"] = result.get("select_multiple_cropping_Rabi_other", "") or "" + result["area_rabi"] = result.get("total_area_cultivation_Rabi", "") or "" + + # Zaid season information (3 keys) + result["crops_zaid"] = result.get("select_multiple_cropping_Zaid", "") or "" + result["crop_zd_o"] = result.get("select_multiple_cropping_Zaid_other", "") or "" + result["area_zaid"] = result.get("total_area_cultivation_Zaid", "") or "" + + # Soil and productivity information (4 keys) + result["productiv"] = result.get("select_one_productivity", "") or "" + result["soil_deg"] = result.get("soil_degraded", "") or "" + result["deg_reas"] = result.get("select_one_reason_degradation", "") or "" + result["deg_reas2"] = result.get("select_one_reason_degradation_1", "") or "" + + res_list.append(result) + + return res_list + + +# MARK: Modify ODK Plan Data +def modify_response_list_plan(res, block, plan_id): + res_list = [] + for result in res: + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + result["work_id"] = result["work_id"] + + work_type = None + selected_work = None + + if "TYPE_OF_WORK" in result: + work_type = result["TYPE_OF_WORK"] + elif "TYPE_OF_WORK_ID" in result: + work_type = result["TYPE_OF_WORK_ID"] + + if work_type: + result["work_type"] = work_type + + work_type_key = re.sub(r"[^a-zA-Z0-9]+", "_", work_type) + + if work_type_key in result: + selected_work = result[work_type_key] + if selected_work: + result["selected_work"] = selected_work + else: + result["selected_work"] = work_type_key + elif work_type in result: + selected_work = result[work_type] + if selected_work: + result["selected_work"] = selected_work + else: + result["selected_work"] = work_type + else: + result["selected_work"] = work_type + + result["ben_settlement"] = result["beneficiary_settlement"] + result["ben_name"] = result["Beneficiary_Name"] + result["ben_contact"] = result["Beneficiary_Contact_Number"] + res_list.append(result) + + return res_list + + +# MARK: Modify ODK Livelihood Data +def modify_response_list_livelihood(res, block, plan_id): + res_list = [] + for result in res: + # if result["__system"]["reviewState"] != "rejected": + if result is None: + continue + + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + try: + if normalize_name(result.get("block_name").lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + latitude, longitude = extract_lat_lon_from_gps(result.get("GPS_point")) + if latitude is not None and longitude is not None: + result["latitude"] = latitude + result["longitude"] = longitude + + result["status_re"] = result["__system"]["reviewState"] + res_list.append(result) + return res_list + + +# MARK: Modify ODK Maintenance / Agrohorticulture (Generic) +def modify_response_list_work(res, block, plan_id): + """ + Robust transform for maintenance and agrohorticulture submissions. + Unlike `modify_response_list_plan`, this avoids bracket-access on keys + that maintenance/agrohorticulture blobs may not carry (e.g. work_id, + Beneficiary_Name). Block filter is best-effort: applied only when the + blob actually has block_name (these models have no block_name DB column). + """ + res_list = [] + for result in res: + if result is None: + continue + if not isinstance(result, dict): + continue + if result.get("__system", {}).get("reviewState") == "rejected": + continue + + blob_block = result.get("block_name") + if blob_block: + try: + if normalize_name(str(blob_block).lower()) != normalize_name(block): + continue + except AttributeError: + continue + + if str(result.get("plan_id")) != str(plan_id): + continue + + lat, lon = extract_lat_lon_from_gps(result.get("GPS_point")) + if lat is not None and lon is not None: + result["latitude"] = lat + result["longitude"] = lon + + sys_info = result.get("__system") or {} + if isinstance(sys_info, dict): + review_state = sys_info.get("reviewState") + if review_state: + result["status_re"] = review_state + + res_list.append(result) + return res_list + + +# Layer-build source-of-truth registry. Key = resource_type / work_type the +# `/add_resources` and `/add_works` endpoints accept. Tuple = (Model, JSON +# blob field, model has block_name column for DB-level pre-filtering). +# +# Resources (workspace=resources): settlement, well, waterbody, cropping +# Works (workspace=works): +# plan_gw — new recharge structures (groundwater) +# main_gw — maintenance of recharge structures +# plan_agri — new irrigation structures +# main_agri — maintenance of irrigation structures +# main_swb — surface water body maintenance (water-structure form) +# main_swb_rs — remote-sensed surface water body maintenance +# livelihood — livelihood +# agrohorticulture — agrohorticulture +_DB_CONFIG = { + "settlement": (ODK_settlement, "data_settlement", True), + "well": (ODK_well, "data_well", True), + "waterbody": (ODK_waterbody, "data_waterbody", True), + "cropping": (ODK_crop, "data_crop", False), + "plan_gw": (ODK_groundwater, "data_groundwater", True), + "plan_agri": (ODK_agri, "data_agri", True), + "livelihood": (ODK_livelihood, "data_livelihood", True), + "main_swb": (SWB_maintenance, "data_swb_maintenance", False), + "main_gw": (GW_maintenance, "data_gw_maintenance", False), + "main_swb_rs": (SWB_RS_maintenance, "data_swb_rs_maintenance", False), + "main_agri": (Agri_maintenance, "data_agri_maintenance", False), + # `data_agohorticulture` is the actual model field name — there is a + # spelling typo in the schema. Respecting it here to avoid a migration. + "agrohorticulture": (ODK_agrohorticulture, "data_agohorticulture", False), +} + +# Fields never useful to project alongside the JSON blob: the blob itself, +# soft-delete metadata, and relational fields (FKs serialise poorly via values()). +_PROJECTION_EXCLUDED_FIELDS = frozenset({ + "data_before_moderation", + "is_deleted", + "deleted_at", + "deleted_by", + "moderated_by", +}) + + +def _scalar_projection_fields(model, json_blob_field: str) -> list: + """ + Concrete, non-relational fields on `model` safe to project via `.values()` + alongside the raw ODK JSON blob. Captures everything moderation can edit + (settlement_name, block_name, nrega_*, lat/lon, status_re, ...) so the + generated layer reflects the latest moderated values, not just the + original ODK submission stored in `data_`. + """ + skip = {json_blob_field, *_PROJECTION_EXCLUDED_FIELDS} + fields = [] + for f in model._meta.get_fields(): + if not getattr(f, "concrete", False): + continue + if f.many_to_one or f.one_to_one or f.many_to_many or f.one_to_many: + continue + if f.name in skip: + continue + fields.append(f.name) + return fields + + +def _merge_moderated(blob: dict, friendly: dict, friendly_canonical: bool) -> dict: + """ + Merge friendly DB column values into the raw ODK blob so the layer carries + both the original ODK keys (Settlements_name, GPS_point, ...) and the + moderated friendly columns (settlement_name, block_name, ...). + + `friendly_canonical=True` (model has a moderation extractor): friendly + columns are kept in sync with every moderation edit, so they win on + collision. `False` (no extractor, e.g. SWB_maintenance): moderation only + touches the blob, so the blob wins on collision. + + GeoPackage/SQLite (the downstream layer format) is case-insensitive on + column names, so a blob key like "GPS_point" and a friendly column + "gps_point" would collide on write. We dedupe case-insensitively in + favour of the canonical side; non-colliding keys (e.g. "Settlements_name" + vs "settlement_name") are both preserved. + """ + if friendly_canonical: + winner, loser = friendly, blob + else: + winner, loser = blob, friendly + + winner_lower = {k.lower() for k in winner} + loser_filtered = { + k: v for k, v in loser.items() + if k.lower() not in winner_lower + } + return {**loser_filtered, **winner} + + +# MARK: Fetch DB Data +def fetch_db_data(csv_path, resource_type, block, plan_id) -> int: + """ + Build the CSV of records for the given (resource_type, plan_id, block) + by reading from our DB (post-moderation source of truth). + + Returns the number of rows actually written to the CSV; 0 means no + usable data was found and the caller should treat it as a soft 404. + """ + logger.info( + f"fetch_db_data: starting — resource_type={resource_type}, " + f"plan_id={plan_id}, block={block}, csv_path={csv_path}" + ) + + entry = _DB_CONFIG.get(resource_type) + if not entry: + logger.warning(f"fetch_db_data: unknown resource_type '{resource_type}'") + return 0 + + model, data_field, has_block_col = entry + projection_fields = _scalar_projection_fields(model, data_field) + friendly_canonical = model in _MODERATION_EXTRACTORS + logger.info( + f"fetch_db_data: querying {model.__name__}.{data_field} " + f"with plan_id={plan_id}, is_deleted=False" + + ( + f", block_name icontains '{block}'" + if has_block_col + else " (no block_name column, skipping DB block filter)" + ) + ) + logger.info( + f"fetch_db_data: projecting blob '{data_field}' + " + f"{len(projection_fields)} moderated column(s) " + f"(friendly_canonical={friendly_canonical}): {projection_fields}" + ) + + qs = model.objects.filter(plan_id=str(plan_id), is_deleted=False) + if has_block_col: + qs = qs.filter(block_name__icontains=block.replace("_", " ").strip()) + + raw_rows = list(qs.values(data_field, *projection_fields)) + logger.info( + f"fetch_db_data: DB returned {len(raw_rows)} record(s) for " + f"resource_type={resource_type}, plan_id={plan_id}" + ) + + response_list = [] + empty_blob_count = 0 + for row in raw_rows: + blob = row.get(data_field) or {} + if not blob: + empty_blob_count += 1 + continue + friendly = {k: v for k, v in row.items() if k != data_field} + response_list.append(_merge_moderated(blob, friendly, friendly_canonical)) + + if empty_blob_count: + logger.warning( + f"fetch_db_data: skipped {empty_blob_count} record(s) with empty " + f"{data_field}" + ) + + if not response_list: + logger.warning( + f"fetch_db_data: no usable records for resource_type={resource_type}, " + f"plan_id={plan_id}, block={block}" + ) + return 0 + + logger.info( + f"fetch_db_data: running transform for resource_type={resource_type} " + f"on {len(response_list)} record(s) (each enriched with " + f"{len(projection_fields)} moderated column(s))" + ) + + all_keys = set() + if resource_type == "settlement": + rows = modify_response_list_settlement(response_list, block, plan_id) + elif resource_type == "well": + rows = modify_response_list_well(response_list, block, plan_id) + elif resource_type == "waterbody": + rows = modify_response_list_waterbody(response_list, block, plan_id) + elif resource_type == "cropping": + rows = modify_reponse_list_cropping(response_list, block, plan_id) + elif resource_type in ["plan_gw", "main_swb", "plan_agri"]: + rows = modify_response_list_plan(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + elif resource_type == "livelihood": + rows = modify_response_list_livelihood(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + elif resource_type in [ + "main_gw", "main_swb_rs", "main_agri", "agrohorticulture", + ]: + rows = modify_response_list_work(response_list, block, plan_id) + for item in rows: + all_keys.update(extract_keys(item)) + else: + logger.warning( + f"fetch_db_data: no transform defined for resource_type='{resource_type}'" + ) + return 0 + + logger.info( + f"fetch_db_data: transform produced {len(rows)} row(s) " + f"(filtered from {len(response_list)}) for resource_type={resource_type}" + ) + + if not rows: + logger.warning( + f"fetch_db_data: transform returned empty list for " + f"resource_type={resource_type}, plan_id={plan_id}, block={block}" + ) + return 0 + + logger.info(f"fetch_db_data: writing CSV to {csv_path}") + _write_csv(resource_type, rows, all_keys, csv_path) + logger.info( + f"fetch_db_data: done — {len(rows)} row(s) written to {csv_path} " + f"(columns include {len(projection_fields)} moderated friendly field(s))" + ) + return len(rows) + + +def flatten_dict(d, parent_key="", sep="_"): + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + + +def extract_keys(d, parent_key="", sep="_"): + keys = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + keys.append(new_key) + if isinstance(v, dict): + keys.extend(extract_keys(v, new_key, sep=sep)) + return keys + + +# MARK: Bearer Token +def fetch_bearer_token(email: str, password: str) -> str: + try: + if _token_cache["token"] and _token_cache["expires_at"]: + now = datetime.now(timezone.utc) + if now < _token_cache["expires_at"]: + return _token_cache["token"] + + response = requests.post( + ODK_URL_SESSION, json={"email": email, "password": password} + ) + print("Response: ", response) + if response.status_code == 200: + response_data = response.json() + _token_cache["token"] = response_data.get("token") + _token_cache["expires_at"] = dateutil.parser.parse( + response_data.get("expiresAt") + ) + return _token_cache["token"] + else: + raise Exception( + f"Failed to fetch bearer token. Status code: {response.status_code}" + ) + except Exception as e: + print(f"An error occurred while fetching the bearer token: {str(e)}") + raise diff --git a/stats_generator/mws_indicators.py b/stats_generator/mws_indicators.py index 49cedf07..e67d272c 100644 --- a/stats_generator/mws_indicators.py +++ b/stats_generator/mws_indicators.py @@ -1,1130 +1,1130 @@ -import os -import requests, json -import pandas as pd -import pymannkendall as mk -import numpy as np -import ast -from nrm_app.settings import EXCEL_PATH -from .utils import get_url -from rest_framework.response import Response -from rest_framework import status -from django.http import HttpResponse -from .models import LayerInfo - - -def create_geojson_for_all_mws(existing_geojson_path, df, new_geojson_path): - with open(existing_geojson_path) as f: - existing_data = json.load(f) - - features = [] - - for _, row in df.iterrows(): - uid = row["mws_id"] - geometry = None - - for feature in existing_data["features"]: - if feature["properties"].get("uid") == uid: - geometry = feature["geometry"] - break - - if geometry is None: - print( - f"No geometry found for uid: {uid}. Using default geometry (e.g., None)." - ) - geometry = {"type": "Point", "coordinates": [0, 0]} - - properties = row.to_dict() - - new_feature = { - "type": "Feature", - "geometry": geometry, - "properties": properties, - } - features.append(new_feature) - - new_feature_collection = {"type": "FeatureCollection", "features": features} - - with open(new_geojson_path, "w") as f: - json.dump(new_feature_collection, f) - - -def generate_mws_data_for_kyl_filters( - state, district, block, file_type, regenerate=None -): - state_folder = state.replace(" ", "_").upper() - district_folder = district.replace(" ", "_").upper() - file_xl_path = os.path.join( - EXCEL_PATH, - "data/stats_excel_files", - state_folder, - district_folder, - f"{district}_{block}", - ) - if regenerate: - file_path = None - else: - file_path = get_mws_KYL_filter_data(state, district, block, file_type) - if not file_path: - try: - sheets = { - "hydrological_annual": -1, - "terrain": -1, - "croppingIntensity_annual": -1, - "surfaceWaterBodies_annual": -1, - "croppingDrought_kharif": -1, - "nrega_annual": -1, - "mws_intersect_villages": -1, - "change_detection_degradation": -1, - "change_detection_afforestation": -1, - "change_detection_deforestation": -1, - "change_detection_urbanization": -1, - "change_detection_cropintensity": -1, - "terrain_lulc_slope": -1, - "terrain_lulc_plain": -1, - "restoration_vector": -1, - "aquifer_vector": -1, - "soge_vector": -1, - "lcw_conflict": -1, - "factory_csr": -1, - "mining": -1, - "green_credit": -1, - "mws_intersect_swb": -1, - "dem": -1, - "canal": -1, - "river": -1, - "lulc_vector": -1, - "drainage_density": -1, - } - - try: - with pd.ExcelFile(file_xl_path + ".xlsx") as xl: - available_sheets = xl.sheet_names # Get list of available sheets - - # Try to parse each sheet if it exists - for sheet_name in sheets.keys(): - if sheet_name in available_sheets: - try: - sheets[sheet_name] = xl.parse(sheet_name) - except Exception as e: - print(f"Error parsing sheet {sheet_name}: {e}") - sheets[sheet_name] = -1 - else: - print(f"Sheet {sheet_name} not found in Excel file") - sheets[sheet_name] = -1 - - except Exception as e: - print(f"Error reading Excel file: {e}") - # Return all sheets as -1 if the file can't be read - return {k: -1 for k in sheets.keys()} - - results = [] - df_hydrological_annual = sheets["hydrological_annual"] - - for specific_mws_id in df_hydrological_annual["UID"].unique(): - hydro_annual_mws_data = df_hydrological_annual[ - df_hydrological_annual["UID"] == specific_mws_id - ] - precipitation_columns = hydro_annual_mws_data.filter( - like="Precipitation" - ) # Avg_precipitation - total_percipitation_column = precipitation_columns.shape[1] - sum_precipitation = precipitation_columns.sum(axis=1).sum() - avg_percipitation = round( - sum_precipitation / total_percipitation_column, 4 - ) - - try: - terrain_vector_mws_data = sheets["terrain"][ - sheets["terrain"]["UID"] == specific_mws_id - ] - terrainCluster_ID = terrain_vector_mws_data.get( - "terrain_cluster_id", None - ).iloc[ - 0 - ] # terrain - except: - terrainCluster_ID = -9999 - - try: - df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ - sheets["croppingIntensity_annual"]["UID"] == specific_mws_id - ] - df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) - - crp_Intensity_columns = df_crp_intensity_mws_data.filter( - like="cropping_intensity_unit_less" - ) # cropping_intensity_avg - total_crp_Intensity_column = crp_Intensity_columns.shape[1] - sum_crp_Intensity = crp_Intensity_columns.sum(axis=1).sum() - cropping_intensity_avg = round( - ( - sum_crp_Intensity / total_crp_Intensity_column - if total_crp_Intensity_column > 0 - else 0 - ), - 4, - ) - - ######### Cropping Intensity Trend ################# - crp_intensity_T = df_crp_intensity_mws_data.filter( - like="cropping_intensity_unit_less" - ).dropna() # Drop rows with NaN for trend calculation - crp_intensity_T = crp_intensity_T.squeeze().tolist()[:-3] - result = mk.original_test(crp_intensity_T) - - def sens_slope(data): - slopes = [] - for i in range(len(data) - 1): - for j in range(i + 1, len(data)): - s = (data[j] - data[i]) / (j - i) - slopes.append(s) - return np.median(slopes) - - cropping_intensity_trend_value = sens_slope(crp_intensity_T) - cropping_intensity_trend = None - if result.trend == "no trend": - cropping_intensity_trend = "0" - elif result.trend == "increasing": - cropping_intensity_trend = "1" - else: - cropping_intensity_trend = "-1" - - # Total cropped area, replace NaN with 0 for these columns as well - total_cropped_area = df_crp_intensity_mws_data.iloc[0][ - "sum_area_in_ha" - ] - - # Handle single-cropped area calculation - single_crop_columns = df_crp_intensity_mws_data.filter( - like="single_cropped_area" - ) # avg_single_cropped - total_single_crop_column = single_crop_columns.shape[1] - sum_single_crop = single_crop_columns.sum(axis=1).sum() - percent_single_crop = ( - sum_single_crop * 100 / total_cropped_area - if total_cropped_area > 0 - else 0 - ) - avg_single_cropped = round( - ( - percent_single_crop / total_single_crop_column - if total_single_crop_column > 0 - else 0 - ), - 4, - ) - - # Handle doubly-cropped area calculation - double_crop_columns = df_crp_intensity_mws_data.filter( - like="doubly_cropped_area" - ) # avg_double_cropped - total_double_crop_column = double_crop_columns.shape[1] - sum_double_crop = double_crop_columns.sum(axis=1).sum() - percent_double_crop = ( - sum_double_crop * 100 / total_cropped_area - if total_cropped_area > 0 - else 0 - ) - avg_double_cropped = round( - ( - percent_double_crop / total_double_crop_column - if total_double_crop_column > 0 - else 0 - ), - 4, - ) - - # Handle triply-cropped area calculation - triply_crop_columns = df_crp_intensity_mws_data.filter( - like="triply_cropped_area" - ) # avg_triply_cropped - total_triply_crop_column = triply_crop_columns.shape[1] - sum_triply_crop = triply_crop_columns.sum(axis=1).sum() - percent_triply_crop = ( - sum_triply_crop * 100 / total_cropped_area - if total_cropped_area > 0 - else 0 - ) - avg_triply_cropped = round( - ( - percent_triply_crop / total_triply_crop_column - if total_triply_crop_column > 0 - else 0 - ), - 4, - ) - - except Exception as e: - # Handle exception and ensure all variables are set - cropping_intensity_avg = -9999 - cropping_intensity_trend = -9999 - avg_single_cropped = -9999 - avg_double_cropped = -9999 - avg_triply_cropped = -9999 - print(f"Error occurred: {e}") - - - ##################### SWB ##################### - try: - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ - sheets["croppingIntensity_annual"]["UID"] == specific_mws_id - ] - - df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) - df_swb_annual_mws_data = df_swb_annual_mws_data.fillna(0) - swb_area_kharif_columns = df_swb_annual_mws_data.filter( - like="kharif_area" - ) - single_kharif_crop_columns = df_crp_intensity_mws_data.filter( - like="single_kharif_cropped_area" - ) - double_crop_columns = df_crp_intensity_mws_data.filter( - like="doubly_cropped_area" - ) - triply_crop_columns = df_crp_intensity_mws_data.filter( - like="triply_cropped_area" - ) - - combined_columns_kharif = single_kharif_crop_columns.add( - double_crop_columns, fill_value=0 - ) - combined_columns_kharif = combined_columns_kharif.add( - triply_crop_columns, fill_value=0 - ) - total_cropped_area_kharif = combined_columns_kharif.sum( - axis=1 - ).sum() - total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] - sum_swb_area_kharif = swb_area_kharif_columns.sum(axis=1).sum() - - avg_wsr_ratio_kharif = ( - sum_swb_area_kharif / total_cropped_area_kharif - if total_cropped_area_kharif > 0 - else 0 - ) - avg_wsr_ratio_kharif = round( - avg_wsr_ratio_kharif * 100 / total_swb_area_kharif_column, 4 - ) - swb_area_rabi_columns = df_swb_annual_mws_data.filter( - like="rabi_area" - ) - single_non_kharif_crop_columns = df_crp_intensity_mws_data.filter( - like="single_non_kharif_cropped_area" - ) - - # Combine the cropping areas and calculate total cropped area for Rabi - combined_columns_rabi = single_non_kharif_crop_columns.add( - double_crop_columns, fill_value=0 - ) - combined_columns_rabi = combined_columns_rabi.add( - triply_crop_columns, fill_value=0 - ) - total_cropped_area_rabi = combined_columns_rabi.sum(axis=1).sum() - - total_swb_rabi_column = swb_area_rabi_columns.shape[1] - sum_swb_area_rabi = swb_area_rabi_columns.sum(axis=1).sum() - - # Average WSR ratio for Rabi - avg_wsr_ratio_rabi = ( - sum_swb_area_rabi / total_cropped_area_rabi - if total_cropped_area_rabi > 0 - else 0 - ) - avg_wsr_ratio_rabi = round( - avg_wsr_ratio_rabi * 100 / total_swb_rabi_column, 4 - ) - swb_area_zaid_columns = df_swb_annual_mws_data.filter( - like="zaid_area" - ) - total_cropped_area_zaid = triply_crop_columns.sum(axis=1).sum() - - total_swb_zaid_column = swb_area_zaid_columns.shape[1] - sum_swb_area_zaid = swb_area_zaid_columns.sum(axis=1).sum() - avg_wsr_ratio_zaid = ( - sum_swb_area_zaid / total_cropped_area_zaid - if total_cropped_area_zaid > 0 - else 0 - ) - avg_wsr_ratio_zaid = round( - avg_wsr_ratio_zaid * 100 / total_swb_zaid_column, 4 - ) - - except Exception as e: - avg_wsr_ratio_kharif = -9999 - avg_wsr_ratio_rabi = -9999 - avg_wsr_ratio_zaid = -9999 - print(f"Error occurred: {e}") - - ############ Swb_average - avg_kharif_surface_water_mws = -9999 - avg_rabi_surface_water_mws = -9999 - avg_zaid_surface_water_mws = -9999 - try: - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - if not df_swb_annual_mws_data.empty: - total_swb_area = df_swb_annual_mws_data.iloc[0][ - "total_swb_area_in_ha" - ] - - if total_swb_area != 0: # Check if total_swb_area is not zero - swb_area_kharif_columns = df_swb_annual_mws_data.filter( - like="kharif_area" - ) - total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] - sum_swb_area_kharif = ( - swb_area_kharif_columns.sum(axis=1).sum() / total_swb_area - ) - avg_kharif_surface_water_mws = round( - ( - sum_swb_area_kharif * 100 / total_swb_area_kharif_column - if total_swb_area_kharif_column > 0 - else 0 - ), - 4, - ) - - swb_rabi_area_columns = df_swb_annual_mws_data.filter( - like="rabi_area" - ) - total_swb_rabi_area_column = swb_rabi_area_columns.shape[1] - sum_swb_rabi_area = ( - swb_rabi_area_columns.sum(axis=1).sum() / total_swb_area - ) - avg_rabi_surface_water_mws = round( - ( - sum_swb_rabi_area * 100 / total_swb_rabi_area_column - if total_swb_rabi_area_column > 0 - else 0 - ), - 4, - ) - - swb_zaid_area_columns = df_swb_annual_mws_data.filter( - like="zaid_area" - ) - total_swb_zaid_area_column = swb_zaid_area_columns.shape[1] - sum_swb_zaid_area = ( - swb_zaid_area_columns.sum(axis=1).sum() / total_swb_area - ) - avg_zaid_surface_water_mws = round( - ( - sum_swb_zaid_area * 100 / total_swb_zaid_area_column - if total_swb_zaid_area_column > 0 - else 0 - ), - 4, - ) - else: - avg_perc_kharif_surface_water_mws = ( - avg_perc_rabi_surface_water_mws - ) = avg_perc_zaid_surface_water_mws = 0 - - except: - avg_perc_kharif_surface_water_mws = ( - avg_perc_rabi_surface_water_mws - ) = avg_perc_zaid_surface_water_mws = -9999 - - ################# SWB Trend ###################### - try: - df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ - sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id - ] - swb_T = df_swb_annual_mws_data.filter( - like="total_area_in_ha" - ).dropna() # Drop rows with NaN for trend calculation - swb_T = swb_T.iloc[0].dropna().tolist() - result = mk.original_test(swb_T) - - trend_swb = None - if result.trend == "no trend": - trend_swb = "0" - elif result.trend == "increasing": - trend_swb = "1" - else: - trend_swb = "-1" - except: - trend_swb = -9999 - - ######### G Trend ################# - try: - G_Trend = ( - hydro_annual_mws_data.filter(like="G") - .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) - .dropna() - ) - G_Trend = G_Trend.squeeze().tolist() - result = mk.original_test(G_Trend) - - def sens_slope(data): - slopes = [] - for i in range(len(data) - 1): - for j in range(i + 1, len(data)): - s = (data[j] - data[i]) / (j - i) - slopes.append(s) - return np.median(slopes) - - trend_g_value = sens_slope(G_Trend) - trend_g = None - if result.trend == "no trend": - trend_g = "0" - elif result.trend == "increasing": - trend_g = "1" - else: - trend_g = "-1" - except: - trend_g = -9999 - - ######### drought_category ############## - try: - - layers = LayerInfo.objects.get( - layer_type="vector", workspace="drought" - ) - years = [ - str(year) - for year in range(layers.start_year, layers.end_year + 1) - ] - - df_crpDrought_mws_data = sheets["croppingDrought_kharif"][ - sheets["croppingDrought_kharif"]["UID"] == specific_mws_id - ] - - sum_moderate_severe = { - year: ( - 1 - if ( - df_crpDrought_mws_data.iloc[0][ - f"Moderate_in_weeks_{year}" - ] - + df_crpDrought_mws_data.iloc[0][ - f"Severe_in_weeks_{year}" - ] - ) - >= 5 - else 0 - ) - for year in years - } - sum_of_values = sum(sum_moderate_severe.values()) - drought_category = None - if sum_of_values >= 2: - drought_category = 2 - else: - drought_category = sum_of_values - - ######## avg_dry_spell_in_weeks - dryspell_columns = df_crpDrought_mws_data.filter( - like="drysp_unit_4_weeks" - ) # avg_dry_spell_in_weeks - total_dryspell_column = dryspell_columns.shape[1] - sum_dryspell = dryspell_columns.sum(axis=1).sum() - avg_dry_spell_in_weeks = round( - ( - sum_dryspell / total_dryspell_column - if total_dryspell_column > 0 - else 0 - ), - 4, - ) - except: - drought_category = -9999 - avg_dry_spell_in_weeks = -9999 - - ################# avg_runoff - runoff_columns = hydro_annual_mws_data.filter( - like="RunOff" - ) # avg_runoff - total_runoff_column = runoff_columns.shape[1] - sum_runoff = runoff_columns.sum(axis=1).sum() - avg_runoff = sum_runoff / total_runoff_column - - ############## Nrega Asset ########################## - try: - df_nrega_assets_mws_data = sheets["nrega_annual"][ - sheets["nrega_annual"]["mws_id"] == specific_mws_id - ] - nrega_assets_sum = ( - df_nrega_assets_mws_data.iloc[:, 1:] - .select_dtypes(include="number") - .sum() - .sum() - ) - except: - nrega_assets_sum = -9999 - - ############ MWS Intersect Villages ######################## - try: - df_mws_inters_villages_mws_data = sheets["mws_intersect_villages"][ - sheets["mws_intersect_villages"]["MWS UID"] == specific_mws_id - ] - mws_intersect_villages = df_mws_inters_villages_mws_data.get( - "Village IDs", None - ).iloc[0] - mws_intersect_villages = ast.literal_eval(mws_intersect_villages) - except: - mws_intersect_villages = [] - - ############ Change Detection Degradation ################### - try: - df_change_degr_detection_mws_data = sheets[ - "change_detection_degradation" - ][sheets["change_detection_degradation"]["UID"] == specific_mws_id] - degr_sum = ( - df_change_degr_detection_mws_data[ - [ - "farm_to_barren_area_in_ha", - "farm_to_scrub_land_area_in_ha", - ] - ] - .sum(axis=1) - .iloc[0] - ) - df_change_crp_detection_mws_data = sheets[ - "change_detection_cropintensity" - ][ - sheets["change_detection_cropintensity"]["UID"] - == specific_mws_id - ] - crp_sum = ( - df_change_crp_detection_mws_data[ - [ - "double_to_single_area_in_ha", - "triple_to_double_area_in_ha", - "triple_to_single_area_in_ha", - ] - ] - .sum(axis=1) - .iloc[0] - ) - degradation_land_area = degr_sum + crp_sum - change_in_cropping_intensity_area = ( - df_change_crp_detection_mws_data.get( - "total_change_crop_intensity_area_in_ha", None - ).iloc[0] - ) - - except: - degradation_land_area = -9999 - change_in_cropping_intensity_area = -9999 - - ############ Change Detection Afforestation ################### - try: - df_change_affo_detection_mws_data = sheets[ - "change_detection_afforestation" - ][ - sheets["change_detection_afforestation"]["UID"] - == specific_mws_id - ] - afforestation_column = [ - "barren_to_forest_area_in_ha", - "farm_to_forest_area_in_ha", - ] - afforestation_land_area = df_change_affo_detection_mws_data.get( - "total_afforestation_area_in_ha", None - ).iloc[0] - except: - afforestation_land_area = -9999 - - ############ Change Detection Deforestation ################### - try: - df_change_defo_detection_mws_data = sheets[ - "change_detection_deforestation" - ][ - sheets["change_detection_deforestation"]["UID"] - == specific_mws_id - ] - deforestation_land_area = df_change_defo_detection_mws_data.get( - "total_deforestation_area_in_ha", None - ).iloc[0] - except: - deforestation_land_area = -9999 - - ############ Change Detection Urbanization ################### - try: - df_change_urba_detection_mws_data = sheets[ - "change_detection_urbanization" - ][sheets["change_detection_urbanization"]["UID"] == specific_mws_id] - urbanization_land_area = df_change_urba_detection_mws_data.get( - "total_urbanization_area_in_ha", None - ).iloc[0] - except: - urbanization_land_area = -9999 - - ############# Terrain lulc slope / plain ##################### - try: - df_lulc_slope_mws_data = sheets["terrain_lulc_slope"][ - sheets["terrain_lulc_slope"]["UID"] == specific_mws_id - ] - lulc_slope_category = ( - df_lulc_slope_mws_data.get("cluster_name", pd.NA).iloc[0] - if not df_lulc_slope_mws_data.empty - else None - ) - - except: - lulc_slope_category = -9999 - - try: - df_lulc_plain_mws_data = sheets["terrain_lulc_plain"][ - sheets["terrain_lulc_plain"]["UID"] == specific_mws_id - ] - lulc_plain_category = ( - df_lulc_plain_mws_data.get("cluster_name", pd.NA).iloc[0] - if not df_lulc_plain_mws_data.empty - else None - ) - - except: - lulc_plain_category = -9999 - - ################# Restoration Vector ######################### - try: - df_restoration_vector_mws_data = sheets["restoration_vector"][ - sheets["restoration_vector"]["UID"] == specific_mws_id - ] - wide_scale_restoration = df_restoration_vector_mws_data.get( - "wide_scale_restoration_area_in_ha", None - ).iloc[0] - area_protection = df_restoration_vector_mws_data.get( - "protection_area_in_ha", None - ).iloc[0] - except: - wide_scale_restoration = -9999 - area_protection = -9999 - - ################# Aquifer Vector ######################### - aquifer_class_map = {0: "Hard Rock", 1: "Alluvial"} - - class_to_id = {v: k for k, v in aquifer_class_map.items()} - try: - df_aquifer_vector_mws_data = sheets["aquifer_vector"][ - sheets["aquifer_vector"]["UID"] == specific_mws_id - ] - aquifer_class_name = df_aquifer_vector_mws_data.get( - "aquifer_class", None - ).iloc[0] - if aquifer_class_name == "Alluvium": - aquifer_class_name = "Alluvial" - aquifer_class = int(class_to_id.get(aquifer_class_name, "")) - except Exception: - aquifer_class = -9999 - - ################# SOGE Vector ######################### - Soge_class = { - 0: "Safe", - 1: "Semi-Critical", - 2: "Critical", - 3: "Over Exploited", - 4: "Not Assessed", - } - - class_to_id = {v: k for k, v in Soge_class.items()} - try: - df_soge_vector_mws_data = sheets["soge_vector"][ - sheets["soge_vector"]["UID"] == specific_mws_id - ] - soge_class_name = df_soge_vector_mws_data.get( - "class_name", None - ).iloc[0] - soge_class = int( - class_to_id.get(soge_class_name, "") - ) # Returns None if not found - except Exception: - soge_class = -9999 - - ################## LCW Conflict ###################### - ## if count is 0 then Areas with no conflicts else Areas with conflicts - try: - lcw_conflict_count = sheets["lcw_conflict"][ - sheets["lcw_conflict"]["UID"] == specific_mws_id - ].shape[0] - if lcw_conflict_count == 0: - lcw_conflict = 0 - else: - lcw_conflict = 1 - except Exception as e: - lcw_conflict = -9999 - - ################## mining ###################### - ## if count is 0 then Areas with no mining else Areas with mining - try: - mining_count = sheets["mining"][ - sheets["mining"]["UID"] == specific_mws_id - ].shape[0] - if mining_count == 0: - mining = 0 - else: - mining = 1 - except Exception as e: - mining = -9999 - - ################## green credit ###################### - ## if count is 0 then Areas with no green credit else Areas with green credit - try: - green_credit_count = sheets["green_credit"][ - sheets["green_credit"]["UID"] == specific_mws_id - ].shape[0] - if green_credit_count == 0: - green_credit = 0 - else: - green_credit = 1 - except Exception as e: - green_credit = -9999 - - ################## factory csr ###################### - ## if count is 0 then Areas with no factory else Areas with factory - try: - factory_csr_count = sheets["factory_csr"][ - sheets["factory_csr"]["UID"] == specific_mws_id - ].shape[0] - if factory_csr_count == 0: - factory_csr = 0 - else: - factory_csr = 1 - except Exception as e: - factory_csr = -9999 - - ############ MWS Intersect Swb ######################## - try: - swb_df = sheets["mws_intersect_swb"] - - if swb_df is not -1 and not swb_df.empty: - mws_swb_data = swb_df[swb_df["UID"] == specific_mws_id] - - mws_intersect_swb = mws_swb_data.apply( - lambda row: { - "swbId": str(row["SWB_UID"]), - "swbName": ( - str(row["Waterbodies_name"]) - if pd.notna(row["Waterbodies_name"]) - else "" - ), - "latitude": ( - float(row["Latitude"]) - if pd.notna(row["Latitude"]) - else None - ), - "longitude": ( - float(row["Longitude"]) - if pd.notna(row["Longitude"]) - else None - ), - }, - axis=1, - ).tolist() - else: - mws_intersect_swb = [] - - except Exception as e: - print(f"Error in SWB funda: {e}") - mws_intersect_swb = [] - - ############ DEM (Digital Elevation Model) ######################## - try: - dem_df = sheets["dem"] - if dem_df is not -1 and not dem_df.empty: - mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] - - # Average of all UID mean elevations - overall_mean_elevation = dem_df["mean_elevation_in_m"].mean() - if not mws_dem_data.empty: - row = mws_dem_data.iloc[0] - relief = round( - row["max_elevation_in_m"] - row["min_elevation_in_m"], 2 - ) - mean_elevation = round(row["mean_elevation_in_m"], 2) - - # Relative mean elevation - if overall_mean_elevation != 0: - relative_mean_elevation = round( - (mean_elevation - overall_mean_elevation), 2 - ) - else: - relative_mean_elevation = 0 - - else: - relief = 0 - mean_elevation = 0 - relative_mean_elevation = 0 - - else: - relief = -9999 - mean_elevation = -9999 - relative_mean_elevation = -9999 - - except Exception as e: - print(f"Error in getting DEM data: {e}") - relief = -9999 - mean_elevation = -9999 - relative_mean_elevation = -9999 - - ############ Canal ######################## - try: - canal_df = sheets["canal"] - if canal_df is not -1 and not canal_df.empty: - mws_canal_data = canal_df[canal_df["UID"] == specific_mws_id] - if not mws_canal_data.empty: - canal_available = True - else: - canal_available = False - - else: - canal_available = False - - except Exception as e: - print(f"Error in getting canal data: {e}") - canal_available = -9999 - - ############ Canal ######################## - try: - river_df = sheets["river"] - if river_df is not -1 and not river_df.empty: - mws_river_data = river_df[river_df["UID"] == specific_mws_id] - if not mws_river_data.empty: - river_available = True - else: - river_available = False - - else: - river_available = False - - except Exception as e: - print(f"Error in getting canal data: {e}") - river_available = -9999 - - ############ lulc vector ######################## - try: - lulc_shrub_percent = -9999 - lulc_forest_percent = -9999 - lulc_crop_percent = -9999 - - lulc_df = sheets["lulc_vector"] - - if lulc_df is not -1 and not lulc_df.empty: - - mws_lulc_data = lulc_df[lulc_df["UID"] == specific_mws_id] - - if not mws_lulc_data.empty: - - row = mws_lulc_data.iloc[0] - - # Total area - area_in_ha = float(row.get("area_in_ha", 0)) - - # Shrub - shrub_cols = [ - col - for col in lulc_df.columns - if col.startswith("shrub_scrub_in_ha_") - ] - - lulc_shrub_area = round( - sum(row[col] for col in shrub_cols) / len(shrub_cols), 2 - ) - - # Forest - forest_cols = [ - col - for col in lulc_df.columns - if col.startswith("tree_forest_in_ha_") - ] - - lulc_forest_area = round( - sum(row[col] for col in forest_cols) / len(forest_cols), - 2, - ) - - if area_in_ha > 0: - lulc_shrub_percent = round( - (lulc_shrub_area / area_in_ha) * 100, 2 - ) - - lulc_forest_percent = round( - (lulc_forest_area / area_in_ha) * 100, 2 - ) - - df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ - sheets["croppingIntensity_annual"]["UID"] == specific_mws_id - ] - - crp_row = df_crp_intensity_mws_data.iloc[0] - area_in_ha = float(crp_row.get("area_in_ha", 0)) - cropped_area_in_ha = float(crp_row.get("sum_area_in_ha", 0)) - - lulc_crop_percent = round( - (cropped_area_in_ha / area_in_ha) * 100, 2 - ) - - else: - lulc_shrub_percent = -9999 - lulc_forest_percent = -9999 - lulc_crop_percent = -9999 - except Exception as e: - print(f"Error in LULC vector: {e}") - - lulc_shrub_percent = -9999 - lulc_forest_percent = -9999 - lulc_crop_percent = -9999 - - ############ Canal ######################## - try: - drainage_density_df = sheets["drainage_density"] - if drainage_density_df is not -1 and not drainage_density_df.empty: - mws_drainage_density_data = drainage_density_df[ - drainage_density_df["UID"] == specific_mws_id - ] - if not mws_drainage_density_data.empty: - row = mws_drainage_density_data.iloc[0] - drainage_density = round(row["drainage_density_std_in_km_per_km2"], 2) - else: - drainage_density = 0 - - else: - drainage_density = -9999 - - - except Exception as e: - print(f"Error in getting drainage_density data: {e}") - drainage_density = -9999 - - results.append( - { - "mws_id": specific_mws_id, - "terrainCluster_ID": terrainCluster_ID, - "avg_precipitation": avg_percipitation, - "cropping_intensity_trend": cropping_intensity_trend, - "cropping_intensity_avg": cropping_intensity_avg, - "avg_single_cropped": avg_single_cropped, - "avg_double_cropped": avg_double_cropped, - "avg_triple_cropped": avg_triply_cropped, - "avg_wsr_ratio_kharif": avg_wsr_ratio_kharif, - "avg_wsr_ratio_rabi": avg_wsr_ratio_rabi, - "avg_wsr_ratio_zaid": avg_wsr_ratio_zaid, - "avg_kharif_surface_water_mws": avg_kharif_surface_water_mws, - "avg_rabi_surface_water_mws": avg_rabi_surface_water_mws, - "avg_zaid_surface_water_mws": avg_zaid_surface_water_mws, - "trend_swb": trend_swb, - "trend_g": trend_g, - "drought_category": drought_category, - "avg_number_dry_spell": avg_dry_spell_in_weeks, - "avg_runoff": round(avg_runoff, 4), - "total_nrega_assets": nrega_assets_sum, - "mws_intersect_villages": mws_intersect_villages, - "degradation_land_area": round(degradation_land_area, 4), - "increase_in_tree_cover": round(afforestation_land_area, 4), - "decrease_in_tree_cover": round(deforestation_land_area, 4), - "degradation_cropping_intensity": round( - change_in_cropping_intensity_area, 4 - ), - "urbanization_area": round(urbanization_land_area, 4), - "lulc_slope_category": lulc_slope_category, - "lulc_plain_category": lulc_plain_category, - "area_wide_scale_restoration": round(wide_scale_restoration, 4), - "area_protection": round(area_protection, 4), - "aquifer_class": aquifer_class, - "soge_class": soge_class, - "lcw_conflict": lcw_conflict, - "mining": mining, - "green_credit": green_credit, - "factory_csr": factory_csr, - "mws_intersect_swb": mws_intersect_swb, - "relief": relief, - "mean_elevation": mean_elevation, - "relative_mean_elevation": relative_mean_elevation, - "canal_available": canal_available, - "river_available": river_available, - "lulc_shrub_percent": lulc_shrub_percent, - "lulc_forest_percent": lulc_forest_percent, - "lulc_crop_percent": lulc_crop_percent, - "drainage_density": drainage_density, - } - ) - - results_df = pd.DataFrame(results) - if file_type == "xlsx": - results_df.to_excel(file_xl_path + "_KYL_filter_data.xlsx", index=False) - elif file_type == "json": - results_list = results_df.to_dict(orient="records") - with open(file_xl_path + "_KYL_filter_data.json", "w") as json_file: - json.dump(results_list, json_file, indent=4) - elif file_type == "geojson": - layer_name = "deltaG_well_depth_" + district + "_" + block - mws_annual_geojson = get_url("mws_layers", layer_name) - response = requests.get(mws_annual_geojson) - response.raise_for_status() - - # Check if response has content - if response.content: - geojson_data = response.json() - deltaG_geojson = file_xl_path + "_deltaG_annual.geojson" - - with open(deltaG_geojson, "w") as f: - json.dump(geojson_data, f) - create_geojson_for_all_mws( - deltaG_geojson, - results_df, - file_xl_path + "_KYL_filter_data.geojson", - ) - file_path = get_mws_KYL_filter_data(state, district, block, file_type) - - except Exception as e: - return Response( - { - "status": "error", - "message": f"Error during file generation: {str(e)}", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - if file_path: - content_type_map = { - "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "json": "application/json", - "geojson": "application/geo+json", - } - content_type = content_type_map.get(file_type, "application/octet-stream") - - with open(file_path, "rb") as file: - response = HttpResponse( - file.read(), - content_type=content_type, - ) - response["Content-Disposition"] = ( - f"attachment; filename={district}_{block}_KYL_filter_data.{file_type}" - ) - return response - - else: - return Response( - {"status": "error", "message": "Failed to generate or download file"}, - status=status.HTTP_404_NOT_FOUND, - ) - - -def get_mws_KYL_filter_data(state, district, block, file_type): - state_folder = state.replace(" ", "_").upper() - district_folder = district.replace(" ", "_").upper() - file_xl_path = os.path.join( - EXCEL_PATH, - "data/stats_excel_files", - state_folder, - district_folder, - f"{district}_{block}", - ) - - file_path = None - if file_type == "xlsx": - file_path = os.path.join(file_xl_path + "_KYL_filter_data.xlsx") - elif file_type == "json": - file_path = os.path.join(file_xl_path + "_KYL_filter_data.json") - elif file_type == "geojson": - file_path = os.path.join(file_xl_path + "_KYL_filter_data.geojson") - - if os.path.exists(file_path): - return file_path - else: - return None +import os +import requests, json +import pandas as pd +import pymannkendall as mk +import numpy as np +import ast +from nrm_app.settings import EXCEL_PATH +from .utils import get_url +from rest_framework.response import Response +from rest_framework import status +from django.http import HttpResponse +from .models import LayerInfo + + +def create_geojson_for_all_mws(existing_geojson_path, df, new_geojson_path): + with open(existing_geojson_path) as f: + existing_data = json.load(f) + + features = [] + + for _, row in df.iterrows(): + uid = row["mws_id"] + geometry = None + + for feature in existing_data["features"]: + if feature["properties"].get("uid") == uid: + geometry = feature["geometry"] + break + + if geometry is None: + print( + f"No geometry found for uid: {uid}. Using default geometry (e.g., None)." + ) + geometry = {"type": "Point", "coordinates": [0, 0]} + + properties = row.to_dict() + + new_feature = { + "type": "Feature", + "geometry": geometry, + "properties": properties, + } + features.append(new_feature) + + new_feature_collection = {"type": "FeatureCollection", "features": features} + + with open(new_geojson_path, "w") as f: + json.dump(new_feature_collection, f) + + +def generate_mws_data_for_kyl_filters( + state, district, block, file_type, regenerate=None +): + state_folder = state.replace(" ", "_").upper() + district_folder = district.replace(" ", "_").upper() + file_xl_path = os.path.join( + EXCEL_PATH, + "data/stats_excel_files", + state_folder, + district_folder, + f"{district}_{block}", + ) + if regenerate: + file_path = None + else: + file_path = get_mws_KYL_filter_data(state, district, block, file_type) + if not file_path: + try: + sheets = { + "hydrological_annual": -1, + "terrain": -1, + "croppingIntensity_annual": -1, + "surfaceWaterBodies_annual": -1, + "croppingDrought_kharif": -1, + "nrega_annual": -1, + "mws_intersect_villages": -1, + "change_detection_degradation": -1, + "change_detection_afforestation": -1, + "change_detection_deforestation": -1, + "change_detection_urbanization": -1, + "change_detection_cropintensity": -1, + "terrain_lulc_slope": -1, + "terrain_lulc_plain": -1, + "restoration_vector": -1, + "aquifer_vector": -1, + "soge_vector": -1, + "lcw_conflict": -1, + "factory_csr": -1, + "mining": -1, + "green_credit": -1, + "mws_intersect_swb": -1, + "dem": -1, + "canal": -1, + "river": -1, + "lulc_vector": -1, + "drainage_density": -1, + } + + try: + with pd.ExcelFile(file_xl_path + ".xlsx") as xl: + available_sheets = xl.sheet_names # Get list of available sheets + + # Try to parse each sheet if it exists + for sheet_name in sheets.keys(): + if sheet_name in available_sheets: + try: + sheets[sheet_name] = xl.parse(sheet_name) + except Exception as e: + print(f"Error parsing sheet {sheet_name}: {e}") + sheets[sheet_name] = -1 + else: + print(f"Sheet {sheet_name} not found in Excel file") + sheets[sheet_name] = -1 + + except Exception as e: + print(f"Error reading Excel file: {e}") + # Return all sheets as -1 if the file can't be read + return {k: -1 for k in sheets.keys()} + + results = [] + df_hydrological_annual = sheets["hydrological_annual"] + + for specific_mws_id in df_hydrological_annual["UID"].unique(): + hydro_annual_mws_data = df_hydrological_annual[ + df_hydrological_annual["UID"] == specific_mws_id + ] + precipitation_columns = hydro_annual_mws_data.filter( + like="Precipitation" + ) # Avg_precipitation + total_percipitation_column = precipitation_columns.shape[1] + sum_precipitation = precipitation_columns.sum(axis=1).sum() + avg_percipitation = round( + sum_precipitation / total_percipitation_column, 4 + ) + + try: + terrain_vector_mws_data = sheets["terrain"][ + sheets["terrain"]["UID"] == specific_mws_id + ] + terrainCluster_ID = terrain_vector_mws_data.get( + "terrain_cluster_id", None + ).iloc[ + 0 + ] # terrain + except: + terrainCluster_ID = -9999 + + try: + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) + + crp_Intensity_columns = df_crp_intensity_mws_data.filter( + like="cropping_intensity_unit_less" + ) # cropping_intensity_avg + total_crp_Intensity_column = crp_Intensity_columns.shape[1] + sum_crp_Intensity = crp_Intensity_columns.sum(axis=1).sum() + cropping_intensity_avg = round( + ( + sum_crp_Intensity / total_crp_Intensity_column + if total_crp_Intensity_column > 0 + else 0 + ), + 4, + ) + + ######### Cropping Intensity Trend ################# + crp_intensity_T = df_crp_intensity_mws_data.filter( + like="cropping_intensity_unit_less" + ).dropna() # Drop rows with NaN for trend calculation + crp_intensity_T = crp_intensity_T.squeeze().tolist()[:-3] + result = mk.original_test(crp_intensity_T) + + def sens_slope(data): + slopes = [] + for i in range(len(data) - 1): + for j in range(i + 1, len(data)): + s = (data[j] - data[i]) / (j - i) + slopes.append(s) + return np.median(slopes) + + cropping_intensity_trend_value = sens_slope(crp_intensity_T) + cropping_intensity_trend = None + if result.trend == "no trend": + cropping_intensity_trend = "0" + elif result.trend == "increasing": + cropping_intensity_trend = "1" + else: + cropping_intensity_trend = "-1" + + # Total cropped area, replace NaN with 0 for these columns as well + total_cropped_area = df_crp_intensity_mws_data.iloc[0][ + "sum_area_in_ha" + ] + + # Handle single-cropped area calculation + single_crop_columns = df_crp_intensity_mws_data.filter( + like="single_cropped_area" + ) # avg_single_cropped + total_single_crop_column = single_crop_columns.shape[1] + sum_single_crop = single_crop_columns.sum(axis=1).sum() + percent_single_crop = ( + sum_single_crop * 100 / total_cropped_area + if total_cropped_area > 0 + else 0 + ) + avg_single_cropped = round( + ( + percent_single_crop / total_single_crop_column + if total_single_crop_column > 0 + else 0 + ), + 4, + ) + + # Handle doubly-cropped area calculation + double_crop_columns = df_crp_intensity_mws_data.filter( + like="doubly_cropped_area" + ) # avg_double_cropped + total_double_crop_column = double_crop_columns.shape[1] + sum_double_crop = double_crop_columns.sum(axis=1).sum() + percent_double_crop = ( + sum_double_crop * 100 / total_cropped_area + if total_cropped_area > 0 + else 0 + ) + avg_double_cropped = round( + ( + percent_double_crop / total_double_crop_column + if total_double_crop_column > 0 + else 0 + ), + 4, + ) + + # Handle triply-cropped area calculation + triply_crop_columns = df_crp_intensity_mws_data.filter( + like="triply_cropped_area" + ) # avg_triply_cropped + total_triply_crop_column = triply_crop_columns.shape[1] + sum_triply_crop = triply_crop_columns.sum(axis=1).sum() + percent_triply_crop = ( + sum_triply_crop * 100 / total_cropped_area + if total_cropped_area > 0 + else 0 + ) + avg_triply_cropped = round( + ( + percent_triply_crop / total_triply_crop_column + if total_triply_crop_column > 0 + else 0 + ), + 4, + ) + + except Exception as e: + # Handle exception and ensure all variables are set + cropping_intensity_avg = -9999 + cropping_intensity_trend = -9999 + avg_single_cropped = -9999 + avg_double_cropped = -9999 + avg_triply_cropped = -9999 + print(f"Error occurred: {e}") + + + ##################### SWB ##################### + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + + df_crp_intensity_mws_data = df_crp_intensity_mws_data.fillna(0) + df_swb_annual_mws_data = df_swb_annual_mws_data.fillna(0) + swb_area_kharif_columns = df_swb_annual_mws_data.filter( + like="kharif_area" + ) + single_kharif_crop_columns = df_crp_intensity_mws_data.filter( + like="single_kharif_cropped_area" + ) + double_crop_columns = df_crp_intensity_mws_data.filter( + like="doubly_cropped_area" + ) + triply_crop_columns = df_crp_intensity_mws_data.filter( + like="triply_cropped_area" + ) + + combined_columns_kharif = single_kharif_crop_columns.add( + double_crop_columns, fill_value=0 + ) + combined_columns_kharif = combined_columns_kharif.add( + triply_crop_columns, fill_value=0 + ) + total_cropped_area_kharif = combined_columns_kharif.sum( + axis=1 + ).sum() + total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] + sum_swb_area_kharif = swb_area_kharif_columns.sum(axis=1).sum() + + avg_wsr_ratio_kharif = ( + sum_swb_area_kharif / total_cropped_area_kharif + if total_cropped_area_kharif > 0 + else 0 + ) + avg_wsr_ratio_kharif = round( + avg_wsr_ratio_kharif * 100 / total_swb_area_kharif_column, 4 + ) + swb_area_rabi_columns = df_swb_annual_mws_data.filter( + like="rabi_area" + ) + single_non_kharif_crop_columns = df_crp_intensity_mws_data.filter( + like="single_non_kharif_cropped_area" + ) + + # Combine the cropping areas and calculate total cropped area for Rabi + combined_columns_rabi = single_non_kharif_crop_columns.add( + double_crop_columns, fill_value=0 + ) + combined_columns_rabi = combined_columns_rabi.add( + triply_crop_columns, fill_value=0 + ) + total_cropped_area_rabi = combined_columns_rabi.sum(axis=1).sum() + + total_swb_rabi_column = swb_area_rabi_columns.shape[1] + sum_swb_area_rabi = swb_area_rabi_columns.sum(axis=1).sum() + + # Average WSR ratio for Rabi + avg_wsr_ratio_rabi = ( + sum_swb_area_rabi / total_cropped_area_rabi + if total_cropped_area_rabi > 0 + else 0 + ) + avg_wsr_ratio_rabi = round( + avg_wsr_ratio_rabi * 100 / total_swb_rabi_column, 4 + ) + swb_area_zaid_columns = df_swb_annual_mws_data.filter( + like="zaid_area" + ) + total_cropped_area_zaid = triply_crop_columns.sum(axis=1).sum() + + total_swb_zaid_column = swb_area_zaid_columns.shape[1] + sum_swb_area_zaid = swb_area_zaid_columns.sum(axis=1).sum() + avg_wsr_ratio_zaid = ( + sum_swb_area_zaid / total_cropped_area_zaid + if total_cropped_area_zaid > 0 + else 0 + ) + avg_wsr_ratio_zaid = round( + avg_wsr_ratio_zaid * 100 / total_swb_zaid_column, 4 + ) + + except Exception as e: + avg_wsr_ratio_kharif = -9999 + avg_wsr_ratio_rabi = -9999 + avg_wsr_ratio_zaid = -9999 + print(f"Error occurred: {e}") + + ############ Swb_average + avg_kharif_surface_water_mws = -9999 + avg_rabi_surface_water_mws = -9999 + avg_zaid_surface_water_mws = -9999 + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + if not df_swb_annual_mws_data.empty: + total_swb_area = df_swb_annual_mws_data.iloc[0][ + "total_swb_area_in_ha" + ] + + if total_swb_area != 0: # Check if total_swb_area is not zero + swb_area_kharif_columns = df_swb_annual_mws_data.filter( + like="kharif_area" + ) + total_swb_area_kharif_column = swb_area_kharif_columns.shape[1] + sum_swb_area_kharif = ( + swb_area_kharif_columns.sum(axis=1).sum() / total_swb_area + ) + avg_kharif_surface_water_mws = round( + ( + sum_swb_area_kharif * 100 / total_swb_area_kharif_column + if total_swb_area_kharif_column > 0 + else 0 + ), + 4, + ) + + swb_rabi_area_columns = df_swb_annual_mws_data.filter( + like="rabi_area" + ) + total_swb_rabi_area_column = swb_rabi_area_columns.shape[1] + sum_swb_rabi_area = ( + swb_rabi_area_columns.sum(axis=1).sum() / total_swb_area + ) + avg_rabi_surface_water_mws = round( + ( + sum_swb_rabi_area * 100 / total_swb_rabi_area_column + if total_swb_rabi_area_column > 0 + else 0 + ), + 4, + ) + + swb_zaid_area_columns = df_swb_annual_mws_data.filter( + like="zaid_area" + ) + total_swb_zaid_area_column = swb_zaid_area_columns.shape[1] + sum_swb_zaid_area = ( + swb_zaid_area_columns.sum(axis=1).sum() / total_swb_area + ) + avg_zaid_surface_water_mws = round( + ( + sum_swb_zaid_area * 100 / total_swb_zaid_area_column + if total_swb_zaid_area_column > 0 + else 0 + ), + 4, + ) + else: + avg_perc_kharif_surface_water_mws = ( + avg_perc_rabi_surface_water_mws + ) = avg_perc_zaid_surface_water_mws = 0 + + except: + avg_perc_kharif_surface_water_mws = ( + avg_perc_rabi_surface_water_mws + ) = avg_perc_zaid_surface_water_mws = -9999 + + ################# SWB Trend ###################### + try: + df_swb_annual_mws_data = sheets["surfaceWaterBodies_annual"][ + sheets["surfaceWaterBodies_annual"]["UID"] == specific_mws_id + ] + swb_T = df_swb_annual_mws_data.filter( + like="total_area_in_ha" + ).dropna() # Drop rows with NaN for trend calculation + swb_T = swb_T.iloc[0].dropna().tolist() + result = mk.original_test(swb_T) + + trend_swb = None + if result.trend == "no trend": + trend_swb = "0" + elif result.trend == "increasing": + trend_swb = "1" + else: + trend_swb = "-1" + except: + trend_swb = -9999 + + ######### G Trend ################# + try: + G_Trend = ( + hydro_annual_mws_data.filter(like="G") + .drop(columns=hydro_annual_mws_data.filter(like="DeltaG").columns) + .dropna() + ) + G_Trend = G_Trend.squeeze().tolist() + result = mk.original_test(G_Trend) + + def sens_slope(data): + slopes = [] + for i in range(len(data) - 1): + for j in range(i + 1, len(data)): + s = (data[j] - data[i]) / (j - i) + slopes.append(s) + return np.median(slopes) + + trend_g_value = sens_slope(G_Trend) + trend_g = None + if result.trend == "no trend": + trend_g = "0" + elif result.trend == "increasing": + trend_g = "1" + else: + trend_g = "-1" + except: + trend_g = -9999 + + ######### drought_category ############## + try: + + layers = LayerInfo.objects.get( + layer_type="vector", workspace="drought" + ) + years = [ + str(year) + for year in range(layers.start_year, layers.end_year + 1) + ] + + df_crpDrought_mws_data = sheets["croppingDrought_kharif"][ + sheets["croppingDrought_kharif"]["UID"] == specific_mws_id + ] + + sum_moderate_severe = { + year: ( + 1 + if ( + df_crpDrought_mws_data.iloc[0][ + f"Moderate_in_weeks_{year}" + ] + + df_crpDrought_mws_data.iloc[0][ + f"Severe_in_weeks_{year}" + ] + ) + >= 5 + else 0 + ) + for year in years + } + sum_of_values = sum(sum_moderate_severe.values()) + drought_category = None + if sum_of_values >= 2: + drought_category = 2 + else: + drought_category = sum_of_values + + ######## avg_dry_spell_in_weeks + dryspell_columns = df_crpDrought_mws_data.filter( + like="drysp_unit_4_weeks" + ) # avg_dry_spell_in_weeks + total_dryspell_column = dryspell_columns.shape[1] + sum_dryspell = dryspell_columns.sum(axis=1).sum() + avg_dry_spell_in_weeks = round( + ( + sum_dryspell / total_dryspell_column + if total_dryspell_column > 0 + else 0 + ), + 4, + ) + except: + drought_category = -9999 + avg_dry_spell_in_weeks = -9999 + + ################# avg_runoff + runoff_columns = hydro_annual_mws_data.filter( + like="RunOff" + ) # avg_runoff + total_runoff_column = runoff_columns.shape[1] + sum_runoff = runoff_columns.sum(axis=1).sum() + avg_runoff = sum_runoff / total_runoff_column + + ############## Nrega Asset ########################## + try: + df_nrega_assets_mws_data = sheets["nrega_annual"][ + sheets["nrega_annual"]["mws_id"] == specific_mws_id + ] + nrega_assets_sum = ( + df_nrega_assets_mws_data.iloc[:, 1:] + .select_dtypes(include="number") + .sum() + .sum() + ) + except: + nrega_assets_sum = -9999 + + ############ MWS Intersect Villages ######################## + try: + df_mws_inters_villages_mws_data = sheets["mws_intersect_villages"][ + sheets["mws_intersect_villages"]["MWS UID"] == specific_mws_id + ] + mws_intersect_villages = df_mws_inters_villages_mws_data.get( + "Village IDs", None + ).iloc[0] + mws_intersect_villages = ast.literal_eval(mws_intersect_villages) + except: + mws_intersect_villages = [] + + ############ Change Detection Degradation ################### + try: + df_change_degr_detection_mws_data = sheets[ + "change_detection_degradation" + ][sheets["change_detection_degradation"]["UID"] == specific_mws_id] + degr_sum = ( + df_change_degr_detection_mws_data[ + [ + "farm_to_barren_area_in_ha", + "farm_to_scrub_land_area_in_ha", + ] + ] + .sum(axis=1) + .iloc[0] + ) + df_change_crp_detection_mws_data = sheets[ + "change_detection_cropintensity" + ][ + sheets["change_detection_cropintensity"]["UID"] + == specific_mws_id + ] + crp_sum = ( + df_change_crp_detection_mws_data[ + [ + "double_to_single_area_in_ha", + "triple_to_double_area_in_ha", + "triple_to_single_area_in_ha", + ] + ] + .sum(axis=1) + .iloc[0] + ) + degradation_land_area = degr_sum + crp_sum + change_in_cropping_intensity_area = ( + df_change_crp_detection_mws_data.get( + "total_change_crop_intensity_area_in_ha", None + ).iloc[0] + ) + + except: + degradation_land_area = -9999 + change_in_cropping_intensity_area = -9999 + + ############ Change Detection Afforestation ################### + try: + df_change_affo_detection_mws_data = sheets[ + "change_detection_afforestation" + ][ + sheets["change_detection_afforestation"]["UID"] + == specific_mws_id + ] + afforestation_column = [ + "barren_to_forest_area_in_ha", + "farm_to_forest_area_in_ha", + ] + afforestation_land_area = df_change_affo_detection_mws_data.get( + "total_afforestation_area_in_ha", None + ).iloc[0] + except: + afforestation_land_area = -9999 + + ############ Change Detection Deforestation ################### + try: + df_change_defo_detection_mws_data = sheets[ + "change_detection_deforestation" + ][ + sheets["change_detection_deforestation"]["UID"] + == specific_mws_id + ] + deforestation_land_area = df_change_defo_detection_mws_data.get( + "total_deforestation_area_in_ha", None + ).iloc[0] + except: + deforestation_land_area = -9999 + + ############ Change Detection Urbanization ################### + try: + df_change_urba_detection_mws_data = sheets[ + "change_detection_urbanization" + ][sheets["change_detection_urbanization"]["UID"] == specific_mws_id] + urbanization_land_area = df_change_urba_detection_mws_data.get( + "total_urbanization_area_in_ha", None + ).iloc[0] + except: + urbanization_land_area = -9999 + + ############# Terrain lulc slope / plain ##################### + try: + df_lulc_slope_mws_data = sheets["terrain_lulc_slope"][ + sheets["terrain_lulc_slope"]["UID"] == specific_mws_id + ] + lulc_slope_category = ( + df_lulc_slope_mws_data.get("cluster_name", pd.NA).iloc[0] + if not df_lulc_slope_mws_data.empty + else None + ) + + except: + lulc_slope_category = -9999 + + try: + df_lulc_plain_mws_data = sheets["terrain_lulc_plain"][ + sheets["terrain_lulc_plain"]["UID"] == specific_mws_id + ] + lulc_plain_category = ( + df_lulc_plain_mws_data.get("cluster_name", pd.NA).iloc[0] + if not df_lulc_plain_mws_data.empty + else None + ) + + except: + lulc_plain_category = -9999 + + ################# Restoration Vector ######################### + try: + df_restoration_vector_mws_data = sheets["restoration_vector"][ + sheets["restoration_vector"]["UID"] == specific_mws_id + ] + wide_scale_restoration = df_restoration_vector_mws_data.get( + "wide_scale_restoration_area_in_ha", None + ).iloc[0] + area_protection = df_restoration_vector_mws_data.get( + "protection_area_in_ha", None + ).iloc[0] + except: + wide_scale_restoration = -9999 + area_protection = -9999 + + ################# Aquifer Vector ######################### + aquifer_class_map = {0: "Hard Rock", 1: "Alluvial"} + + class_to_id = {v: k for k, v in aquifer_class_map.items()} + try: + df_aquifer_vector_mws_data = sheets["aquifer_vector"][ + sheets["aquifer_vector"]["UID"] == specific_mws_id + ] + aquifer_class_name = df_aquifer_vector_mws_data.get( + "aquifer_class", None + ).iloc[0] + if aquifer_class_name == "Alluvium": + aquifer_class_name = "Alluvial" + aquifer_class = int(class_to_id.get(aquifer_class_name, "")) + except Exception: + aquifer_class = -9999 + + ################# SOGE Vector ######################### + Soge_class = { + 0: "Safe", + 1: "Semi-Critical", + 2: "Critical", + 3: "Over Exploited", + 4: "Not Assessed", + } + + class_to_id = {v: k for k, v in Soge_class.items()} + try: + df_soge_vector_mws_data = sheets["soge_vector"][ + sheets["soge_vector"]["UID"] == specific_mws_id + ] + soge_class_name = df_soge_vector_mws_data.get( + "class_name", None + ).iloc[0] + soge_class = int( + class_to_id.get(soge_class_name, "") + ) # Returns None if not found + except Exception: + soge_class = -9999 + + ################## LCW Conflict ###################### + ## if count is 0 then Areas with no conflicts else Areas with conflicts + try: + lcw_conflict_count = sheets["lcw_conflict"][ + sheets["lcw_conflict"]["UID"] == specific_mws_id + ].shape[0] + if lcw_conflict_count == 0: + lcw_conflict = 0 + else: + lcw_conflict = 1 + except Exception as e: + lcw_conflict = -9999 + + ################## mining ###################### + ## if count is 0 then Areas with no mining else Areas with mining + try: + mining_count = sheets["mining"][ + sheets["mining"]["UID"] == specific_mws_id + ].shape[0] + if mining_count == 0: + mining = 0 + else: + mining = 1 + except Exception as e: + mining = -9999 + + ################## green credit ###################### + ## if count is 0 then Areas with no green credit else Areas with green credit + try: + green_credit_count = sheets["green_credit"][ + sheets["green_credit"]["UID"] == specific_mws_id + ].shape[0] + if green_credit_count == 0: + green_credit = 0 + else: + green_credit = 1 + except Exception as e: + green_credit = -9999 + + ################## factory csr ###################### + ## if count is 0 then Areas with no factory else Areas with factory + try: + factory_csr_count = sheets["factory_csr"][ + sheets["factory_csr"]["UID"] == specific_mws_id + ].shape[0] + if factory_csr_count == 0: + factory_csr = 0 + else: + factory_csr = 1 + except Exception as e: + factory_csr = -9999 + + ############ MWS Intersect Swb ######################## + try: + swb_df = sheets["mws_intersect_swb"] + + if swb_df is not -1 and not swb_df.empty: + mws_swb_data = swb_df[swb_df["UID"] == specific_mws_id] + + mws_intersect_swb = mws_swb_data.apply( + lambda row: { + "swbId": str(row["SWB_UID"]), + "swbName": ( + str(row["Waterbodies_name"]) + if pd.notna(row["Waterbodies_name"]) + else "" + ), + "latitude": ( + float(row["Latitude"]) + if pd.notna(row["Latitude"]) + else None + ), + "longitude": ( + float(row["Longitude"]) + if pd.notna(row["Longitude"]) + else None + ), + }, + axis=1, + ).tolist() + else: + mws_intersect_swb = [] + + except Exception as e: + print(f"Error in SWB funda: {e}") + mws_intersect_swb = [] + + ############ DEM (Digital Elevation Model) ######################## + try: + dem_df = sheets["dem"] + if dem_df is not -1 and not dem_df.empty: + mws_dem_data = dem_df[dem_df["UID"] == specific_mws_id] + + # Average of all UID mean elevations + overall_mean_elevation = dem_df["mean_elevation_in_m"].mean() + if not mws_dem_data.empty: + row = mws_dem_data.iloc[0] + relief = round( + row["max_elevation_in_m"] - row["min_elevation_in_m"], 2 + ) + mean_elevation = round(row["mean_elevation_in_m"], 2) + + # Relative mean elevation + if overall_mean_elevation != 0: + relative_mean_elevation = round( + (mean_elevation - overall_mean_elevation), 2 + ) + else: + relative_mean_elevation = 0 + + else: + relief = 0 + mean_elevation = 0 + relative_mean_elevation = 0 + + else: + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 + + except Exception as e: + print(f"Error in getting DEM data: {e}") + relief = -9999 + mean_elevation = -9999 + relative_mean_elevation = -9999 + + ############ Canal ######################## + try: + canal_df = sheets["canal"] + if canal_df is not -1 and not canal_df.empty: + mws_canal_data = canal_df[canal_df["UID"] == specific_mws_id] + if not mws_canal_data.empty: + canal_available = True + else: + canal_available = False + + else: + canal_available = False + + except Exception as e: + print(f"Error in getting canal data: {e}") + canal_available = -9999 + + ############ Canal ######################## + try: + river_df = sheets["river"] + if river_df is not -1 and not river_df.empty: + mws_river_data = river_df[river_df["UID"] == specific_mws_id] + if not mws_river_data.empty: + river_available = True + else: + river_available = False + + else: + river_available = False + + except Exception as e: + print(f"Error in getting canal data: {e}") + river_available = -9999 + + ############ lulc vector ######################## + try: + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 + + lulc_df = sheets["lulc_vector"] + + if lulc_df is not -1 and not lulc_df.empty: + + mws_lulc_data = lulc_df[lulc_df["UID"] == specific_mws_id] + + if not mws_lulc_data.empty: + + row = mws_lulc_data.iloc[0] + + # Total area + area_in_ha = float(row.get("area_in_ha", 0)) + + # Shrub + shrub_cols = [ + col + for col in lulc_df.columns + if col.startswith("shrub_scrub_in_ha_") + ] + + lulc_shrub_area = round( + sum(row[col] for col in shrub_cols) / len(shrub_cols), 2 + ) + + # Forest + forest_cols = [ + col + for col in lulc_df.columns + if col.startswith("tree_forest_in_ha_") + ] + + lulc_forest_area = round( + sum(row[col] for col in forest_cols) / len(forest_cols), + 2, + ) + + if area_in_ha > 0: + lulc_shrub_percent = round( + (lulc_shrub_area / area_in_ha) * 100, 2 + ) + + lulc_forest_percent = round( + (lulc_forest_area / area_in_ha) * 100, 2 + ) + + df_crp_intensity_mws_data = sheets["croppingIntensity_annual"][ + sheets["croppingIntensity_annual"]["UID"] == specific_mws_id + ] + + crp_row = df_crp_intensity_mws_data.iloc[0] + area_in_ha = float(crp_row.get("area_in_ha", 0)) + cropped_area_in_ha = float(crp_row.get("sum_area_in_ha", 0)) + + lulc_crop_percent = round( + (cropped_area_in_ha / area_in_ha) * 100, 2 + ) + + else: + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 + except Exception as e: + print(f"Error in LULC vector: {e}") + + lulc_shrub_percent = -9999 + lulc_forest_percent = -9999 + lulc_crop_percent = -9999 + + ############ Canal ######################## + try: + drainage_density_df = sheets["drainage_density"] + if drainage_density_df is not -1 and not drainage_density_df.empty: + mws_drainage_density_data = drainage_density_df[ + drainage_density_df["UID"] == specific_mws_id + ] + if not mws_drainage_density_data.empty: + row = mws_drainage_density_data.iloc[0] + drainage_density = round(row["drainage_density_std_in_km_per_km2"], 2) + else: + drainage_density = 0 + + else: + drainage_density = -9999 + + + except Exception as e: + print(f"Error in getting drainage_density data: {e}") + drainage_density = -9999 + + results.append( + { + "mws_id": specific_mws_id, + "terrainCluster_ID": terrainCluster_ID, + "avg_precipitation": avg_percipitation, + "cropping_intensity_trend": cropping_intensity_trend, + "cropping_intensity_avg": cropping_intensity_avg, + "avg_single_cropped": avg_single_cropped, + "avg_double_cropped": avg_double_cropped, + "avg_triple_cropped": avg_triply_cropped, + "avg_wsr_ratio_kharif": avg_wsr_ratio_kharif, + "avg_wsr_ratio_rabi": avg_wsr_ratio_rabi, + "avg_wsr_ratio_zaid": avg_wsr_ratio_zaid, + "avg_kharif_surface_water_mws": avg_kharif_surface_water_mws, + "avg_rabi_surface_water_mws": avg_rabi_surface_water_mws, + "avg_zaid_surface_water_mws": avg_zaid_surface_water_mws, + "trend_swb": trend_swb, + "trend_g": trend_g, + "drought_category": drought_category, + "avg_number_dry_spell": avg_dry_spell_in_weeks, + "avg_runoff": round(avg_runoff, 4), + "total_nrega_assets": nrega_assets_sum, + "mws_intersect_villages": mws_intersect_villages, + "degradation_land_area": round(degradation_land_area, 4), + "increase_in_tree_cover": round(afforestation_land_area, 4), + "decrease_in_tree_cover": round(deforestation_land_area, 4), + "degradation_cropping_intensity": round( + change_in_cropping_intensity_area, 4 + ), + "urbanization_area": round(urbanization_land_area, 4), + "lulc_slope_category": lulc_slope_category, + "lulc_plain_category": lulc_plain_category, + "area_wide_scale_restoration": round(wide_scale_restoration, 4), + "area_protection": round(area_protection, 4), + "aquifer_class": aquifer_class, + "soge_class": soge_class, + "lcw_conflict": lcw_conflict, + "mining": mining, + "green_credit": green_credit, + "factory_csr": factory_csr, + "mws_intersect_swb": mws_intersect_swb, + "relief": relief, + "mean_elevation": mean_elevation, + "relative_mean_elevation": relative_mean_elevation, + "canal_available": canal_available, + "river_available": river_available, + "lulc_shrub_percent": lulc_shrub_percent, + "lulc_forest_percent": lulc_forest_percent, + "lulc_crop_percent": lulc_crop_percent, + "drainage_density": drainage_density, + } + ) + + results_df = pd.DataFrame(results) + if file_type == "xlsx": + results_df.to_excel(file_xl_path + "_KYL_filter_data.xlsx", index=False) + elif file_type == "json": + results_list = results_df.to_dict(orient="records") + with open(file_xl_path + "_KYL_filter_data.json", "w") as json_file: + json.dump(results_list, json_file, indent=4) + elif file_type == "geojson": + layer_name = "deltaG_well_depth_" + district + "_" + block + mws_annual_geojson = get_url("mws_layers", layer_name) + response = requests.get(mws_annual_geojson) + response.raise_for_status() + + # Check if response has content + if response.content: + geojson_data = response.json() + deltaG_geojson = file_xl_path + "_deltaG_annual.geojson" + + with open(deltaG_geojson, "w") as f: + json.dump(geojson_data, f) + create_geojson_for_all_mws( + deltaG_geojson, + results_df, + file_xl_path + "_KYL_filter_data.geojson", + ) + file_path = get_mws_KYL_filter_data(state, district, block, file_type) + + except Exception as e: + return Response( + { + "status": "error", + "message": f"Error during file generation: {str(e)}", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + if file_path: + content_type_map = { + "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "json": "application/json", + "geojson": "application/geo+json", + } + content_type = content_type_map.get(file_type, "application/octet-stream") + + with open(file_path, "rb") as file: + response = HttpResponse( + file.read(), + content_type=content_type, + ) + response["Content-Disposition"] = ( + f"attachment; filename={district}_{block}_KYL_filter_data.{file_type}" + ) + return response + + else: + return Response( + {"status": "error", "message": "Failed to generate or download file"}, + status=status.HTTP_404_NOT_FOUND, + ) + + +def get_mws_KYL_filter_data(state, district, block, file_type): + state_folder = state.replace(" ", "_").upper() + district_folder = district.replace(" ", "_").upper() + file_xl_path = os.path.join( + EXCEL_PATH, + "data/stats_excel_files", + state_folder, + district_folder, + f"{district}_{block}", + ) + + file_path = None + if file_type == "xlsx": + file_path = os.path.join(file_xl_path + "_KYL_filter_data.xlsx") + elif file_type == "json": + file_path = os.path.join(file_xl_path + "_KYL_filter_data.json") + elif file_type == "geojson": + file_path = os.path.join(file_xl_path + "_KYL_filter_data.geojson") + + if os.path.exists(file_path): + return file_path + else: + return None diff --git a/stats_generator/utils.py b/stats_generator/utils.py index bcc2c694..fd563c3c 100644 --- a/stats_generator/utils.py +++ b/stats_generator/utils.py @@ -1,2269 +1,2269 @@ -import os -import requests, json -from django.http import HttpResponse, Http404 -from rest_framework.response import Response -import pandas as pd -import geopandas as gpd -from collections import defaultdict -from datetime import datetime -from nrm_app.settings import GEOSERVER_URL, EXCEL_PATH -import numpy as np -from shapely.geometry import Point, shape -from .models import LayerInfo -from django.http import HttpResponse -from rest_framework import status -from pathlib import Path - - -def fetch_layers_for_excel_generation(): - """ - Fetch all vector layers where `excel_to_be_generated` is True. - """ - layers = LayerInfo.objects.filter( - layer_type="vector", excel_to_be_generated=True - ).values("layer_name", "workspace", "start_year", "end_year") - return list(layers) - - -def get_url(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 get_vector_layer_geoserver(state, district, block, specific_sheets=None): - print(f"Generate Stats excel for {state}_{district}_{block}") - base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") - district_path = os.path.join( - base_path, state.replace(" ", "_").upper(), district.replace(" ", "_").upper() - ) - os.makedirs(district_path, exist_ok=True) - xlsx_file = os.path.join(district_path, f"{district}_{block}.xlsx") - - workspaces_to_process = specific_sheets - - # Handle existing sheets when adding specific workspaces - results = [] - file_exists = os.path.exists(xlsx_file) - mode = "a" if file_exists else "w" - - # Use append mode with if_sheet_exists='replace' - with pd.ExcelWriter( - xlsx_file, - engine="openpyxl", - mode=mode, - if_sheet_exists="replace" if mode == "a" else None, - ) as writer: - for layer in fetch_layers_for_excel_generation(): - workspace = layer["workspace"] - - if workspaces_to_process and workspace not in workspaces_to_process: - continue - - start_year = layer.get("start_year") - end_year = layer.get("end_year") - - if "{district}" in layer["layer_name"] and "{block}" in layer["layer_name"]: - layer_name = layer["layer_name"].format(district=district, block=block) - else: - layer_name = layer["layer_name"] - - print(f"Processing layer: {layer_name}") - print(f"Workspace for the layer is: {workspace}") - - geojson_data = None - try: - url = get_url(workspace, layer_name) - response = requests.get(url) - response.raise_for_status() - geojson_data = response.json() - except requests.exceptions.RequestException as e: - print(f"Failed to fetch data for {layer_name}: {e}") - results.append( - {"layer": layer_name, "status": "failed", "workspace": workspace} - ) - continue - - # Process the data based on workspace - if workspace == "terrain": - create_excel_for_terrain(geojson_data, xlsx_file, writer) - elif ( - workspace == "terrain_lulc" - and layer_name == f"{district}_{block}_lulc_slope" - ): - create_excel_for_terrain_lulc_slope(geojson_data, xlsx_file, writer) - elif ( - workspace == "terrain_lulc" - and layer_name == f"{district}_{block}_lulc_plain" - ): - create_excel_for_terrain_lulc_plain(geojson_data, xlsx_file, writer) - elif workspace == "swb": - create_excel_for_swb( - geojson_data, xlsx_file, writer, start_year, end_year - ) - create_excel_for_mws_intersect_swb( - geojson_data, writer, district, block - ) - elif workspace == "nrega_assets": - mws_lay_name = f"deltaG_well_depth_{district}_{block}" - mws_file_url = get_url("mws_layers", mws_lay_name) - - try: - response = requests.get(mws_file_url) - response.raise_for_status() - mws_geojson_datas = response.json() - except requests.exceptions.RequestException as e: - print(f"Failed to fetch MWS data: {e}") - continue - - create_excel_for_nrega_assets( - geojson_data, - mws_geojson_datas, - xlsx_file, - writer, - start_year, - end_year, - ) - try: - fetch_village_asset_count( - state, district, block, writer, xlsx_file, start_year, end_year - ) - except Exception as e: - print("Exception as e", str(e)) - - elif workspace == "crop_intensity": - create_excel_crop_inten( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "drought": - create_excel_crop_drou( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif ( - workspace == "mws_layers" - and layer_name == f"deltaG_well_depth_{district}_{block}" - ): - parsed_data_annual_mws = parse_geojson_annual_mws(geojson_data) - create_excel_annual_mws(parsed_data_annual_mws, xlsx_file, writer) - try: - create_excel_mws_inters_villages( - geojson_data, xlsx_file, writer, district, block - ) - except Exception as e: - print("Exception", str(e)) - elif ( - workspace == "mws_layers" - and layer_name == f"deltaG_fortnight_{district}_{block}" - ): - processed_data = [ - process_feature(feature) for feature in geojson_data["features"] - ] - create_excel_seas_mws( - processed_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "panchayat_boundaries": - create_excel_for_village_boun(geojson_data, writer) - elif workspace == "drought_causality": - create_excel_for_drought_causality( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "ccd": - create_excel_for_ccd( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "canopy_height": - create_excel_for_ch( - geojson_data, xlsx_file, writer, start_year, end_year - ) - elif workspace == "tree_overall_ch": - create_excel_for_overall_tree_change(geojson_data, xlsx_file, writer) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Afforestation" - ): - create_excel_chan_detection_afforestation( - geojson_data, xlsx_file, writer - ) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_CropIntensity" - ): - create_excel_chan_detection_cropintensity( - geojson_data, xlsx_file, writer - ) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Deforestation" - ): - create_excel_chan_detection_deforestation( - geojson_data, xlsx_file, writer - ) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Degradation" - ): - create_excel_chan_detection_degradation(geojson_data, xlsx_file, writer) - elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Urbanization" - ): - create_excel_chan_detection_urbanization( - geojson_data, xlsx_file, writer - ) - elif workspace == "restoration": - create_excel_for_restoration(geojson_data, xlsx_file, writer) - elif workspace == "aquifer": - create_excel_for_aquifer(geojson_data, writer) - elif workspace == "soge": - create_excel_for_soge(geojson_data, xlsx_file, writer) - elif workspace == "lcw": - create_excel_for_lcw(geojson_data, writer) - elif workspace == "agroecological": - create_excel_for_agroecological(geojson_data, writer) - elif workspace == "factory_csr": - create_excel_for_factory_csr(geojson_data, writer) - elif workspace == "green_credit": - create_excel_for_green_credit(geojson_data, writer) - elif workspace == "mining": - create_excel_for_mining(geojson_data, writer) - elif workspace == "stream_order": - create_excel_for_stream_order(geojson_data, writer) - elif workspace == "mws_connectivity": - create_excel_for_mws_connectivity(geojson_data, writer) - elif workspace == "mws": - create_excel_for_mws(geojson_data, writer) - elif workspace == "facilities_proximity": - create_excel_for_facilities(geojson_data, writer) - elif workspace == "dem": - create_excel_for_dem(geojson_data, writer) - elif workspace == "canal": - create_excel_for_canal(geojson_data, writer) - elif workspace == "river": - create_excel_for_river(geojson_data, writer) - elif workspace == "lulc_vector": - create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) - elif workspace == "drainage_density": - create_excel_for_drainage_density(geojson_data, writer) - elif workspace == "antyodaya_2020": - create_excel_for_antyodaya_20(geojson_data, writer) - elif workspace == "livestocks": - create_excel_for_livestock(geojson_data, writer) - - results.append( - {"layer": layer_name, "status": "success", "workspace": workspace} - ) - - return results - - -def create_excel_for_livestock(data, writer): - try: - features = data.get("features", []) - df_data = [feature.get("properties", {}) for feature in features] - df = pd.DataFrame(df_data) - - # Columns to exclude - exclude_cols = ["state_name","district_name","TEHSIL"] - df = df.drop(columns=exclude_cols, errors="ignore") - - # Keep important columns first if they exist - first_cols = [c for c in ["pc11_village_id", "NAME"] if c in df.columns] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - - df.to_excel(writer, sheet_name="livestock", index=False) - print("Excel file created for livestock") - except Exception as e: - print(f"Error in getting livestock data: {e}") - - -def create_excel_for_antyodaya_20(data, writer): - try: - features = data.get("features", []) - df_data = [feature.get("properties", {}) for feature in features] - df = pd.DataFrame(df_data) - - # Keep important columns first if they exist - first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - - # Round numeric columns - numeric_cols = df.select_dtypes(include=["number"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="antyodaya", index=False) - print("Excel file created for antyodaya") - except Exception as e: - print(f"Error in getting antyodaya data: {e}") - - -def create_excel_for_drainage_density(data, writer): - import ast - print("Inside create_excel_for Drainage Density") - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), - "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), - "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="drainage_density", index=False) - print("Excel file created for Drainage Density") - - -def create_excel_for_lulc_vector(data, writer, start_year, end_year): - df_data = [] - features = data["features"] - years = list(range(start_year, end_year + 1)) - - classes = { - "barrenland": ("barrenland", "barrenla"), - "built_up_area": ("built-up_a", "built-up"), - "cropland": ("cropland_a", "cropland"), - "double_crop": ("doubly_cro", "doubly_c"), - "triple_crop": ("triply_cro", "triply_c"), - "tree_forest": ("tree_fores", "tree_for"), - "shrub_scrub": ("shrub_scru", "shrub_sc"), - "single_kharif": ("single_kha", "single_k"), - "single_non_kharif": ("single_non", "single_n"), - "k_water": ("k_water_ar", "k_water_"), - "kr_water": ("kr_water_a", "kr_water"), - "krz_water": ("krz_water_", "krz_wate"), - } - - def get_key(base_key, trunc_prefix, idx): - """Derive the property key for a given year index.""" - if idx == 0: - return base_key - return f"{trunc_prefix}_{idx}" - - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "sum_in_ha": (properties.get("sum") or 0) / 10000, - } - - for idx, year in enumerate(years): - for class_name, (base_key, trunc_prefix) in classes.items(): - key = get_key(base_key, trunc_prefix, idx) - row[f"{class_name}_in_ha_{year}"] = properties.get(key, 0) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="lulc_vector", index=False) - print("Excel file created for lulc vector") - - -def create_excel_for_canal(data, writer): - print("Inside create_excel_for Canal") - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "project_name": properties.get("prjname", ""), - "canal_code": properties.get("cancode", ""), - "canal_name": properties.get("canname", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="canal", index=False) - print("Excel file created for canal") - except Exception as e: - print("Canal Layer not found :: ", e) - - -def create_excel_for_river(data, writer): - print("Inside create_excel_for River") - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "river_name": properties.get("rivname", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="river", index=False) - print("Excel file created for river") - except Exception as e: - print("River Layer not found :: ", e) - - -def create_excel_for_dem(data, writer): - print("Inside create_excel_for DEM") - - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties.get("uid", ""), - "min_elevation_in_m": properties.get("min_elevation", ""), - "max_elevation_in_m": properties.get("max_elevation", ""), - "mean_elevation_in_m": properties.get("mean_elevation", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="dem", index=False) - print("Excel file created for dem") - - -def create_excel_for_mws_intersect_swb(swb_geojson, writer, district, block): - print("Inside create_excel_for_mws_intersect_swb") - - # --- Fetch MWS layer --- - mws_layer_name = f"mws_{district}_{block}" - mws_data_url = get_url("mws", mws_layer_name) - - mws_response = requests.get(mws_data_url) - if mws_response.status_code != 200: - print(f"Error fetching MWS data: {mws_response.status_code}") - return - - mws_geojson = mws_response.json() - - def calculate_intersection_area(geom1, geom2): - if geom1.intersects(geom2): - return geom1.intersection(geom2).area - return 0 - - rows = [] - - for mws_feature in mws_geojson["features"]: - mws_props = mws_feature["properties"] - mws_uid = mws_props.get("uid") - mws_geom = shape(mws_feature["geometry"]) - - for swb_feature in swb_geojson["features"]: - swb_props = swb_feature["properties"] - swb_geom = shape(swb_feature["geometry"]) - - intersection_area = calculate_intersection_area(mws_geom, swb_geom) - - if intersection_area > 0: - # waterbodies centroid calculation - centroid = swb_geom.centroid - lon, lat = centroid.x, centroid.y - - rows.append( - { - "UID": mws_uid, - "SWB_UID": swb_props.get("UID"), - "Waterbodies_name": swb_props.get("water_body_name"), - "Latitude": lat, - "Longitude": lon, - } - ) - - df = pd.DataFrame(rows) - - if not df.empty: - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="mws_intersect_swb", index=False) - print("Excel sheet 'mws_intersect_swb' created successfully") - - -def create_excel_for_facilities(data, writer): - try: - features = data["features"] - df_data = [feature["properties"] for feature in features] - - df = pd.DataFrame(df_data) - - first_cols = ["censuscode2011", "censusname"] - other_cols = [c for c in df.columns if c not in first_cols] - df = df[first_cols + other_cols] - - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] - df.rename( - columns={ - col: f"{col}_in_km" - for col in df.columns - if col not in exclude_cols - }, - inplace=True - ) - - # Write to Excel - df.to_excel(writer, sheet_name="facilities_proximity", index=False) - - print("Excel file created for facilities_proximity") - except Exception as e: - print("facilities_proximity Layer not found :: ", e) - - - -def create_excel_for_mws(data, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "watershed_code": properties.get("wsconc", ""), - "basin_code": properties.get("bacode", ""), - "sub_basin_code": properties.get("sbcode", ""), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mws", index=False) - print("Excel file created for mws") - - -def create_excel_for_mws_connectivity(data, writer): - """ - direction_map = { - '0': 'No Direction', - '1': 'North-East', - '2': 'East', - '3': 'South-East', - '4': 'South', - '5': 'South-West', - '6': 'West', - '7': 'North-West', - '8': 'North' - } - """ - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "direction": properties["direction"], - "downstream_mws": properties["downstream"], - "upstream_mws": properties["upstream"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mws_connectivity", index=False) - print("Excel file created for mws_connectivity") - - -def create_excel_for_stream_order(data, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "order_1_area_percent": properties["1"], - "order_2_area_percent": properties["2"], - "order_3_area_percent": properties["3"], - "order_4_area_percent": properties["4"], - "order_5_area_percent": properties["5"], - "order_6_area_percent": properties["6"], - "order_7_area_percent": properties["7"], - "order_8_area_percent": properties["8"], - "order_9_area_percent": properties["9"], - "order_10_area_percent": properties["10"], - "order_11_area_percent": properties["11"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="stream_order", index=False) - print("Excel file created for stream order") - - -def create_excel_for_mining(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["company_na"], - "proposal": properties["proposal"], - "sector_moefcc": properties["sector_moe"], - "village": properties["village"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df.replace("", "unknown", inplace=True) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="mining", index=False) - print("Excel file created for mining") - except Exception as e: - print("Mining Layer not found :: ", e) - - -def create_excel_for_green_credit(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "division": properties["division"], - # "parcel_id": properties["parcel_id"], - "land_info": properties["land_info"], - "kml_url": properties["kml_url"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="green_credit", index=False) - print("Excel file created for green_credit") - except Exception as e: - print("green credit Layer not found :: ", e) - - -def create_excel_for_factory_csr(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "Company_Name": properties["COMPANY NA"], - "ADDRESS": properties["ADDRESS"], - "LOCATION T": properties["LOCATION T"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="factory_csr", index=False) - print("Excel file created for factory_csr") - except Exception as e: - print("factory csr Layer not found :: ", e) - - -def create_excel_for_agroecological(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "organization_name": properties["organization_name"], - "organization_type": properties["organization_type"], - "created_at": properties["created_at"], - "contact_person": properties["contact_person"], - "email": properties["email"], - "domains": properties["domains"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="agroecological", index=False) - print("Excel file created for agroecological") - except Exception as e: - print("agroecological Layer not found :: ", e) - - -def create_excel_for_lcw(data, writer): - try: - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "title_of_conflict": properties["Title of Conflict"], - "link_to_conflict": properties["Link to conflict"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="lcw_conflict", index=False) - print("Excel file created for lcw_conflict") - except Exception as e: - print("lcw Layer not found :: ", e) - - -def create_excel_for_soge(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "soge_dev_percent": properties["sgw_dev_pe"], - "class_code": properties["code"], - "class_name": properties["class"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="soge_vector", index=False) - print(f"Excel file created for soge_vector") - - -def create_excel_for_aquifer(data, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - if properties.get("aquifer_class", "") == "Hard-Rock": - aquifer_class = "Hard Rock" - else: - aquifer_class = "Alluvium" - row = { - "UID": properties.get("uid", ""), - "area_in_ha": properties.get("area_in_ha", ""), - "aquifer_class": aquifer_class, - "principle_aq_Alluvium_percent": properties.get( - "principle_aq_Alluvium_percent", "0" - ), - "principle_aq_Banded Gneissic Complex_percent": properties.get( - "principle_aq_Banded Gneissic Complex_percent", "0" - ), - "principle_aq_Basalt_percent": properties.get( - "principle_aq_Basalt_percent", "0" - ), - "principle_aq_Charnockite_percent": properties.get( - "principle_aq_Charnockite_percent", "0" - ), - "principle_aq_Gneiss_percent": properties.get( - "principle_aq_Gneiss_percent", "0" - ), - "principle_aq_Granite_percent": properties.get( - "principle_aq_Granite_percent", "0" - ), - "principle_aq_Intrusive_percent": properties.get( - "principle_aq_Intrusive_percent", "0" - ), - "principle_aq_Khondalite_percent": properties.get( - "principle_aq_Khondalite_percent", "0" - ), - "principle_aq_Laterite_percent": properties.get( - "principle_aq_Laterite_percent", "0" - ), - "principle_aq_Limestone_percent": properties.get( - "principle_aq_Limestone_percent", "0" - ), - "principle_aq_Quartzite_percent": properties.get( - "principle_aq_Quartzite_percent", "0" - ), - "principle_aq_Sandstone_percent": properties.get( - "principle_aq_Sandstone_percent", "0" - ), - "principle_aq_Schist_percent": properties.get( - "principle_aq_Schist_percent", "0" - ), - "principle_aq_Shale_percent": properties.get( - "principle_aq_Shale_percent", "0" - ), - "principle_aq_None_percent": properties.get( - "principle_aq_None_percent", "0" - ), - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - # Round numeric values - numeric_cols = df.select_dtypes(include=["number"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df = df.sort_values(["UID"]) - df.to_excel(writer, sheet_name="aquifer_vector", index=False) - print("Excel file created for aquifer_vector") - - -def create_excel_for_restoration(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "wide_scale_restoration_area_in_ha": properties["Wide-scale"], - "protection_area_in_ha": properties["Protection"], - "mosaic_restoration_area_in_ha": properties["Mosaic Res"], - "excluded_areas_in_ha": properties["Excluded A"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="restoration_vector", index=False) - print(f"Excel file created for restoration_vector") - - -def create_excel_for_overall_tree_change(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "afforestation_area_in_ha": properties["Afforestation"], - "deforestation_area_in_ha": properties["Deforestation"], - "degradation_area_in_ha": properties["Degradation"], - "improvement_area_in_ha": properties["Improvement"], - "missing_data_in_ha": properties["Missing Data"], - "no_change_area_in_ha": properties["No_Change"], - "partially_degraded_area_in_ha": properties["Partially_Degraded"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="overall_tree_change", index=False) - print(f"Excel file created for overall_tree_change") - - -def create_excel_for_ccd(data, xlsx_file, writer, start_year, end_year): - df_data = [] - features = data["features"] - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - } - - for year in range(start_year, end_year + 1): - row["high_density_area_in_ha_" + str(year)] = properties.get( - "High_Density_" + str(year), None - ) - row["low_density_area_in_ha_" + str(year)] = properties.get( - "Low_Density_" + str(year), None - ) - row["missing_data_area_in_ha_" + str(year)] = properties.get( - "Missing_Data_" + str(year), None - ) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="Canopy_Cover_Density", index=False) - print(f"Excel file created for Canopy_Cover_Density") - - -def create_excel_for_ch(data, xlsx_file, writer, start_year, end_year): - df_data = [] - features = data["features"] - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - } - - for year in range(start_year, end_year + 1): - row["short_trees_area_in_ha_" + str(year)] = properties.get( - "Short_Trees_" + str(year), None - ) - row["medium_trees_area_in_ha_" + str(year)] = properties.get( - "Medium_Height_Trees_" + str(year), None - ) - row["tall_trees_area_in_ha_" + str(year)] = properties.get( - "Tall_Trees_" + str(year), None - ) - row["missing_data_area_in_ha_" + str(year)] = properties.get( - "Missing_Data_" + str(year), None - ) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="Canopy_height", index=False) - print(f"Excel file created for Canopy_height") - - -def create_excel_for_drought_causality(data, xlsx_file, writer, start_year, end_year): - df_data = [] - features = data["features"] - for feature in features: - properties = feature["properties"] - - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - } - - for year in range(start_year, end_year + 1): - row["severe_moderate_drought_causality_" + str(year)] = properties.get( - "se_mo_" + str(year), None - ) - row["mild_drought_causality_" + str(year)] = properties.get( - "mild_" + str(year), None - ) - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="drought_causality", index=False) - print(f"Excel file created for drought_causality") - - -def create_excel_chan_detection_afforestation(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "barren_to_forest_area_in_ha": properties.get("ba_fo", None), - "built_up_to_forest_area_in_ha": properties.get("bu_fo", None), - "farm_to_forest_area_in_ha": properties.get("fa_fo", None), - "forest_to_forest_area_in_ha": properties.get("fo_fo", None), - "scrub_land_to_forest_area_in_ha": properties.get("sc_fo", None), - "total_afforestation_area_in_ha": properties.get("total_aff", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_afforestation", index=False) - print(f"Excel file created for change_detection_afforestation") - - -def create_excel_chan_detection_cropintensity(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "single_to_single_area_in_ha": properties.get("si_si", None), - "single_to_double_area_in_ha": properties.get("si_do", None), - "single_to_triple_area_in_ha": properties.get("si_tr", None), - "double_to_single_area_in_ha": properties.get("do_si", None), - "double_to_double_area_in_ha": properties.get("do_do", None), - "double_to_triple_area_in_ha": properties.get("do_tr", None), - "triple_to_single_area_in_ha": properties.get("tr_si", None), - "triple_to_double_area_in_ha": properties.get("tr_do", None), - "triple_to_triple_area_in_ha": properties.get("tr_tr", None), - "total_change_crop_intensity_area_in_ha": properties.get( - "total_chan", properties.get("total_change", None) - ), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_cropintensity", index=False) - print(f"Excel file created for change_detection_cropintensity") - - -def create_excel_chan_detection_deforestation(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "forest_to_barren_area_in_ha": properties.get("fo_ba", None), - "forest_to_built_up_area_in_ha": properties.get("fo_bu", None), - "forest_to_farm_area_in_ha": properties.get("fo_fa", None), - "forest_to_forest_area_in_ha": properties.get("fo_fo", None), - "forest_to_scrub_land_area_in_ha": properties.get("fo_sc", None), - "total_deforestation_area_in_ha": properties.get("total_def", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_deforestation", index=False) - print(f"Excel file created for change_detection_deforestation") - - -def create_excel_chan_detection_degradation(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "farm_to_barren_area_in_ha": properties.get("f_ba", None), - "farm_to_built_up_area_in_ha": properties.get("f_bu", None), - "farm_to_farm_area_in_ha": properties.get("f_f", None), - "farm_to_scrub_land_area_in_ha": properties.get("f_sc", None), - "total_degradation_area_in_ha": properties.get("total_deg", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_degradation", index=False) - print(f"Excel file created for change_detection_degradation") - - -def create_excel_chan_detection_urbanization(data, xlsx_file, writer): - df_data = [] - features = data["features"] - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - df_data.append( - { - "UID": uid, - "area_in_ha": properties.get("area_in_ha", None), - "barren_shrub_to_built_up_area_in_ha": properties.get("b_bu", None), - "built_up_to_built_up_area_in_ha": properties.get("bu_bu", None), - "tree_farm_to_built_up_area_in_ha": properties.get("tr_bu", None), - "water_to_built_up_area_in_ha": properties.get("w_bu", None), - "total_urbanization_area_in_ha": properties.get("total_urb", None), - } - ) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="change_detection_urbanization", index=False) - print(f"Excel file created for change_detection_urbanization") - - -def create_excel_mws_inters_villages(mws_geojson, xlsx_file, writer, district, block): - print("Inside create_excel_mws_inters_villages") - admin_layer_name = district + "_" + block - admin_file_url = get_url("panchayat_boundaries", admin_layer_name) - - response = requests.get(admin_file_url) - if response.status_code != 200: - print(f"Error fetching data: {response.status_code}") - return - - village_geojson = response.json() - - def calculate_intersection_area_ha(village_geom, mws_geom, mws_area_ha): - if village_geom.intersects(mws_geom): - intersection = village_geom.intersection(mws_geom) - if mws_geom.area == 0: - return 0 - # ratio of intersection to full MWS geometry area (both in degrees²) - ratio = intersection.area / mws_geom.area - return ratio * mws_area_ha - return 0 - - mws_villages_dict = {} - - for mws_feature in mws_geojson["features"]: - mws_uid = mws_feature["properties"]["uid"] - mws_area = mws_feature["properties"]["area_in_ha"] - mws_geom = shape(mws_feature["geometry"]) - village_ids = set() - village_details = {} - - for village_feature in village_geojson["features"]: - village_id = village_feature["properties"]["vill_ID"] - if village_id == 0: - continue - - village_geom = shape(village_feature["geometry"]) - area_intersected_ha = calculate_intersection_area_ha( - village_geom, mws_geom, mws_area - ) - if area_intersected_ha > 0: - village_ids.add(village_id) - percentage_of_area = ( - (area_intersected_ha / mws_area) * 100 if mws_area > 0 else 0 - ) - village_details[village_id] = { - "area_intersect": round(area_intersected_ha, 2), - "percentage_of_area": round(percentage_of_area, 2), - } - - if village_ids: - mws_villages_dict[mws_uid] = { - "area_in_ha": mws_area, - "village_ids": list(village_ids), - "village_details": village_details, - } - - data = [ - { - "MWS UID": mws_uid, - "area_in_ha": values["area_in_ha"], - "Village IDs": values["village_ids"], - "Village Details": values["village_details"], - } - for mws_uid, values in mws_villages_dict.items() - ] - - df = pd.DataFrame(data) - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - df.to_excel(writer, sheet_name="mws_intersect_villages", index=False) - print("The data has been saved to mws_intersect_villages") - - -# def create_excel_village_inters_mwss(mws_geojson, xlsx_file, writer, district, block): -# print("Inside create_excel_village_inters_mwss") -# admin_layer_name = district + '_' + block -# admin_file_url = get_url('panchayat_boundaries', admin_layer_name) - -# response = requests.get(admin_file_url) -# if response.status_code != 200: -# print(f"Error fetching data: {response.status_code}") -# return -# village_geojson = response.json() - -# def calculate_intersection_area(village_geom: BaseGeometry, mws_geom: BaseGeometry) : -# try: -# # Check for empty geometries -# if village_geom.is_empty or mws_geom.is_empty: -# return 0.0 - -# # Fix invalid geometries if needed -# if not village_geom.is_valid: -# village_geom = village_geom.buffer(0) -# if not mws_geom.is_valid: -# mws_geom = mws_geom.buffer(0) - -# # Calculate intersection -# if village_geom.intersects(mws_geom): -# intersection = village_geom.intersection(mws_geom) -# return intersection.area if not intersection.is_empty else 0.0 - -# return 0.0 - -# except Exception as e: -# print(f"Error calculating intersection area: {e}") -# return 0.0 - - -# data = [] - -# processed_villages = set() - -# for village_feature in village_geojson['features']: -# village_id = village_feature['properties']['vill_ID'] -# village_name = village_feature['properties']['vill_name'] - -# village_key = (village_id, village_name) - -# if village_key in processed_villages: -# continue -# processed_villages.add(village_key) - -# village_geom = shape(village_feature['geometry']) - -# mws_uids = [] -# intersection_areas = [] - -# for mws_feature in mws_geojson['features']: -# mws_geom = shape(mws_feature['geometry']) -# area_intersected = calculate_intersection_area(village_geom, mws_geom) -# if area_intersected > 0: -# mws_uids.append(mws_feature['properties']['uid']) -# intersection_areas.append(area_intersected) - -# data.append({ -# 'Village ID': village_id, -# 'Village Name': village_name, -# 'MWS UIDs': mws_uids, -# }) - -# df = pd.DataFrame(data) - -# ## for roundoff all numeric value upto 2 decimal -# numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns -# df[numeric_cols] = df[numeric_cols].round(2) - -# df.to_excel(writer, sheet_name='village_intersect_mwss', index=False) -# print("Excel created for village_intersect_mwss") - - -def create_excel_for_terrain(data, output_file, writer): - print("Inside create_excel_for_terrain function") - df_data = [] - - terrain_description = { - 0: "Broad Sloppy and Hilly", - 1: "Mostly Plains", - 2: "Mostly Hills and Valleys", - 3: "Broad Plains and Slopes", - } - - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "terrain_cluster_id": properties["terrainClu"], - "terrain_description": terrain_description.get(properties["terrainClu"]), - "hill_slope_area_percent": properties["hill_slope"], - "plain_area_percent": properties["plain_area"], - "ridge_area_percent": properties["ridge_area"], - "slopy_area_percent": properties["slopy_area"], - "valley_area_percent": properties["valley_are"], - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="terrain", index=False) - print(f"Excel file created for terrain vector") - - -def create_excel_for_terrain_lulc_slope(data, output_file, writer): - df_data = [] - - terrain_description = { - 0: "Broad Sloppy and Hilly", - 1: "Mostly Plains", - 2: "Mostly Hills and Valleys", - 3: "Broad Plains and Slopes", - } - - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "terrain_cluster_id": properties["terrain_cl"], - "terrain_description": terrain_description.get(properties["terrain_cl"]), - "cluster_name": properties["clust_name"], - "barren_area_percent": properties["barren"], - "forests_area_percent": properties["forests"], - "shrub_scrubs_area_percent": properties["shrub_scru"], - "single_kharif_area_percent": properties["sing_khari"], - "single_non_kharif_area_percent": properties["sing_non_k"], - "double_cropping_area_percent": properties["double"], - "triple_cropping_area_percent": properties["triple"], - } - - df_data.append(row) - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="terrain_lulc_slope", index=False) - print("Excel file created for terrain_lulc_slope") - - -def create_excel_for_terrain_lulc_plain(data, output_file, writer): - df_data = [] - - terrain_description = { - 0: "Broad Sloppy and Hilly", - 1: "Mostly Plains", - 2: "Mostly Hills and Valleys", - 3: "Broad Plains and Slopes", - } - - features = data["features"] - - for feature in features: - properties = feature["properties"] - row = { - "UID": properties["uid"], - "area_in_ha": properties["area_in_ha"], - "terrain_cluster_id": properties["terrain_cl"], - "terrain_description": terrain_description.get(properties["terrain_cl"]), - "cluster_name": properties["clust_name"], - "barren_area_percent": properties["barren"], - "forests_area_percent": properties["forest"], - "shrub_scrubs_area_percent": properties["shrubs_scr"], - "single_non_kharif_area_percent": properties["sing_non_k"], - "single_kharif_area_percent": properties["sing_crop"], - "double_cropping_area_percent": properties["double_cro"], - "triple_cropping_area_percent": properties["triple_cro"], - } - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="terrain_lulc_plain", index=False) - print("Excel file created for terrain_lulc_plain") - - -def create_excel_for_swb(data, output_file, writer, start_year, end_year): - df_data = [] - features = data.get("features", []) - - for feature in features: - properties = feature.get("properties", {}) - uid = properties.get("MWS_UID", "Unknown") - - def calculate_area(base_area, percentage): - if base_area == 0 or percentage == 0: - return 0 - return base_area * (percentage / 100) - - parts = uid.split("_") - num_uid_parts_is = [ - f"{parts[i]}_{parts[i+1]}" for i in range(0, len(parts) - 1, 2) - ] - if len(parts) % 2 == 1: # Check for an unpaired last part - num_uid_parts_is.append(parts[-1]) - - # Generate years dynamically based on start_year and end_year - years = range(start_year, end_year) - - for num_uid_part in num_uid_parts_is: - row = {"UID": num_uid_part} - - for year in years: - short_year = f"{str(year)[-2:]}-{str(year+1)[-2:]}" - - # Construct keys dynamically using the shortened year format - total_area_key = f"area_{short_year}" - kharif_key = f"k_{short_year}" - rabi_key = f"kr_{short_year}" - zaid_key = f"krz_{short_year}" - - # Get values from properties - total_area = properties.get(total_area_key, 0) - kharif_percentage = properties.get(kharif_key, 0) - rabi_percentage = properties.get(rabi_key, 0) - zaid_percentage = properties.get(zaid_key, 0) - - # Calculate areas - row[f"total_area_in_ha_{year}-{year+1}"] = total_area / len( - num_uid_parts_is - ) - row[f"kharif_area_in_ha_{year}-{year+1}"] = calculate_area( - total_area, kharif_percentage - ) / len(num_uid_parts_is) - row[f"rabi_area_in_ha_{year}-{year+1}"] = calculate_area( - total_area, rabi_percentage - ) / len(num_uid_parts_is) - row[f"zaid_area_in_ha_{year}-{year+1}"] = calculate_area( - total_area, zaid_percentage - ) / len(num_uid_parts_is) - - # Add total SWB area - row["total_swb_area_in_ha"] = properties.get("area_ored", 0) / len( - num_uid_parts_is - ) - df_data.append(row) - - df = pd.DataFrame(df_data) - agg_dict = {col: "sum" for col in df.columns if col != "UID"} - grouped_df = df.groupby("UID").agg(agg_dict).reset_index() - - df = grouped_df.sort_values(["UID"]) - - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="surfaceWaterBodies_annual", index=False) - print("Excel file created for surfaceWaterBodies_annual") - - -def create_excel_for_nrega_assets( - nrega_data, mws_data, output_file, writer, start_year, end_year -): - workCategoryMapping = { - "SWC - Landscape level impact": "Soil and water conservation", - "Agri Impact - HH, Community": "Land restoration", - "Plantation": "Plantations", - "Irrigation - Site level impact": "Irrigation on farms", - "Irrigation Site level - Non RWH": "Other farm works", - "Household Livelihood": "Off-farm livelihood assets", - "Others - HH, Community": "Community assets", - } - - mws = gpd.GeoDataFrame.from_features(mws_data["features"]) - nrega = gpd.GeoDataFrame.from_features(nrega_data["features"]) - - # Set CRS if available in JSON - if "crs" in mws_data: - mws.set_crs(mws_data["crs"]["properties"]["name"], inplace=True) - if "crs" in nrega_data: - nrega.set_crs(nrega_data["crs"]["properties"]["name"], inplace=True) - - joined = gpd.sjoin(nrega, mws, how="inner", predicate="within") - counts = {} - - df_data = [] - valid_years = range(start_year, end_year) - - date_formats = [ - "%d-%b-%y %H:%M:%S.%f", - "%d-%b-%y %H:%M:%S", - "%d-%m-%y %H:%M:%S.%f", - "%d-%m-%y %H:%M:%S", - "%d-%b-%Y %H:%M:%S.%f", - "%d-%b-%Y %H:%M:%S", - "%d-%m-%Y %H:%M:%S.%f", - "%d-%m-%Y %H:%M:%S", - "%Y-%m-%d %H:%M:%S.%f", - "%Y-%m-%d %H:%M:%S", - "%Y/%m/%d %H:%M:%S.%f", - "%Y/%m/%d %H:%M:%S", - "%Y-%m-%dT%H:%M:%SZ", - ] - - for _, row in joined.iterrows(): - creation_t = row["creation_t"] - work_category = row["WorkCatego"] - mws_id = row["uid"] - - if isinstance(creation_t, pd.Timestamp): - creation_t = creation_t.strftime("%d-%m-%Y %H:%M:%S") - - date_obj = None - for date_format in date_formats: - try: - date_obj = datetime.strptime(creation_t, date_format) - break - except ValueError: - continue - - if date_obj is None: - continue - - year = date_obj.year - if year < 100: - year += 2000 - - if year not in valid_years: - continue - - category = workCategoryMapping.get(work_category, "Others - HH, Community") - - if mws_id not in counts: - counts[mws_id] = { - year: {cat: 0 for cat in workCategoryMapping.values()} - for year in range(start_year, end_year) - } - - if category not in counts[mws_id][year]: - counts[mws_id][year][category] = 0 - counts[mws_id][year][category] += 1 - - for mws_id, year_data in counts.items(): - row = {"mws_id": mws_id} - for year, categories in year_data.items(): - for category in workCategoryMapping.values(): - count = categories.get(category, 0) - row[f"{category}_count_{year}"] = count - df_data.append(row) - - if not df_data: - print("No data was collected for the DataFrame.") - else: - print(f"Collected {len(df_data)} rows of data for the DataFrame.") - - if df_data: - df = pd.DataFrame(df_data) - df.to_excel(writer, sheet_name="nrega_annual", index=False) - print("Excel file created for nrega_annual") - return "successfully created" - else: - print("No data available to write to Excel.") - - -def create_excel_village_nrega_assets( - result_df, output_file, writer, all_villages_df, start_year, end_year -): - workCategoryMapping = { - "SWC - Landscape level impact": "Soil and water conservation", - "Agri Impact - HH, Community": "Land restoration", - "Plantation": "Plantations", - "Irrigation - Site level impact": "Irrigation on farms", - "Irrigation Site level - Non RWH": "Other farm works", - "Household Livelihood": "Off-farm livelihood assets", - "Others - HH, Community": "Community assets", - } - - # start_year, end_year = 2017, 2022 - year_range = range(start_year, end_year + 1) - - # Initialize all-zero DataFrame for all villages - rows = [] - for _, row in all_villages_df.iterrows(): - base_row = {"vill_id": row["vill_ID"], "vill_name": row["vill_name"]} - for year in year_range: - for cat in workCategoryMapping.values(): - base_row[f"{cat}_count_{year}"] = 0 - rows.append(base_row) - - final_df = pd.DataFrame(rows) - - # Fill counts from assets - for _, row in result_df.iterrows(): - creation_t = row["creation_t"] - try: - date_obj = pd.to_datetime(creation_t, errors="coerce") - if pd.isnull(date_obj): - continue - except: - continue - - year = date_obj.year - if year not in year_range: - continue - - category = workCategoryMapping.get(row["WorkCatego"]) - if not category: - continue - - mask = (final_df["vill_id"] == row["vill_ID"]) & ( - final_df["vill_name"] == row["vill_name"] - ) - col_name = f"{category}_count_{year}" - final_df.loc[mask, col_name] += 1 - - # Sort columns for clean layout - id_cols = ["vill_id", "vill_name"] - category_cols = sorted( - [col for col in final_df.columns if col not in id_cols], - key=lambda x: (int(x.split("_")[-1]), x), - ) - final_df = final_df[id_cols + category_cols] - - # Save to Excel - final_df = final_df.drop_duplicates(subset=["vill_id", "vill_name"]) - final_df.to_excel(writer, sheet_name="nrega_assets_village", index=False) - print("Excel file created successfully with all villages.") - - -def fetch_village_asset_count( - state, district, block, writer, output_file, start_year, end_year -): - # 1. Read village data - village_gdf = gpd.read_file(get_url("panchayat_boundaries", f"{district}_{block}"))[ - ["vill_ID", "vill_name", "geometry"] - ].copy() - print("Village data loaded") - - # 2. Get NREGA data with timeout to prevent hanging - try: - nrega_url = get_url("nrega_assets", f"{district}_{block}") - print(f"Fetching: {nrega_url}") - - # Add timeout to prevent hanging - response = requests.get(nrega_url, timeout=120) - nrega_json = response.json() - print("NREGA data fetched successfully") - - except Exception as e: - print(f"Failed to get NREGA data: {e}") - # Return villages with "No Asset" if API fails - result_df = village_gdf[["vill_ID", "vill_name"]].copy() - result_df["Asset ID"] = "No Asset" - result_df["creation_t"] = "No Asset" - result_df["WorkCatego"] = "No Asset" - create_excel_village_nrega_assets( - result_df, - output_file, - writer, - village_gdf[["vill_ID", "vill_name"]], - start_year, - end_year, - ) - return result_df, village_gdf[["vill_ID", "vill_name"]] - - # 3. Process asset features - points_data = [] - features = nrega_json.get("features", []) - print(f"Processing {len(features)} features") - - for feature in features: - try: - point = Point(feature["geometry"]["coordinates"]) - properties = feature["properties"] - points_data.append( - { - "geometry": point, - "Asset ID": properties.get("Asset ID", "MISSING"), - "creation_t": properties.get("creation_t", ""), - "WorkCatego": properties.get("WorkCatego", ""), - } - ) - except: - continue - - # If no valid points, return no assets - if not points_data: - print("No valid asset points found") - result_df = village_gdf[["vill_ID", "vill_name"]].copy() - result_df["Asset ID"] = "No Asset" - result_df["creation_t"] = "No Asset" - result_df["WorkCatego"] = "No Asset" - create_excel_village_nrega_assets( - result_df, - output_file, - writer, - village_gdf[["vill_ID", "vill_name"]], - start_year, - end_year, - ) - return result_df, village_gdf[["vill_ID", "vill_name"]] - - print("Asset points created") - - # 4. Create points GeoDataFrame and match CRS - points_gdf = gpd.GeoDataFrame(points_data, geometry="geometry") - if village_gdf.crs != points_gdf.crs: - points_gdf.set_crs(village_gdf.crs, inplace=True) - - # 5. Find which village each asset belongs to - joined_gdf = gpd.sjoin(points_gdf, village_gdf, how="inner", predicate="within") - - # 6. Get asset + village info - result_df = joined_gdf[ - ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] - ].copy() - - # 7. Add villages that have no assets - villages_with_assets = result_df["vill_ID"].unique() - no_asset_villages = village_gdf[ - ~village_gdf["vill_ID"].isin(villages_with_assets) - ].copy() - no_asset_villages["Asset ID"] = "No Asset" - no_asset_villages["creation_t"] = "No Asset" - no_asset_villages["WorkCatego"] = "No Asset" - - result_df = pd.concat( - [ - result_df, - no_asset_villages[ - ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] - ], - ], - ignore_index=True, - ) - - print("Processing complete") - - # 8. Create Excel file - create_excel_village_nrega_assets( - result_df, - output_file, - writer, - village_gdf[["vill_ID", "vill_name"]], - start_year, - end_year, - ) - - return result_df, village_gdf[["vill_ID", "vill_name"]] - - -def analyze_results(village_asset_count, village_gdf): - villages_with_counts = village_gdf.merge( - village_asset_count, on="vill_ID", how="left" - ) - villages_with_counts["asset_count"] = villages_with_counts["asset_count"].fillna(0) - return villages_with_counts - - -def create_excel_crop_inten(data, output_file, writer, start_year, end_year): - df_data = [] - - features = data["features"] - for feature in features: - properties = feature.get("properties", {}) - uid = properties.get("uid", "Unknown") - row = {"UID": uid, "area_in_ha": properties.get("area_in_ha", 0)} - - # Process each year in range using new key naming convention - for year in range(start_year, end_year + 1): - cropping_key = f"cropping_intensity_{year}" - single_c_key = f"single_cropped_area_{year}" - single_k_key = f"single_kharif_cropped_area_{year}" - single_n_key = f"single_non_kharif_cropped_area_{year}" - doubly_c_key = f"doubly_cropped_area_{year}" - triply_c_key = f"triply_cropped_area_{year}" - - row[f"cropping_intensity_unit_less_{year}-{year + 1}"] = properties.get( - cropping_key, 0 - ) - row[f"single_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - single_c_key, 0 - ) - row[f"single_kharif_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - single_k_key, 0 - ) - row[f"single_non_kharif_cropped_area_in_ha_{year}-{year + 1}"] = ( - properties.get(single_n_key, 0) - ) - row[f"doubly_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - doubly_c_key, 0 - ) - row[f"triply_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( - triply_c_key, 0 - ) - - row["sum_area_in_ha"] = properties.get("sum", 0) / 10000 - df_data.append(row) - - # Create and format DataFrame - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - # Round numeric columns - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - # Write to Excel - df.to_excel(writer, sheet_name="croppingIntensity_annual", index=False) - print("Excel file created for cropping intensity.") - - -def create_excel_crop_drou(data, output_file, writer, start_year, end_year): - df_data = [] - - features = data["features"] - for feature in features: - properties = feature.get("properties", {}) - row = {"UID": properties.get("uid", "Unknown ID")} - - for year in range(start_year, end_year + 1): - drlb_key = f"drlb_{year}" - drysp_key = f"drysp_{year}" - kh_cr_key = f"kh_cr_{year}" - m_ons_key = f"m_ons_{year}" - pcr_k_key = f"pcr_k_{year}" - t_wks_key = f"t_wks_{year}" - - # Get drought levels (drlb) and count occurrences - drlb_value = properties.get(drlb_key, "") - row[f"No_Drought_in_weeks_{year}"] = drlb_value.count("0") - row[f"Mild_in_weeks_{year}"] = drlb_value.count("1") - row[f"Moderate_in_weeks_{year}"] = drlb_value.count("2") - row[f"Severe_in_weeks_{year}"] = drlb_value.count("3") - - # Add other properties - row[f"drysp_unit_4_weeks_{year}"] = properties.get(drysp_key, "0") - row[f"kharif_cropped_sqkm_{year}"] = properties.get(kh_cr_key, "0") - row[f"monsoon_onset_{year}"] = properties.get(m_ons_key, "0") - row[f"kharif_cropped_area_percent_{year}"] = properties.get(pcr_k_key, "0") - row[f"total_weeks_{year}"] = properties.get(t_wks_key, "0") - - df_data.append(row) - - # Create DataFrame - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - # Write to Excel - df.to_excel(writer, sheet_name="croppingDrought_kharif", index=False) - print("Excel file created for cropping drought.") - - -def parse_geojson_annual_mws(data): - features = data["features"] - - all_data = defaultdict(lambda: defaultdict(dict)) - - for feature in features: - properties = feature["properties"] - uid = properties.get("uid", "Unknown") - - for key, value in properties.items(): - if isinstance(key, str) and isinstance(value, str): - if key.startswith("20") and len(key) == 9: - year = key - try: - # Attempt to parse the value as JSON - year_data = json.loads(value.replace("'", '"')) - all_data[uid][year] = year_data - except Exception as e: - print(f"Couldn't parse data for {uid}, {key}: {e}") - - return all_data - - -def create_excel_annual_mws(data, output_file, writer): - df_data = [] - year_columns = ["ET", "RunOff", "G", "DeltaG", "Precipitation", "WellDepth"] - - for uid, years in data.items(): - row = {"UID": uid} - - for year, metrics in years.items(): - start_year = year[:4] - end_year = str(int(start_year) + 1) - formatted_year = f"{start_year}-{end_year}" - - for col in year_columns: - if col == "WellDepth": - column_name = f"{col}_in_m_{formatted_year}" - row[column_name] = metrics.get(col, "N/A") - else: - column_name = f"{col}_in_mm_{formatted_year}" - row[column_name] = metrics.get(col, "N/A") - - df_data.append(row) - - df = pd.DataFrame(df_data) - df = df.sort_values(["UID"]) - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="hydrological_annual", index=False) - print("Excel file created for hydrological_annual") - - -def parse_json_seas_mws(file_path): - with open(file_path, "r") as file: - data = json.load(file) - return data - - -def get_season(month): - if month in (3, 4, 5, 6): - return "zaid" - elif month in (7, 8, 9, 10): - return "kharif" - elif month in (11, 12, 1, 2): - return "rabi" - - -def process_feature(feature): - uid = feature["properties"]["uid"] - results = { - "UID": uid, - "precipitation": {"kharif": {}, "rabi": {}, "zaid": {}}, - "et": {"kharif": {}, "rabi": {}, "zaid": {}}, - "runoff": {"kharif": {}, "rabi": {}, "zaid": {}}, - "delta g": {"kharif": {}, "rabi": {}, "zaid": {}}, - "g": {"kharif": {}, "rabi": {}, "zaid": {}}, - } - - variable_mapping = { - "Precipitation": "precipitation", - "ET": "et", - "RunOff": "runoff", - "DeltaG": "delta g", - "G": "g", - } - - for key, value in feature["properties"].items(): - if key.startswith("20"): - try: - date = datetime.strptime(key, "%Y-%m-%d") - year = date.year - month = date.month - season = get_season(month) - if season == "rabi": - current_year = year - 1 if month in (1, 2) else year - elif season == "zaid": - current_year = year - 1 if month in (3, 4, 5, 6) else year - else: - current_year = year - data = json.loads(value) - - for json_var, result_var in variable_mapping.items(): - if json_var in data: - if current_year not in results[result_var][season]: - results[result_var][season][current_year] = 0.0 - results[result_var][season][current_year] += float( - data[json_var] - ) - - except (ValueError, json.JSONDecodeError) as e: - print(f"Error processing data for date {key}: {e}") - continue - return results - - -def create_excel_seas_mws(processed_data, output_file, writer, start_year, end_year): - variables = ["precipitation", "et", "runoff", "delta g", "g"] - seasons = ["kharif", "rabi", "zaid"] - - data = {"UID": []} - for variable in variables: - for year in range(start_year, end_year + 1): - for season in seasons: - end_to_year = year + 1 - column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" - data[column_name] = [] - - for feature_data in processed_data: - data["UID"].append(feature_data["UID"]) - for variable in variables: - for year in range(start_year, end_year + 1): - for season in seasons: - end_to_year = year + 1 - column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" - value = feature_data[variable].get(season, {}).get(year, 0.0) - data[column_name].append(value) - - df = pd.DataFrame(data) - df = df.sort_values("UID") - - ## for roundoff all numeric value upto 2 decimal - numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns - df[numeric_cols] = df[numeric_cols].round(2) - - df.to_excel(writer, sheet_name="hydrological_seasonal", index=False) - print(f"Excel file created hydrological_seasonal") - - -def create_excel_for_village_boun(old_geojson, writer): - results = [] - - village_data = {} - - for feature in old_geojson["features"]: - properties = feature["properties"] - - # Extract properties - state_census_ID = properties.get("state_cen", None) - dist_census_ID = properties.get("dist_cen", None) - block_census_ID = properties.get("block_cen", None) - village_id = properties.get("vill_ID", None) - village_name = properties.get("vill_name", None) - - # Initialize village data using village_id as the key - if village_id not in village_data: - village_data[village_id] = { - "village_name": village_name, - "TOT_P": 0, - "P_LIT": 0, - "P_SC": 0, - "P_ST": 0, - "state_census_ID": state_census_ID, - "dist_census_ID": dist_census_ID, - "block_census_ID": block_census_ID, - "geometry": feature["geometry"], - } - - village_data[village_id]["TOT_P"] += properties.get("TOT_P", 0) - village_data[village_id]["P_LIT"] += properties.get("P_LIT", 0) - village_data[village_id]["P_SC"] += properties.get("P_SC", 0) - village_data[village_id]["P_ST"] += properties.get("P_ST", 0) - - for village_id, data in village_data.items(): - total_popu = data["TOT_P"] - literacy_rate = data["P_LIT"] * 100 / total_popu if total_popu > 0 else 0.0 - total_SC_popu = data["P_SC"] - total_ST_popu = data["P_ST"] - sc_perce = (data["P_SC"] * 100 / total_popu) if total_popu > 0 else 0.0 - st_perce = (data["P_ST"] * 100 / total_popu) if total_popu > 0 else 0.0 - - results.append( - { - "state_census_ID": data["state_census_ID"], - "dist_census_ID": data["dist_census_ID"], - "block_census_ID": data["block_census_ID"], - "village_id": village_id, - "village_name": data["village_name"], - "total_population_count": total_popu, - "total_SC_population_count": total_SC_popu, - "total_ST_population_count": total_ST_popu, - "literacy_rate_percent": literacy_rate, - "SC_percent": sc_perce, - "ST_percent": st_perce, - } - ) - - results_df = pd.DataFrame(results) - results_df.to_excel(writer, sheet_name="social_economic_indicator", index=False) - - print(f"Excel file created for social_economic_indicator") - - -def download_layers_excel_file(state, district, block): - base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") - state_path = os.path.join(base_path, state.upper()) - district_path = os.path.join(state_path, district.upper()) - filename = f"{district}_{block}.xlsx" - file_path = os.path.join(district_path, filename) - - output_dir = Path(file_path).parent - output_dir.mkdir(parents=True, exist_ok=True) - - if not os.path.exists(file_path): - try: - if not get_vector_layer_geoserver(state, district, block): - return Response( - { - "status": "error", - "message": "Failed to generate vector layer from GeoServer.", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - if not os.path.exists(file_path): - return Response( - {"status": "error", "message": "Failed to generate Excel file."}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - except Exception as e: - return Response( - { - "status": "error", - "message": f"Error during file generation: {str(e)}", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - else: - print(f"Excel file already exists at: {file_path}") - - # Single file reading logic - only written once! - if os.path.exists(file_path): - try: - with open(file_path, "rb") as file: - response = HttpResponse( - file.read(), - content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ) - response["Content-Disposition"] = f"attachment; filename={filename}" - return response - except Exception as e: - return Response( - {"status": "error", "message": f"Error reading file: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - else: - return Response( - {"status": "error", "message": "Failed to locate Excel file."}, - status=status.HTTP_404_NOT_FOUND, - ) - - -def generate_stats_excel_file(state, district, block): - """ - Deletes existing Excel layer file and forces regeneration. - Then returns the newly generated file. - """ - base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") - state_path = os.path.join(base_path, state.upper()) - district_path = os.path.join(state_path, district.upper()) - filename = f"{district}_{block}.xlsx" - file_path = os.path.join(district_path, filename) - - try: - # Create directory if it doesn't exist - output_dir = Path(file_path).parent - output_dir.mkdir(parents=True, exist_ok=True) - - # ALWAYS delete existing file if it exists - if os.path.exists(file_path): - os.remove(file_path) - - from .mws_indicators import generate_mws_data_for_kyl_filters - from .village_indicators import get_generate_filter_data_village - from public_api.views import get_tehsil_json - - get_vector_layer_geoserver(state, district, block) - get_tehsil_json(state, district, block, 1) - generate_mws_data_for_kyl_filters(state, district, block, "json", 1) - get_generate_filter_data_village(state, district, block, 1) - - if not os.path.exists(file_path): - return Response( - { - "status": "error", - "message": "Excel file generation completed but file not found.", - }, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - # Return the newly generated file - with open(file_path, "rb") as file: - response = HttpResponse( - file.read(), - content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ) - response["Content-Disposition"] = f"attachment; filename={filename}" - return response - - except Exception as e: - return Response( - {"status": "error", "message": f"Failed to generate stats excel: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - - -def add_sheets_to_excel(state, district, block, sheets): - try: - sheets_to_add = [sheet.strip() for sheet in sheets.split(",") if sheet.strip()] - results = [] - - for sheet in sheets_to_add: - r = get_vector_layer_geoserver(state, district, block, sheet) - results.append(r) - - from .mws_indicators import generate_mws_data_for_kyl_filters - from .village_indicators import get_generate_filter_data_village - - from public_api.views import get_tehsil_json - - get_tehsil_json(state, district, block, 1) - generate_mws_data_for_kyl_filters(state, district, block, "json", 1) - get_generate_filter_data_village(state, district, block, 1) - - successful = [r for r in results if r["status"] == "success"] - failed = [r for r in results if r["status"] == "failed"] - - response_data = { - "status": "success" if successful else "failed", - "message": f"Stats data added info. {len(successful)} successful, {len(failed)} failed.", - } - - return response_data - - except Exception as e: - return { - "status": "error", - "message": str(e), - } +import os +import requests, json +from django.http import HttpResponse, Http404 +from rest_framework.response import Response +import pandas as pd +import geopandas as gpd +from collections import defaultdict +from datetime import datetime +from nrm_app.settings import GEOSERVER_URL, EXCEL_PATH +import numpy as np +from shapely.geometry import Point, shape +from .models import LayerInfo +from django.http import HttpResponse +from rest_framework import status +from pathlib import Path + + +def fetch_layers_for_excel_generation(): + """ + Fetch all vector layers where `excel_to_be_generated` is True. + """ + layers = LayerInfo.objects.filter( + layer_type="vector", excel_to_be_generated=True + ).values("layer_name", "workspace", "start_year", "end_year") + return list(layers) + + +def get_url(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 get_vector_layer_geoserver(state, district, block, specific_sheets=None): + print(f"Generate Stats excel for {state}_{district}_{block}") + base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") + district_path = os.path.join( + base_path, state.replace(" ", "_").upper(), district.replace(" ", "_").upper() + ) + os.makedirs(district_path, exist_ok=True) + xlsx_file = os.path.join(district_path, f"{district}_{block}.xlsx") + + workspaces_to_process = specific_sheets + + # Handle existing sheets when adding specific workspaces + results = [] + file_exists = os.path.exists(xlsx_file) + mode = "a" if file_exists else "w" + + # Use append mode with if_sheet_exists='replace' + with pd.ExcelWriter( + xlsx_file, + engine="openpyxl", + mode=mode, + if_sheet_exists="replace" if mode == "a" else None, + ) as writer: + for layer in fetch_layers_for_excel_generation(): + workspace = layer["workspace"] + + if workspaces_to_process and workspace not in workspaces_to_process: + continue + + start_year = layer.get("start_year") + end_year = layer.get("end_year") + + if "{district}" in layer["layer_name"] and "{block}" in layer["layer_name"]: + layer_name = layer["layer_name"].format(district=district, block=block) + else: + layer_name = layer["layer_name"] + + print(f"Processing layer: {layer_name}") + print(f"Workspace for the layer is: {workspace}") + + geojson_data = None + try: + url = get_url(workspace, layer_name) + response = requests.get(url) + response.raise_for_status() + geojson_data = response.json() + except requests.exceptions.RequestException as e: + print(f"Failed to fetch data for {layer_name}: {e}") + results.append( + {"layer": layer_name, "status": "failed", "workspace": workspace} + ) + continue + + # Process the data based on workspace + if workspace == "terrain": + create_excel_for_terrain(geojson_data, xlsx_file, writer) + elif ( + workspace == "terrain_lulc" + and layer_name == f"{district}_{block}_lulc_slope" + ): + create_excel_for_terrain_lulc_slope(geojson_data, xlsx_file, writer) + elif ( + workspace == "terrain_lulc" + and layer_name == f"{district}_{block}_lulc_plain" + ): + create_excel_for_terrain_lulc_plain(geojson_data, xlsx_file, writer) + elif workspace == "swb": + create_excel_for_swb( + geojson_data, xlsx_file, writer, start_year, end_year + ) + create_excel_for_mws_intersect_swb( + geojson_data, writer, district, block + ) + elif workspace == "nrega_assets": + mws_lay_name = f"deltaG_well_depth_{district}_{block}" + mws_file_url = get_url("mws_layers", mws_lay_name) + + try: + response = requests.get(mws_file_url) + response.raise_for_status() + mws_geojson_datas = response.json() + except requests.exceptions.RequestException as e: + print(f"Failed to fetch MWS data: {e}") + continue + + create_excel_for_nrega_assets( + geojson_data, + mws_geojson_datas, + xlsx_file, + writer, + start_year, + end_year, + ) + try: + fetch_village_asset_count( + state, district, block, writer, xlsx_file, start_year, end_year + ) + except Exception as e: + print("Exception as e", str(e)) + + elif workspace == "crop_intensity": + create_excel_crop_inten( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "drought": + create_excel_crop_drou( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif ( + workspace == "mws_layers" + and layer_name == f"deltaG_well_depth_{district}_{block}" + ): + parsed_data_annual_mws = parse_geojson_annual_mws(geojson_data) + create_excel_annual_mws(parsed_data_annual_mws, xlsx_file, writer) + try: + create_excel_mws_inters_villages( + geojson_data, xlsx_file, writer, district, block + ) + except Exception as e: + print("Exception", str(e)) + elif ( + workspace == "mws_layers" + and layer_name == f"deltaG_fortnight_{district}_{block}" + ): + processed_data = [ + process_feature(feature) for feature in geojson_data["features"] + ] + create_excel_seas_mws( + processed_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "panchayat_boundaries": + create_excel_for_village_boun(geojson_data, writer) + elif workspace == "drought_causality": + create_excel_for_drought_causality( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "ccd": + create_excel_for_ccd( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "canopy_height": + create_excel_for_ch( + geojson_data, xlsx_file, writer, start_year, end_year + ) + elif workspace == "tree_overall_ch": + create_excel_for_overall_tree_change(geojson_data, xlsx_file, writer) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Afforestation" + ): + create_excel_chan_detection_afforestation( + geojson_data, xlsx_file, writer + ) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_CropIntensity" + ): + create_excel_chan_detection_cropintensity( + geojson_data, xlsx_file, writer + ) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Deforestation" + ): + create_excel_chan_detection_deforestation( + geojson_data, xlsx_file, writer + ) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Degradation" + ): + create_excel_chan_detection_degradation(geojson_data, xlsx_file, writer) + elif ( + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Urbanization" + ): + create_excel_chan_detection_urbanization( + geojson_data, xlsx_file, writer + ) + elif workspace == "restoration": + create_excel_for_restoration(geojson_data, xlsx_file, writer) + elif workspace == "aquifer": + create_excel_for_aquifer(geojson_data, writer) + elif workspace == "soge": + create_excel_for_soge(geojson_data, xlsx_file, writer) + elif workspace == "lcw": + create_excel_for_lcw(geojson_data, writer) + elif workspace == "agroecological": + create_excel_for_agroecological(geojson_data, writer) + elif workspace == "factory_csr": + create_excel_for_factory_csr(geojson_data, writer) + elif workspace == "green_credit": + create_excel_for_green_credit(geojson_data, writer) + elif workspace == "mining": + create_excel_for_mining(geojson_data, writer) + elif workspace == "stream_order": + create_excel_for_stream_order(geojson_data, writer) + elif workspace == "mws_connectivity": + create_excel_for_mws_connectivity(geojson_data, writer) + elif workspace == "mws": + create_excel_for_mws(geojson_data, writer) + elif workspace == "facilities_proximity": + create_excel_for_facilities(geojson_data, writer) + elif workspace == "dem": + create_excel_for_dem(geojson_data, writer) + elif workspace == "canal": + create_excel_for_canal(geojson_data, writer) + elif workspace == "river": + create_excel_for_river(geojson_data, writer) + elif workspace == "lulc_vector": + create_excel_for_lulc_vector(geojson_data, writer, start_year, end_year) + elif workspace == "drainage_density": + create_excel_for_drainage_density(geojson_data, writer) + elif workspace == "antyodaya_2020": + create_excel_for_antyodaya_20(geojson_data, writer) + elif workspace == "livestocks": + create_excel_for_livestock(geojson_data, writer) + + results.append( + {"layer": layer_name, "status": "success", "workspace": workspace} + ) + + return results + + +def create_excel_for_livestock(data, writer): + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Columns to exclude + exclude_cols = ["state_name","district_name","TEHSIL"] + df = df.drop(columns=exclude_cols, errors="ignore") + + # Keep important columns first if they exist + first_cols = [c for c in ["pc11_village_id", "NAME"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + df.to_excel(writer, sheet_name="livestock", index=False) + print("Excel file created for livestock") + except Exception as e: + print(f"Error in getting livestock data: {e}") + + +def create_excel_for_antyodaya_20(data, writer): + try: + features = data.get("features", []) + df_data = [feature.get("properties", {}) for feature in features] + df = pd.DataFrame(df_data) + + # Keep important columns first if they exist + first_cols = [c for c in ["village_id", "village_name"] if c in df.columns] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + # Round numeric columns + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="antyodaya", index=False) + print("Excel file created for antyodaya") + except Exception as e: + print(f"Error in getting antyodaya data: {e}") + + +def create_excel_for_drainage_density(data, writer): + import ast + print("Inside create_excel_for Drainage Density") + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), + "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), + "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="drainage_density", index=False) + print("Excel file created for Drainage Density") + + +def create_excel_for_lulc_vector(data, writer, start_year, end_year): + df_data = [] + features = data["features"] + years = list(range(start_year, end_year + 1)) + + classes = { + "barrenland": ("barrenland", "barrenla"), + "built_up_area": ("built-up_a", "built-up"), + "cropland": ("cropland_a", "cropland"), + "double_crop": ("doubly_cro", "doubly_c"), + "triple_crop": ("triply_cro", "triply_c"), + "tree_forest": ("tree_fores", "tree_for"), + "shrub_scrub": ("shrub_scru", "shrub_sc"), + "single_kharif": ("single_kha", "single_k"), + "single_non_kharif": ("single_non", "single_n"), + "k_water": ("k_water_ar", "k_water_"), + "kr_water": ("kr_water_a", "kr_water"), + "krz_water": ("krz_water_", "krz_wate"), + } + + def get_key(base_key, trunc_prefix, idx): + """Derive the property key for a given year index.""" + if idx == 0: + return base_key + return f"{trunc_prefix}_{idx}" + + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "sum_in_ha": (properties.get("sum") or 0) / 10000, + } + + for idx, year in enumerate(years): + for class_name, (base_key, trunc_prefix) in classes.items(): + key = get_key(base_key, trunc_prefix, idx) + row[f"{class_name}_in_ha_{year}"] = properties.get(key, 0) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="lulc_vector", index=False) + print("Excel file created for lulc vector") + + +def create_excel_for_canal(data, writer): + print("Inside create_excel_for Canal") + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "project_name": properties.get("prjname", ""), + "canal_code": properties.get("cancode", ""), + "canal_name": properties.get("canname", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="canal", index=False) + print("Excel file created for canal") + except Exception as e: + print("Canal Layer not found :: ", e) + + +def create_excel_for_river(data, writer): + print("Inside create_excel_for River") + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "river_name": properties.get("rivname", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="river", index=False) + print("Excel file created for river") + except Exception as e: + print("River Layer not found :: ", e) + + +def create_excel_for_dem(data, writer): + print("Inside create_excel_for DEM") + + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties.get("uid", ""), + "min_elevation_in_m": properties.get("min_elevation", ""), + "max_elevation_in_m": properties.get("max_elevation", ""), + "mean_elevation_in_m": properties.get("mean_elevation", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="dem", index=False) + print("Excel file created for dem") + + +def create_excel_for_mws_intersect_swb(swb_geojson, writer, district, block): + print("Inside create_excel_for_mws_intersect_swb") + + # --- Fetch MWS layer --- + mws_layer_name = f"mws_{district}_{block}" + mws_data_url = get_url("mws", mws_layer_name) + + mws_response = requests.get(mws_data_url) + if mws_response.status_code != 200: + print(f"Error fetching MWS data: {mws_response.status_code}") + return + + mws_geojson = mws_response.json() + + def calculate_intersection_area(geom1, geom2): + if geom1.intersects(geom2): + return geom1.intersection(geom2).area + return 0 + + rows = [] + + for mws_feature in mws_geojson["features"]: + mws_props = mws_feature["properties"] + mws_uid = mws_props.get("uid") + mws_geom = shape(mws_feature["geometry"]) + + for swb_feature in swb_geojson["features"]: + swb_props = swb_feature["properties"] + swb_geom = shape(swb_feature["geometry"]) + + intersection_area = calculate_intersection_area(mws_geom, swb_geom) + + if intersection_area > 0: + # waterbodies centroid calculation + centroid = swb_geom.centroid + lon, lat = centroid.x, centroid.y + + rows.append( + { + "UID": mws_uid, + "SWB_UID": swb_props.get("UID"), + "Waterbodies_name": swb_props.get("water_body_name"), + "Latitude": lat, + "Longitude": lon, + } + ) + + df = pd.DataFrame(rows) + + if not df.empty: + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="mws_intersect_swb", index=False) + print("Excel sheet 'mws_intersect_swb' created successfully") + + +def create_excel_for_facilities(data, writer): + try: + features = data["features"] + df_data = [feature["properties"] for feature in features] + + df = pd.DataFrame(df_data) + + first_cols = ["censuscode2011", "censusname"] + other_cols = [c for c in df.columns if c not in first_cols] + df = df[first_cols + other_cols] + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] + df.rename( + columns={ + col: f"{col}_in_km" + for col in df.columns + if col not in exclude_cols + }, + inplace=True + ) + + # Write to Excel + df.to_excel(writer, sheet_name="facilities_proximity", index=False) + + print("Excel file created for facilities_proximity") + except Exception as e: + print("facilities_proximity Layer not found :: ", e) + + + +def create_excel_for_mws(data, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "watershed_code": properties.get("wsconc", ""), + "basin_code": properties.get("bacode", ""), + "sub_basin_code": properties.get("sbcode", ""), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mws", index=False) + print("Excel file created for mws") + + +def create_excel_for_mws_connectivity(data, writer): + """ + direction_map = { + '0': 'No Direction', + '1': 'North-East', + '2': 'East', + '3': 'South-East', + '4': 'South', + '5': 'South-West', + '6': 'West', + '7': 'North-West', + '8': 'North' + } + """ + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "direction": properties["direction"], + "downstream_mws": properties["downstream"], + "upstream_mws": properties["upstream"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mws_connectivity", index=False) + print("Excel file created for mws_connectivity") + + +def create_excel_for_stream_order(data, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "order_1_area_percent": properties["1"], + "order_2_area_percent": properties["2"], + "order_3_area_percent": properties["3"], + "order_4_area_percent": properties["4"], + "order_5_area_percent": properties["5"], + "order_6_area_percent": properties["6"], + "order_7_area_percent": properties["7"], + "order_8_area_percent": properties["8"], + "order_9_area_percent": properties["9"], + "order_10_area_percent": properties["10"], + "order_11_area_percent": properties["11"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="stream_order", index=False) + print("Excel file created for stream order") + + +def create_excel_for_mining(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["company_na"], + "proposal": properties["proposal"], + "sector_moefcc": properties["sector_moe"], + "village": properties["village"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df.replace("", "unknown", inplace=True) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="mining", index=False) + print("Excel file created for mining") + except Exception as e: + print("Mining Layer not found :: ", e) + + +def create_excel_for_green_credit(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "division": properties["division"], + # "parcel_id": properties["parcel_id"], + "land_info": properties["land_info"], + "kml_url": properties["kml_url"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="green_credit", index=False) + print("Excel file created for green_credit") + except Exception as e: + print("green credit Layer not found :: ", e) + + +def create_excel_for_factory_csr(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "Company_Name": properties["COMPANY NA"], + "ADDRESS": properties["ADDRESS"], + "LOCATION T": properties["LOCATION T"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="factory_csr", index=False) + print("Excel file created for factory_csr") + except Exception as e: + print("factory csr Layer not found :: ", e) + + +def create_excel_for_agroecological(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "organization_name": properties["organization_name"], + "organization_type": properties["organization_type"], + "created_at": properties["created_at"], + "contact_person": properties["contact_person"], + "email": properties["email"], + "domains": properties["domains"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="agroecological", index=False) + print("Excel file created for agroecological") + except Exception as e: + print("agroecological Layer not found :: ", e) + + +def create_excel_for_lcw(data, writer): + try: + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "title_of_conflict": properties["Title of Conflict"], + "link_to_conflict": properties["Link to conflict"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="lcw_conflict", index=False) + print("Excel file created for lcw_conflict") + except Exception as e: + print("lcw Layer not found :: ", e) + + +def create_excel_for_soge(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "soge_dev_percent": properties["sgw_dev_pe"], + "class_code": properties["code"], + "class_name": properties["class"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="soge_vector", index=False) + print(f"Excel file created for soge_vector") + + +def create_excel_for_aquifer(data, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + if properties.get("aquifer_class", "") == "Hard-Rock": + aquifer_class = "Hard Rock" + else: + aquifer_class = "Alluvium" + row = { + "UID": properties.get("uid", ""), + "area_in_ha": properties.get("area_in_ha", ""), + "aquifer_class": aquifer_class, + "principle_aq_Alluvium_percent": properties.get( + "principle_aq_Alluvium_percent", "0" + ), + "principle_aq_Banded Gneissic Complex_percent": properties.get( + "principle_aq_Banded Gneissic Complex_percent", "0" + ), + "principle_aq_Basalt_percent": properties.get( + "principle_aq_Basalt_percent", "0" + ), + "principle_aq_Charnockite_percent": properties.get( + "principle_aq_Charnockite_percent", "0" + ), + "principle_aq_Gneiss_percent": properties.get( + "principle_aq_Gneiss_percent", "0" + ), + "principle_aq_Granite_percent": properties.get( + "principle_aq_Granite_percent", "0" + ), + "principle_aq_Intrusive_percent": properties.get( + "principle_aq_Intrusive_percent", "0" + ), + "principle_aq_Khondalite_percent": properties.get( + "principle_aq_Khondalite_percent", "0" + ), + "principle_aq_Laterite_percent": properties.get( + "principle_aq_Laterite_percent", "0" + ), + "principle_aq_Limestone_percent": properties.get( + "principle_aq_Limestone_percent", "0" + ), + "principle_aq_Quartzite_percent": properties.get( + "principle_aq_Quartzite_percent", "0" + ), + "principle_aq_Sandstone_percent": properties.get( + "principle_aq_Sandstone_percent", "0" + ), + "principle_aq_Schist_percent": properties.get( + "principle_aq_Schist_percent", "0" + ), + "principle_aq_Shale_percent": properties.get( + "principle_aq_Shale_percent", "0" + ), + "principle_aq_None_percent": properties.get( + "principle_aq_None_percent", "0" + ), + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + # Round numeric values + numeric_cols = df.select_dtypes(include=["number"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df = df.sort_values(["UID"]) + df.to_excel(writer, sheet_name="aquifer_vector", index=False) + print("Excel file created for aquifer_vector") + + +def create_excel_for_restoration(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "wide_scale_restoration_area_in_ha": properties["Wide-scale"], + "protection_area_in_ha": properties["Protection"], + "mosaic_restoration_area_in_ha": properties["Mosaic Res"], + "excluded_areas_in_ha": properties["Excluded A"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="restoration_vector", index=False) + print(f"Excel file created for restoration_vector") + + +def create_excel_for_overall_tree_change(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "afforestation_area_in_ha": properties["Afforestation"], + "deforestation_area_in_ha": properties["Deforestation"], + "degradation_area_in_ha": properties["Degradation"], + "improvement_area_in_ha": properties["Improvement"], + "missing_data_in_ha": properties["Missing Data"], + "no_change_area_in_ha": properties["No_Change"], + "partially_degraded_area_in_ha": properties["Partially_Degraded"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="overall_tree_change", index=False) + print(f"Excel file created for overall_tree_change") + + +def create_excel_for_ccd(data, xlsx_file, writer, start_year, end_year): + df_data = [] + features = data["features"] + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + } + + for year in range(start_year, end_year + 1): + row["high_density_area_in_ha_" + str(year)] = properties.get( + "High_Density_" + str(year), None + ) + row["low_density_area_in_ha_" + str(year)] = properties.get( + "Low_Density_" + str(year), None + ) + row["missing_data_area_in_ha_" + str(year)] = properties.get( + "Missing_Data_" + str(year), None + ) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="Canopy_Cover_Density", index=False) + print(f"Excel file created for Canopy_Cover_Density") + + +def create_excel_for_ch(data, xlsx_file, writer, start_year, end_year): + df_data = [] + features = data["features"] + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + } + + for year in range(start_year, end_year + 1): + row["short_trees_area_in_ha_" + str(year)] = properties.get( + "Short_Trees_" + str(year), None + ) + row["medium_trees_area_in_ha_" + str(year)] = properties.get( + "Medium_Height_Trees_" + str(year), None + ) + row["tall_trees_area_in_ha_" + str(year)] = properties.get( + "Tall_Trees_" + str(year), None + ) + row["missing_data_area_in_ha_" + str(year)] = properties.get( + "Missing_Data_" + str(year), None + ) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="Canopy_height", index=False) + print(f"Excel file created for Canopy_height") + + +def create_excel_for_drought_causality(data, xlsx_file, writer, start_year, end_year): + df_data = [] + features = data["features"] + for feature in features: + properties = feature["properties"] + + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + } + + for year in range(start_year, end_year + 1): + row["severe_moderate_drought_causality_" + str(year)] = properties.get( + "se_mo_" + str(year), None + ) + row["mild_drought_causality_" + str(year)] = properties.get( + "mild_" + str(year), None + ) + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="drought_causality", index=False) + print(f"Excel file created for drought_causality") + + +def create_excel_chan_detection_afforestation(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "barren_to_forest_area_in_ha": properties.get("ba_fo", None), + "built_up_to_forest_area_in_ha": properties.get("bu_fo", None), + "farm_to_forest_area_in_ha": properties.get("fa_fo", None), + "forest_to_forest_area_in_ha": properties.get("fo_fo", None), + "scrub_land_to_forest_area_in_ha": properties.get("sc_fo", None), + "total_afforestation_area_in_ha": properties.get("total_aff", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_afforestation", index=False) + print(f"Excel file created for change_detection_afforestation") + + +def create_excel_chan_detection_cropintensity(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "single_to_single_area_in_ha": properties.get("si_si", None), + "single_to_double_area_in_ha": properties.get("si_do", None), + "single_to_triple_area_in_ha": properties.get("si_tr", None), + "double_to_single_area_in_ha": properties.get("do_si", None), + "double_to_double_area_in_ha": properties.get("do_do", None), + "double_to_triple_area_in_ha": properties.get("do_tr", None), + "triple_to_single_area_in_ha": properties.get("tr_si", None), + "triple_to_double_area_in_ha": properties.get("tr_do", None), + "triple_to_triple_area_in_ha": properties.get("tr_tr", None), + "total_change_crop_intensity_area_in_ha": properties.get( + "total_chan", properties.get("total_change", None) + ), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_cropintensity", index=False) + print(f"Excel file created for change_detection_cropintensity") + + +def create_excel_chan_detection_deforestation(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "forest_to_barren_area_in_ha": properties.get("fo_ba", None), + "forest_to_built_up_area_in_ha": properties.get("fo_bu", None), + "forest_to_farm_area_in_ha": properties.get("fo_fa", None), + "forest_to_forest_area_in_ha": properties.get("fo_fo", None), + "forest_to_scrub_land_area_in_ha": properties.get("fo_sc", None), + "total_deforestation_area_in_ha": properties.get("total_def", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_deforestation", index=False) + print(f"Excel file created for change_detection_deforestation") + + +def create_excel_chan_detection_degradation(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "farm_to_barren_area_in_ha": properties.get("f_ba", None), + "farm_to_built_up_area_in_ha": properties.get("f_bu", None), + "farm_to_farm_area_in_ha": properties.get("f_f", None), + "farm_to_scrub_land_area_in_ha": properties.get("f_sc", None), + "total_degradation_area_in_ha": properties.get("total_deg", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_degradation", index=False) + print(f"Excel file created for change_detection_degradation") + + +def create_excel_chan_detection_urbanization(data, xlsx_file, writer): + df_data = [] + features = data["features"] + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + df_data.append( + { + "UID": uid, + "area_in_ha": properties.get("area_in_ha", None), + "barren_shrub_to_built_up_area_in_ha": properties.get("b_bu", None), + "built_up_to_built_up_area_in_ha": properties.get("bu_bu", None), + "tree_farm_to_built_up_area_in_ha": properties.get("tr_bu", None), + "water_to_built_up_area_in_ha": properties.get("w_bu", None), + "total_urbanization_area_in_ha": properties.get("total_urb", None), + } + ) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="change_detection_urbanization", index=False) + print(f"Excel file created for change_detection_urbanization") + + +def create_excel_mws_inters_villages(mws_geojson, xlsx_file, writer, district, block): + print("Inside create_excel_mws_inters_villages") + admin_layer_name = district + "_" + block + admin_file_url = get_url("panchayat_boundaries", admin_layer_name) + + response = requests.get(admin_file_url) + if response.status_code != 200: + print(f"Error fetching data: {response.status_code}") + return + + village_geojson = response.json() + + def calculate_intersection_area_ha(village_geom, mws_geom, mws_area_ha): + if village_geom.intersects(mws_geom): + intersection = village_geom.intersection(mws_geom) + if mws_geom.area == 0: + return 0 + # ratio of intersection to full MWS geometry area (both in degrees²) + ratio = intersection.area / mws_geom.area + return ratio * mws_area_ha + return 0 + + mws_villages_dict = {} + + for mws_feature in mws_geojson["features"]: + mws_uid = mws_feature["properties"]["uid"] + mws_area = mws_feature["properties"]["area_in_ha"] + mws_geom = shape(mws_feature["geometry"]) + village_ids = set() + village_details = {} + + for village_feature in village_geojson["features"]: + village_id = village_feature["properties"]["vill_ID"] + if village_id == 0: + continue + + village_geom = shape(village_feature["geometry"]) + area_intersected_ha = calculate_intersection_area_ha( + village_geom, mws_geom, mws_area + ) + if area_intersected_ha > 0: + village_ids.add(village_id) + percentage_of_area = ( + (area_intersected_ha / mws_area) * 100 if mws_area > 0 else 0 + ) + village_details[village_id] = { + "area_intersect": round(area_intersected_ha, 2), + "percentage_of_area": round(percentage_of_area, 2), + } + + if village_ids: + mws_villages_dict[mws_uid] = { + "area_in_ha": mws_area, + "village_ids": list(village_ids), + "village_details": village_details, + } + + data = [ + { + "MWS UID": mws_uid, + "area_in_ha": values["area_in_ha"], + "Village IDs": values["village_ids"], + "Village Details": values["village_details"], + } + for mws_uid, values in mws_villages_dict.items() + ] + + df = pd.DataFrame(data) + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + df.to_excel(writer, sheet_name="mws_intersect_villages", index=False) + print("The data has been saved to mws_intersect_villages") + + +# def create_excel_village_inters_mwss(mws_geojson, xlsx_file, writer, district, block): +# print("Inside create_excel_village_inters_mwss") +# admin_layer_name = district + '_' + block +# admin_file_url = get_url('panchayat_boundaries', admin_layer_name) + +# response = requests.get(admin_file_url) +# if response.status_code != 200: +# print(f"Error fetching data: {response.status_code}") +# return +# village_geojson = response.json() + +# def calculate_intersection_area(village_geom: BaseGeometry, mws_geom: BaseGeometry) : +# try: +# # Check for empty geometries +# if village_geom.is_empty or mws_geom.is_empty: +# return 0.0 + +# # Fix invalid geometries if needed +# if not village_geom.is_valid: +# village_geom = village_geom.buffer(0) +# if not mws_geom.is_valid: +# mws_geom = mws_geom.buffer(0) + +# # Calculate intersection +# if village_geom.intersects(mws_geom): +# intersection = village_geom.intersection(mws_geom) +# return intersection.area if not intersection.is_empty else 0.0 + +# return 0.0 + +# except Exception as e: +# print(f"Error calculating intersection area: {e}") +# return 0.0 + + +# data = [] + +# processed_villages = set() + +# for village_feature in village_geojson['features']: +# village_id = village_feature['properties']['vill_ID'] +# village_name = village_feature['properties']['vill_name'] + +# village_key = (village_id, village_name) + +# if village_key in processed_villages: +# continue +# processed_villages.add(village_key) + +# village_geom = shape(village_feature['geometry']) + +# mws_uids = [] +# intersection_areas = [] + +# for mws_feature in mws_geojson['features']: +# mws_geom = shape(mws_feature['geometry']) +# area_intersected = calculate_intersection_area(village_geom, mws_geom) +# if area_intersected > 0: +# mws_uids.append(mws_feature['properties']['uid']) +# intersection_areas.append(area_intersected) + +# data.append({ +# 'Village ID': village_id, +# 'Village Name': village_name, +# 'MWS UIDs': mws_uids, +# }) + +# df = pd.DataFrame(data) + +# ## for roundoff all numeric value upto 2 decimal +# numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns +# df[numeric_cols] = df[numeric_cols].round(2) + +# df.to_excel(writer, sheet_name='village_intersect_mwss', index=False) +# print("Excel created for village_intersect_mwss") + + +def create_excel_for_terrain(data, output_file, writer): + print("Inside create_excel_for_terrain function") + df_data = [] + + terrain_description = { + 0: "Broad Sloppy and Hilly", + 1: "Mostly Plains", + 2: "Mostly Hills and Valleys", + 3: "Broad Plains and Slopes", + } + + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "terrain_cluster_id": properties["terrainClu"], + "terrain_description": terrain_description.get(properties["terrainClu"]), + "hill_slope_area_percent": properties["hill_slope"], + "plain_area_percent": properties["plain_area"], + "ridge_area_percent": properties["ridge_area"], + "slopy_area_percent": properties["slopy_area"], + "valley_area_percent": properties["valley_are"], + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="terrain", index=False) + print(f"Excel file created for terrain vector") + + +def create_excel_for_terrain_lulc_slope(data, output_file, writer): + df_data = [] + + terrain_description = { + 0: "Broad Sloppy and Hilly", + 1: "Mostly Plains", + 2: "Mostly Hills and Valleys", + 3: "Broad Plains and Slopes", + } + + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "terrain_cluster_id": properties["terrain_cl"], + "terrain_description": terrain_description.get(properties["terrain_cl"]), + "cluster_name": properties["clust_name"], + "barren_area_percent": properties["barren"], + "forests_area_percent": properties["forests"], + "shrub_scrubs_area_percent": properties["shrub_scru"], + "single_kharif_area_percent": properties["sing_khari"], + "single_non_kharif_area_percent": properties["sing_non_k"], + "double_cropping_area_percent": properties["double"], + "triple_cropping_area_percent": properties["triple"], + } + + df_data.append(row) + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="terrain_lulc_slope", index=False) + print("Excel file created for terrain_lulc_slope") + + +def create_excel_for_terrain_lulc_plain(data, output_file, writer): + df_data = [] + + terrain_description = { + 0: "Broad Sloppy and Hilly", + 1: "Mostly Plains", + 2: "Mostly Hills and Valleys", + 3: "Broad Plains and Slopes", + } + + features = data["features"] + + for feature in features: + properties = feature["properties"] + row = { + "UID": properties["uid"], + "area_in_ha": properties["area_in_ha"], + "terrain_cluster_id": properties["terrain_cl"], + "terrain_description": terrain_description.get(properties["terrain_cl"]), + "cluster_name": properties["clust_name"], + "barren_area_percent": properties["barren"], + "forests_area_percent": properties["forest"], + "shrub_scrubs_area_percent": properties["shrubs_scr"], + "single_non_kharif_area_percent": properties["sing_non_k"], + "single_kharif_area_percent": properties["sing_crop"], + "double_cropping_area_percent": properties["double_cro"], + "triple_cropping_area_percent": properties["triple_cro"], + } + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="terrain_lulc_plain", index=False) + print("Excel file created for terrain_lulc_plain") + + +def create_excel_for_swb(data, output_file, writer, start_year, end_year): + df_data = [] + features = data.get("features", []) + + for feature in features: + properties = feature.get("properties", {}) + uid = properties.get("MWS_UID", "Unknown") + + def calculate_area(base_area, percentage): + if base_area == 0 or percentage == 0: + return 0 + return base_area * (percentage / 100) + + parts = uid.split("_") + num_uid_parts_is = [ + f"{parts[i]}_{parts[i+1]}" for i in range(0, len(parts) - 1, 2) + ] + if len(parts) % 2 == 1: # Check for an unpaired last part + num_uid_parts_is.append(parts[-1]) + + # Generate years dynamically based on start_year and end_year + years = range(start_year, end_year) + + for num_uid_part in num_uid_parts_is: + row = {"UID": num_uid_part} + + for year in years: + short_year = f"{str(year)[-2:]}-{str(year+1)[-2:]}" + + # Construct keys dynamically using the shortened year format + total_area_key = f"area_{short_year}" + kharif_key = f"k_{short_year}" + rabi_key = f"kr_{short_year}" + zaid_key = f"krz_{short_year}" + + # Get values from properties + total_area = properties.get(total_area_key, 0) + kharif_percentage = properties.get(kharif_key, 0) + rabi_percentage = properties.get(rabi_key, 0) + zaid_percentage = properties.get(zaid_key, 0) + + # Calculate areas + row[f"total_area_in_ha_{year}-{year+1}"] = total_area / len( + num_uid_parts_is + ) + row[f"kharif_area_in_ha_{year}-{year+1}"] = calculate_area( + total_area, kharif_percentage + ) / len(num_uid_parts_is) + row[f"rabi_area_in_ha_{year}-{year+1}"] = calculate_area( + total_area, rabi_percentage + ) / len(num_uid_parts_is) + row[f"zaid_area_in_ha_{year}-{year+1}"] = calculate_area( + total_area, zaid_percentage + ) / len(num_uid_parts_is) + + # Add total SWB area + row["total_swb_area_in_ha"] = properties.get("area_ored", 0) / len( + num_uid_parts_is + ) + df_data.append(row) + + df = pd.DataFrame(df_data) + agg_dict = {col: "sum" for col in df.columns if col != "UID"} + grouped_df = df.groupby("UID").agg(agg_dict).reset_index() + + df = grouped_df.sort_values(["UID"]) + + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="surfaceWaterBodies_annual", index=False) + print("Excel file created for surfaceWaterBodies_annual") + + +def create_excel_for_nrega_assets( + nrega_data, mws_data, output_file, writer, start_year, end_year +): + workCategoryMapping = { + "SWC - Landscape level impact": "Soil and water conservation", + "Agri Impact - HH, Community": "Land restoration", + "Plantation": "Plantations", + "Irrigation - Site level impact": "Irrigation on farms", + "Irrigation Site level - Non RWH": "Other farm works", + "Household Livelihood": "Off-farm livelihood assets", + "Others - HH, Community": "Community assets", + } + + mws = gpd.GeoDataFrame.from_features(mws_data["features"]) + nrega = gpd.GeoDataFrame.from_features(nrega_data["features"]) + + # Set CRS if available in JSON + if "crs" in mws_data: + mws.set_crs(mws_data["crs"]["properties"]["name"], inplace=True) + if "crs" in nrega_data: + nrega.set_crs(nrega_data["crs"]["properties"]["name"], inplace=True) + + joined = gpd.sjoin(nrega, mws, how="inner", predicate="within") + counts = {} + + df_data = [] + valid_years = range(start_year, end_year) + + date_formats = [ + "%d-%b-%y %H:%M:%S.%f", + "%d-%b-%y %H:%M:%S", + "%d-%m-%y %H:%M:%S.%f", + "%d-%m-%y %H:%M:%S", + "%d-%b-%Y %H:%M:%S.%f", + "%d-%b-%Y %H:%M:%S", + "%d-%m-%Y %H:%M:%S.%f", + "%d-%m-%Y %H:%M:%S", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S", + "%Y/%m/%d %H:%M:%S.%f", + "%Y/%m/%d %H:%M:%S", + "%Y-%m-%dT%H:%M:%SZ", + ] + + for _, row in joined.iterrows(): + creation_t = row["creation_t"] + work_category = row["WorkCatego"] + mws_id = row["uid"] + + if isinstance(creation_t, pd.Timestamp): + creation_t = creation_t.strftime("%d-%m-%Y %H:%M:%S") + + date_obj = None + for date_format in date_formats: + try: + date_obj = datetime.strptime(creation_t, date_format) + break + except ValueError: + continue + + if date_obj is None: + continue + + year = date_obj.year + if year < 100: + year += 2000 + + if year not in valid_years: + continue + + category = workCategoryMapping.get(work_category, "Others - HH, Community") + + if mws_id not in counts: + counts[mws_id] = { + year: {cat: 0 for cat in workCategoryMapping.values()} + for year in range(start_year, end_year) + } + + if category not in counts[mws_id][year]: + counts[mws_id][year][category] = 0 + counts[mws_id][year][category] += 1 + + for mws_id, year_data in counts.items(): + row = {"mws_id": mws_id} + for year, categories in year_data.items(): + for category in workCategoryMapping.values(): + count = categories.get(category, 0) + row[f"{category}_count_{year}"] = count + df_data.append(row) + + if not df_data: + print("No data was collected for the DataFrame.") + else: + print(f"Collected {len(df_data)} rows of data for the DataFrame.") + + if df_data: + df = pd.DataFrame(df_data) + df.to_excel(writer, sheet_name="nrega_annual", index=False) + print("Excel file created for nrega_annual") + return "successfully created" + else: + print("No data available to write to Excel.") + + +def create_excel_village_nrega_assets( + result_df, output_file, writer, all_villages_df, start_year, end_year +): + workCategoryMapping = { + "SWC - Landscape level impact": "Soil and water conservation", + "Agri Impact - HH, Community": "Land restoration", + "Plantation": "Plantations", + "Irrigation - Site level impact": "Irrigation on farms", + "Irrigation Site level - Non RWH": "Other farm works", + "Household Livelihood": "Off-farm livelihood assets", + "Others - HH, Community": "Community assets", + } + + # start_year, end_year = 2017, 2022 + year_range = range(start_year, end_year + 1) + + # Initialize all-zero DataFrame for all villages + rows = [] + for _, row in all_villages_df.iterrows(): + base_row = {"vill_id": row["vill_ID"], "vill_name": row["vill_name"]} + for year in year_range: + for cat in workCategoryMapping.values(): + base_row[f"{cat}_count_{year}"] = 0 + rows.append(base_row) + + final_df = pd.DataFrame(rows) + + # Fill counts from assets + for _, row in result_df.iterrows(): + creation_t = row["creation_t"] + try: + date_obj = pd.to_datetime(creation_t, errors="coerce") + if pd.isnull(date_obj): + continue + except: + continue + + year = date_obj.year + if year not in year_range: + continue + + category = workCategoryMapping.get(row["WorkCatego"]) + if not category: + continue + + mask = (final_df["vill_id"] == row["vill_ID"]) & ( + final_df["vill_name"] == row["vill_name"] + ) + col_name = f"{category}_count_{year}" + final_df.loc[mask, col_name] += 1 + + # Sort columns for clean layout + id_cols = ["vill_id", "vill_name"] + category_cols = sorted( + [col for col in final_df.columns if col not in id_cols], + key=lambda x: (int(x.split("_")[-1]), x), + ) + final_df = final_df[id_cols + category_cols] + + # Save to Excel + final_df = final_df.drop_duplicates(subset=["vill_id", "vill_name"]) + final_df.to_excel(writer, sheet_name="nrega_assets_village", index=False) + print("Excel file created successfully with all villages.") + + +def fetch_village_asset_count( + state, district, block, writer, output_file, start_year, end_year +): + # 1. Read village data + village_gdf = gpd.read_file(get_url("panchayat_boundaries", f"{district}_{block}"))[ + ["vill_ID", "vill_name", "geometry"] + ].copy() + print("Village data loaded") + + # 2. Get NREGA data with timeout to prevent hanging + try: + nrega_url = get_url("nrega_assets", f"{district}_{block}") + print(f"Fetching: {nrega_url}") + + # Add timeout to prevent hanging + response = requests.get(nrega_url, timeout=120) + nrega_json = response.json() + print("NREGA data fetched successfully") + + except Exception as e: + print(f"Failed to get NREGA data: {e}") + # Return villages with "No Asset" if API fails + result_df = village_gdf[["vill_ID", "vill_name"]].copy() + result_df["Asset ID"] = "No Asset" + result_df["creation_t"] = "No Asset" + result_df["WorkCatego"] = "No Asset" + create_excel_village_nrega_assets( + result_df, + output_file, + writer, + village_gdf[["vill_ID", "vill_name"]], + start_year, + end_year, + ) + return result_df, village_gdf[["vill_ID", "vill_name"]] + + # 3. Process asset features + points_data = [] + features = nrega_json.get("features", []) + print(f"Processing {len(features)} features") + + for feature in features: + try: + point = Point(feature["geometry"]["coordinates"]) + properties = feature["properties"] + points_data.append( + { + "geometry": point, + "Asset ID": properties.get("Asset ID", "MISSING"), + "creation_t": properties.get("creation_t", ""), + "WorkCatego": properties.get("WorkCatego", ""), + } + ) + except: + continue + + # If no valid points, return no assets + if not points_data: + print("No valid asset points found") + result_df = village_gdf[["vill_ID", "vill_name"]].copy() + result_df["Asset ID"] = "No Asset" + result_df["creation_t"] = "No Asset" + result_df["WorkCatego"] = "No Asset" + create_excel_village_nrega_assets( + result_df, + output_file, + writer, + village_gdf[["vill_ID", "vill_name"]], + start_year, + end_year, + ) + return result_df, village_gdf[["vill_ID", "vill_name"]] + + print("Asset points created") + + # 4. Create points GeoDataFrame and match CRS + points_gdf = gpd.GeoDataFrame(points_data, geometry="geometry") + if village_gdf.crs != points_gdf.crs: + points_gdf.set_crs(village_gdf.crs, inplace=True) + + # 5. Find which village each asset belongs to + joined_gdf = gpd.sjoin(points_gdf, village_gdf, how="inner", predicate="within") + + # 6. Get asset + village info + result_df = joined_gdf[ + ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] + ].copy() + + # 7. Add villages that have no assets + villages_with_assets = result_df["vill_ID"].unique() + no_asset_villages = village_gdf[ + ~village_gdf["vill_ID"].isin(villages_with_assets) + ].copy() + no_asset_villages["Asset ID"] = "No Asset" + no_asset_villages["creation_t"] = "No Asset" + no_asset_villages["WorkCatego"] = "No Asset" + + result_df = pd.concat( + [ + result_df, + no_asset_villages[ + ["vill_ID", "vill_name", "Asset ID", "creation_t", "WorkCatego"] + ], + ], + ignore_index=True, + ) + + print("Processing complete") + + # 8. Create Excel file + create_excel_village_nrega_assets( + result_df, + output_file, + writer, + village_gdf[["vill_ID", "vill_name"]], + start_year, + end_year, + ) + + return result_df, village_gdf[["vill_ID", "vill_name"]] + + +def analyze_results(village_asset_count, village_gdf): + villages_with_counts = village_gdf.merge( + village_asset_count, on="vill_ID", how="left" + ) + villages_with_counts["asset_count"] = villages_with_counts["asset_count"].fillna(0) + return villages_with_counts + + +def create_excel_crop_inten(data, output_file, writer, start_year, end_year): + df_data = [] + + features = data["features"] + for feature in features: + properties = feature.get("properties", {}) + uid = properties.get("uid", "Unknown") + row = {"UID": uid, "area_in_ha": properties.get("area_in_ha", 0)} + + # Process each year in range using new key naming convention + for year in range(start_year, end_year + 1): + cropping_key = f"cropping_intensity_{year}" + single_c_key = f"single_cropped_area_{year}" + single_k_key = f"single_kharif_cropped_area_{year}" + single_n_key = f"single_non_kharif_cropped_area_{year}" + doubly_c_key = f"doubly_cropped_area_{year}" + triply_c_key = f"triply_cropped_area_{year}" + + row[f"cropping_intensity_unit_less_{year}-{year + 1}"] = properties.get( + cropping_key, 0 + ) + row[f"single_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + single_c_key, 0 + ) + row[f"single_kharif_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + single_k_key, 0 + ) + row[f"single_non_kharif_cropped_area_in_ha_{year}-{year + 1}"] = ( + properties.get(single_n_key, 0) + ) + row[f"doubly_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + doubly_c_key, 0 + ) + row[f"triply_cropped_area_in_ha_{year}-{year + 1}"] = properties.get( + triply_c_key, 0 + ) + + row["sum_area_in_ha"] = properties.get("sum", 0) / 10000 + df_data.append(row) + + # Create and format DataFrame + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + # Round numeric columns + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + # Write to Excel + df.to_excel(writer, sheet_name="croppingIntensity_annual", index=False) + print("Excel file created for cropping intensity.") + + +def create_excel_crop_drou(data, output_file, writer, start_year, end_year): + df_data = [] + + features = data["features"] + for feature in features: + properties = feature.get("properties", {}) + row = {"UID": properties.get("uid", "Unknown ID")} + + for year in range(start_year, end_year + 1): + drlb_key = f"drlb_{year}" + drysp_key = f"drysp_{year}" + kh_cr_key = f"kh_cr_{year}" + m_ons_key = f"m_ons_{year}" + pcr_k_key = f"pcr_k_{year}" + t_wks_key = f"t_wks_{year}" + + # Get drought levels (drlb) and count occurrences + drlb_value = properties.get(drlb_key, "") + row[f"No_Drought_in_weeks_{year}"] = drlb_value.count("0") + row[f"Mild_in_weeks_{year}"] = drlb_value.count("1") + row[f"Moderate_in_weeks_{year}"] = drlb_value.count("2") + row[f"Severe_in_weeks_{year}"] = drlb_value.count("3") + + # Add other properties + row[f"drysp_unit_4_weeks_{year}"] = properties.get(drysp_key, "0") + row[f"kharif_cropped_sqkm_{year}"] = properties.get(kh_cr_key, "0") + row[f"monsoon_onset_{year}"] = properties.get(m_ons_key, "0") + row[f"kharif_cropped_area_percent_{year}"] = properties.get(pcr_k_key, "0") + row[f"total_weeks_{year}"] = properties.get(t_wks_key, "0") + + df_data.append(row) + + # Create DataFrame + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + # Write to Excel + df.to_excel(writer, sheet_name="croppingDrought_kharif", index=False) + print("Excel file created for cropping drought.") + + +def parse_geojson_annual_mws(data): + features = data["features"] + + all_data = defaultdict(lambda: defaultdict(dict)) + + for feature in features: + properties = feature["properties"] + uid = properties.get("uid", "Unknown") + + for key, value in properties.items(): + if isinstance(key, str) and isinstance(value, str): + if key.startswith("20") and len(key) == 9: + year = key + try: + # Attempt to parse the value as JSON + year_data = json.loads(value.replace("'", '"')) + all_data[uid][year] = year_data + except Exception as e: + print(f"Couldn't parse data for {uid}, {key}: {e}") + + return all_data + + +def create_excel_annual_mws(data, output_file, writer): + df_data = [] + year_columns = ["ET", "RunOff", "G", "DeltaG", "Precipitation", "WellDepth"] + + for uid, years in data.items(): + row = {"UID": uid} + + for year, metrics in years.items(): + start_year = year[:4] + end_year = str(int(start_year) + 1) + formatted_year = f"{start_year}-{end_year}" + + for col in year_columns: + if col == "WellDepth": + column_name = f"{col}_in_m_{formatted_year}" + row[column_name] = metrics.get(col, "N/A") + else: + column_name = f"{col}_in_mm_{formatted_year}" + row[column_name] = metrics.get(col, "N/A") + + df_data.append(row) + + df = pd.DataFrame(df_data) + df = df.sort_values(["UID"]) + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="hydrological_annual", index=False) + print("Excel file created for hydrological_annual") + + +def parse_json_seas_mws(file_path): + with open(file_path, "r") as file: + data = json.load(file) + return data + + +def get_season(month): + if month in (3, 4, 5, 6): + return "zaid" + elif month in (7, 8, 9, 10): + return "kharif" + elif month in (11, 12, 1, 2): + return "rabi" + + +def process_feature(feature): + uid = feature["properties"]["uid"] + results = { + "UID": uid, + "precipitation": {"kharif": {}, "rabi": {}, "zaid": {}}, + "et": {"kharif": {}, "rabi": {}, "zaid": {}}, + "runoff": {"kharif": {}, "rabi": {}, "zaid": {}}, + "delta g": {"kharif": {}, "rabi": {}, "zaid": {}}, + "g": {"kharif": {}, "rabi": {}, "zaid": {}}, + } + + variable_mapping = { + "Precipitation": "precipitation", + "ET": "et", + "RunOff": "runoff", + "DeltaG": "delta g", + "G": "g", + } + + for key, value in feature["properties"].items(): + if key.startswith("20"): + try: + date = datetime.strptime(key, "%Y-%m-%d") + year = date.year + month = date.month + season = get_season(month) + if season == "rabi": + current_year = year - 1 if month in (1, 2) else year + elif season == "zaid": + current_year = year - 1 if month in (3, 4, 5, 6) else year + else: + current_year = year + data = json.loads(value) + + for json_var, result_var in variable_mapping.items(): + if json_var in data: + if current_year not in results[result_var][season]: + results[result_var][season][current_year] = 0.0 + results[result_var][season][current_year] += float( + data[json_var] + ) + + except (ValueError, json.JSONDecodeError) as e: + print(f"Error processing data for date {key}: {e}") + continue + return results + + +def create_excel_seas_mws(processed_data, output_file, writer, start_year, end_year): + variables = ["precipitation", "et", "runoff", "delta g", "g"] + seasons = ["kharif", "rabi", "zaid"] + + data = {"UID": []} + for variable in variables: + for year in range(start_year, end_year + 1): + for season in seasons: + end_to_year = year + 1 + column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" + data[column_name] = [] + + for feature_data in processed_data: + data["UID"].append(feature_data["UID"]) + for variable in variables: + for year in range(start_year, end_year + 1): + for season in seasons: + end_to_year = year + 1 + column_name = f"{variable}_{season}_in_mm_{year}-{end_to_year}" + value = feature_data[variable].get(season, {}).get(year, 0.0) + data[column_name].append(value) + + df = pd.DataFrame(data) + df = df.sort_values("UID") + + ## for roundoff all numeric value upto 2 decimal + numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns + df[numeric_cols] = df[numeric_cols].round(2) + + df.to_excel(writer, sheet_name="hydrological_seasonal", index=False) + print(f"Excel file created hydrological_seasonal") + + +def create_excel_for_village_boun(old_geojson, writer): + results = [] + + village_data = {} + + for feature in old_geojson["features"]: + properties = feature["properties"] + + # Extract properties + state_census_ID = properties.get("state_cen", None) + dist_census_ID = properties.get("dist_cen", None) + block_census_ID = properties.get("block_cen", None) + village_id = properties.get("vill_ID", None) + village_name = properties.get("vill_name", None) + + # Initialize village data using village_id as the key + if village_id not in village_data: + village_data[village_id] = { + "village_name": village_name, + "TOT_P": 0, + "P_LIT": 0, + "P_SC": 0, + "P_ST": 0, + "state_census_ID": state_census_ID, + "dist_census_ID": dist_census_ID, + "block_census_ID": block_census_ID, + "geometry": feature["geometry"], + } + + village_data[village_id]["TOT_P"] += properties.get("TOT_P", 0) + village_data[village_id]["P_LIT"] += properties.get("P_LIT", 0) + village_data[village_id]["P_SC"] += properties.get("P_SC", 0) + village_data[village_id]["P_ST"] += properties.get("P_ST", 0) + + for village_id, data in village_data.items(): + total_popu = data["TOT_P"] + literacy_rate = data["P_LIT"] * 100 / total_popu if total_popu > 0 else 0.0 + total_SC_popu = data["P_SC"] + total_ST_popu = data["P_ST"] + sc_perce = (data["P_SC"] * 100 / total_popu) if total_popu > 0 else 0.0 + st_perce = (data["P_ST"] * 100 / total_popu) if total_popu > 0 else 0.0 + + results.append( + { + "state_census_ID": data["state_census_ID"], + "dist_census_ID": data["dist_census_ID"], + "block_census_ID": data["block_census_ID"], + "village_id": village_id, + "village_name": data["village_name"], + "total_population_count": total_popu, + "total_SC_population_count": total_SC_popu, + "total_ST_population_count": total_ST_popu, + "literacy_rate_percent": literacy_rate, + "SC_percent": sc_perce, + "ST_percent": st_perce, + } + ) + + results_df = pd.DataFrame(results) + results_df.to_excel(writer, sheet_name="social_economic_indicator", index=False) + + print(f"Excel file created for social_economic_indicator") + + +def download_layers_excel_file(state, district, block): + base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") + state_path = os.path.join(base_path, state.upper()) + district_path = os.path.join(state_path, district.upper()) + filename = f"{district}_{block}.xlsx" + file_path = os.path.join(district_path, filename) + + output_dir = Path(file_path).parent + output_dir.mkdir(parents=True, exist_ok=True) + + if not os.path.exists(file_path): + try: + if not get_vector_layer_geoserver(state, district, block): + return Response( + { + "status": "error", + "message": "Failed to generate vector layer from GeoServer.", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + if not os.path.exists(file_path): + return Response( + {"status": "error", "message": "Failed to generate Excel file."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + except Exception as e: + return Response( + { + "status": "error", + "message": f"Error during file generation: {str(e)}", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + else: + print(f"Excel file already exists at: {file_path}") + + # Single file reading logic - only written once! + if os.path.exists(file_path): + try: + with open(file_path, "rb") as file: + response = HttpResponse( + file.read(), + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + response["Content-Disposition"] = f"attachment; filename={filename}" + return response + except Exception as e: + return Response( + {"status": "error", "message": f"Error reading file: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + else: + return Response( + {"status": "error", "message": "Failed to locate Excel file."}, + status=status.HTTP_404_NOT_FOUND, + ) + + +def generate_stats_excel_file(state, district, block): + """ + Deletes existing Excel layer file and forces regeneration. + Then returns the newly generated file. + """ + base_path = os.path.join(EXCEL_PATH, "data/stats_excel_files") + state_path = os.path.join(base_path, state.upper()) + district_path = os.path.join(state_path, district.upper()) + filename = f"{district}_{block}.xlsx" + file_path = os.path.join(district_path, filename) + + try: + # Create directory if it doesn't exist + output_dir = Path(file_path).parent + output_dir.mkdir(parents=True, exist_ok=True) + + # ALWAYS delete existing file if it exists + if os.path.exists(file_path): + os.remove(file_path) + + from .mws_indicators import generate_mws_data_for_kyl_filters + from .village_indicators import get_generate_filter_data_village + from public_api.views import get_tehsil_json + + get_vector_layer_geoserver(state, district, block) + get_tehsil_json(state, district, block, 1) + generate_mws_data_for_kyl_filters(state, district, block, "json", 1) + get_generate_filter_data_village(state, district, block, 1) + + if not os.path.exists(file_path): + return Response( + { + "status": "error", + "message": "Excel file generation completed but file not found.", + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + # Return the newly generated file + with open(file_path, "rb") as file: + response = HttpResponse( + file.read(), + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + response["Content-Disposition"] = f"attachment; filename={filename}" + return response + + except Exception as e: + return Response( + {"status": "error", "message": f"Failed to generate stats excel: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + +def add_sheets_to_excel(state, district, block, sheets): + try: + sheets_to_add = [sheet.strip() for sheet in sheets.split(",") if sheet.strip()] + results = [] + + for sheet in sheets_to_add: + r = get_vector_layer_geoserver(state, district, block, sheet) + results.append(r) + + from .mws_indicators import generate_mws_data_for_kyl_filters + from .village_indicators import get_generate_filter_data_village + + from public_api.views import get_tehsil_json + + get_tehsil_json(state, district, block, 1) + generate_mws_data_for_kyl_filters(state, district, block, "json", 1) + get_generate_filter_data_village(state, district, block, 1) + + successful = [r for r in results if r["status"] == "success"] + failed = [r for r in results if r["status"] == "failed"] + + response_data = { + "status": "success" if successful else "failed", + "message": f"Stats data added info. {len(successful)} successful, {len(failed)} failed.", + } + + return response_data + + except Exception as e: + return { + "status": "error", + "message": str(e), + } From 672d3968c6bfc06d490625a3348ed5561a2a4888 Mon Sep 17 00:00:00 2001 From: aman verma Date: Tue, 16 Jun 2026 15:49:49 +0000 Subject: [PATCH 35/38] rebase fix --- templates/resource-report.html | 2348 ++++++++++++++++---------------- 1 file changed, 1174 insertions(+), 1174 deletions(-) diff --git a/templates/resource-report.html b/templates/resource-report.html index d5deda26..09bb53e4 100644 --- a/templates/resource-report.html +++ b/templates/resource-report.html @@ -1,1175 +1,1175 @@ - - - - - - - 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. -

        -
        -
        - - - - - + + + + + + + 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 747a2b75e49923ba8b492074f81829fc5206de35 Mon Sep 17 00:00:00 2001 From: Aman Verma Date: Tue, 16 Jun 2026 21:51:24 +0530 Subject: [PATCH 36/38] drainage_density excel fix (#1023) --- stats_generator/utils.py | 96 ++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/stats_generator/utils.py b/stats_generator/utils.py index fd563c3c..43756108 100644 --- a/stats_generator/utils.py +++ b/stats_generator/utils.py @@ -49,10 +49,10 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): # Use append mode with if_sheet_exists='replace' with pd.ExcelWriter( - xlsx_file, - engine="openpyxl", - mode=mode, - if_sheet_exists="replace" if mode == "a" else None, + xlsx_file, + engine="openpyxl", + mode=mode, + if_sheet_exists="replace" if mode == "a" else None, ) as writer: for layer in fetch_layers_for_excel_generation(): workspace = layer["workspace"] @@ -88,13 +88,13 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): if workspace == "terrain": create_excel_for_terrain(geojson_data, xlsx_file, writer) elif ( - workspace == "terrain_lulc" - and layer_name == f"{district}_{block}_lulc_slope" + workspace == "terrain_lulc" + and layer_name == f"{district}_{block}_lulc_slope" ): create_excel_for_terrain_lulc_slope(geojson_data, xlsx_file, writer) elif ( - workspace == "terrain_lulc" - and layer_name == f"{district}_{block}_lulc_plain" + workspace == "terrain_lulc" + and layer_name == f"{district}_{block}_lulc_plain" ): create_excel_for_terrain_lulc_plain(geojson_data, xlsx_file, writer) elif workspace == "swb": @@ -140,8 +140,8 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): geojson_data, xlsx_file, writer, start_year, end_year ) elif ( - workspace == "mws_layers" - and layer_name == f"deltaG_well_depth_{district}_{block}" + workspace == "mws_layers" + and layer_name == f"deltaG_well_depth_{district}_{block}" ): parsed_data_annual_mws = parse_geojson_annual_mws(geojson_data) create_excel_annual_mws(parsed_data_annual_mws, xlsx_file, writer) @@ -152,8 +152,8 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): except Exception as e: print("Exception", str(e)) elif ( - workspace == "mws_layers" - and layer_name == f"deltaG_fortnight_{district}_{block}" + workspace == "mws_layers" + and layer_name == f"deltaG_fortnight_{district}_{block}" ): processed_data = [ process_feature(feature) for feature in geojson_data["features"] @@ -178,34 +178,34 @@ def get_vector_layer_geoserver(state, district, block, specific_sheets=None): elif workspace == "tree_overall_ch": create_excel_for_overall_tree_change(geojson_data, xlsx_file, writer) elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Afforestation" + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Afforestation" ): create_excel_chan_detection_afforestation( geojson_data, xlsx_file, writer ) elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_CropIntensity" + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_CropIntensity" ): create_excel_chan_detection_cropintensity( geojson_data, xlsx_file, writer ) elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Deforestation" + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Deforestation" ): create_excel_chan_detection_deforestation( geojson_data, xlsx_file, writer ) elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Degradation" + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Degradation" ): create_excel_chan_detection_degradation(geojson_data, xlsx_file, writer) elif ( - workspace == "change_detection" - and layer_name == f"change_vector_{district}_{block}_Urbanization" + workspace == "change_detection" + and layer_name == f"change_vector_{district}_{block}_Urbanization" ): create_excel_chan_detection_urbanization( geojson_data, xlsx_file, writer @@ -263,7 +263,7 @@ def create_excel_for_livestock(data, writer): df = pd.DataFrame(df_data) # Columns to exclude - exclude_cols = ["state_name","district_name","TEHSIL"] + exclude_cols = ["state_name", "district_name", "TEHSIL"] df = df.drop(columns=exclude_cols, errors="ignore") # Keep important columns first if they exist @@ -299,6 +299,7 @@ def create_excel_for_antyodaya_20(data, writer): def create_excel_for_drainage_density(data, writer): import ast + print("Inside create_excel_for Drainage Density") df_data = [] features = data["features"] @@ -308,9 +309,15 @@ def create_excel_for_drainage_density(data, writer): row = { "UID": properties.get("uid", ""), "area_in_ha": properties.get("area_in_ha", ""), - "drainage_density_in_km_per_km2": properties.get("drainage_density", ""), - "drainage_density_std_in_km_per_km2": properties.get("drainage_density_std", ""), - "stream_order_length_in_km": sum(ast.literal_eval(properties.get("stream_length_km", ""))), + "drainage_density_in_km_per_km2": properties.get( + "drainage_density_weighted", "" + ), + "drainage_density_std_in_km_per_km2": properties.get( + "drainage_density_std", "" + ), + "stream_order_length_in_km": sum( + ast.literal_eval(properties.get("stream_length_km", "")) + ), } df_data.append(row) @@ -518,14 +525,20 @@ def create_excel_for_facilities(data, writer): numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns df[numeric_cols] = df[numeric_cols].round(2) - exclude_cols = ["censuscode2011", "censusname", "district", "core_admin_uid", "shrid2", "state", "tehsil"] + exclude_cols = [ + "censuscode2011", + "censusname", + "district", + "core_admin_uid", + "shrid2", + "state", + "tehsil", + ] df.rename( columns={ - col: f"{col}_in_km" - for col in df.columns - if col not in exclude_cols + col: f"{col}_in_km" for col in df.columns if col not in exclude_cols }, - inplace=True + inplace=True, ) # Write to Excel @@ -534,7 +547,6 @@ def create_excel_for_facilities(data, writer): print("Excel file created for facilities_proximity") except Exception as e: print("facilities_proximity Layer not found :: ", e) - def create_excel_for_mws(data, writer): @@ -1449,7 +1461,7 @@ def calculate_area(base_area, percentage): parts = uid.split("_") num_uid_parts_is = [ - f"{parts[i]}_{parts[i+1]}" for i in range(0, len(parts) - 1, 2) + f"{parts[i]}_{parts[i + 1]}" for i in range(0, len(parts) - 1, 2) ] if len(parts) % 2 == 1: # Check for an unpaired last part num_uid_parts_is.append(parts[-1]) @@ -1461,7 +1473,7 @@ def calculate_area(base_area, percentage): row = {"UID": num_uid_part} for year in years: - short_year = f"{str(year)[-2:]}-{str(year+1)[-2:]}" + short_year = f"{str(year)[-2:]}-{str(year + 1)[-2:]}" # Construct keys dynamically using the shortened year format total_area_key = f"area_{short_year}" @@ -1476,16 +1488,16 @@ def calculate_area(base_area, percentage): zaid_percentage = properties.get(zaid_key, 0) # Calculate areas - row[f"total_area_in_ha_{year}-{year+1}"] = total_area / len( + row[f"total_area_in_ha_{year}-{year + 1}"] = total_area / len( num_uid_parts_is ) - row[f"kharif_area_in_ha_{year}-{year+1}"] = calculate_area( + row[f"kharif_area_in_ha_{year}-{year + 1}"] = calculate_area( total_area, kharif_percentage ) / len(num_uid_parts_is) - row[f"rabi_area_in_ha_{year}-{year+1}"] = calculate_area( + row[f"rabi_area_in_ha_{year}-{year + 1}"] = calculate_area( total_area, rabi_percentage ) / len(num_uid_parts_is) - row[f"zaid_area_in_ha_{year}-{year+1}"] = calculate_area( + row[f"zaid_area_in_ha_{year}-{year + 1}"] = calculate_area( total_area, zaid_percentage ) / len(num_uid_parts_is) @@ -1509,7 +1521,7 @@ def calculate_area(base_area, percentage): def create_excel_for_nrega_assets( - nrega_data, mws_data, output_file, writer, start_year, end_year + nrega_data, mws_data, output_file, writer, start_year, end_year ): workCategoryMapping = { "SWC - Landscape level impact": "Soil and water conservation", @@ -1613,7 +1625,7 @@ def create_excel_for_nrega_assets( def create_excel_village_nrega_assets( - result_df, output_file, writer, all_villages_df, start_year, end_year + result_df, output_file, writer, all_villages_df, start_year, end_year ): workCategoryMapping = { "SWC - Landscape level impact": "Soil and water conservation", @@ -1658,7 +1670,7 @@ def create_excel_village_nrega_assets( continue mask = (final_df["vill_id"] == row["vill_ID"]) & ( - final_df["vill_name"] == row["vill_name"] + final_df["vill_name"] == row["vill_name"] ) col_name = f"{category}_count_{year}" final_df.loc[mask, col_name] += 1 @@ -1678,7 +1690,7 @@ def create_excel_village_nrega_assets( def fetch_village_asset_count( - state, district, block, writer, output_file, start_year, end_year + state, district, block, writer, output_file, start_year, end_year ): # 1. Read village data village_gdf = gpd.read_file(get_url("panchayat_boundaries", f"{district}_{block}"))[ From 5477caabcded13e102687f31ac843bbc7caab682 Mon Sep 17 00:00:00 2001 From: Pawan Date: Tue, 9 Jun 2026 18:15:05 +0530 Subject: [PATCH 37/38] Embed google recaptcha --- .installation_state/django_migrations.done | 1 + .installation_state/env_file.done | 1 + .installation_state/superuser.done | 1 + nrm_app/settings.py | 872 ++++++++++----------- users/views.py | 41 +- 5 files changed, 473 insertions(+), 443 deletions(-) create mode 100644 .installation_state/django_migrations.done create mode 100644 .installation_state/env_file.done create mode 100644 .installation_state/superuser.done diff --git a/.installation_state/django_migrations.done b/.installation_state/django_migrations.done new file mode 100644 index 00000000..3a907cb4 --- /dev/null +++ b/.installation_state/django_migrations.done @@ -0,0 +1 @@ +2026-06-07T07:47:07Z diff --git a/.installation_state/env_file.done b/.installation_state/env_file.done new file mode 100644 index 00000000..7812f049 --- /dev/null +++ b/.installation_state/env_file.done @@ -0,0 +1 @@ +2026-06-07T07:36:50Z diff --git a/.installation_state/superuser.done b/.installation_state/superuser.done new file mode 100644 index 00000000..e04b301c --- /dev/null +++ b/.installation_state/superuser.done @@ -0,0 +1 @@ +2026-06-07T07:49:33Z diff --git a/nrm_app/settings.py b/nrm_app/settings.py index f08fe9d6..0c057de8 100755 --- a/nrm_app/settings.py +++ b/nrm_app/settings.py @@ -1,436 +1,436 @@ -""" -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") + +# 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="") +RECAPTCHA_SECRET_KEY = env("RECAPTCHA_SECRET_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/users/views.py b/users/views.py index 0949e77e..29a74bbe 100644 --- a/users/views.py +++ b/users/views.py @@ -1,4 +1,5 @@ import logging +import requests from django.conf import settings from django.contrib.auth.models import Group @@ -96,6 +97,18 @@ def post(self, request, *args, **kwargs): status=status.HTTP_201_CREATED, ) +def verify_recaptcha(token): + response = requests.post( + "https://www.google.com/recaptcha/api/siteverify", + data={ + "secret": settings.RECAPTCHA_SECRET_KEY, + "response": token, + }, + ) + + result = response.json() + + return result.get("success", False) class LoginView(TokenObtainPairView): """ @@ -107,16 +120,30 @@ class LoginView(TokenObtainPairView): def post(self, request, *args, **kwargs): # Call parent class method to validate credentials and get tokens - response = super().post(request, *args, **kwargs) + captcha_token = request.data.get("captcha") + + if not captcha_token: + return Response( + {"message": "Captcha is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if not verify_recaptcha(captcha_token): + return Response( + {"message": "Invalid captcha"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + response = super().post(request, *args, **kwargs) - token = response.data.get("access") - jwt_auth = JWTAuthentication() - validated_token = jwt_auth.get_validated_token(token) - user = jwt_auth.get_user(validated_token) + token = response.data.get("access") + jwt_auth = JWTAuthentication() + validated_token = jwt_auth.get_validated_token(token) + user = jwt_auth.get_user(validated_token) - response.data["user"] = UserSerializer(user).data + response.data["user"] = UserSerializer(user).data - return response + return response class LogoutView(generics.GenericAPIView): From 818c6e65597349cab236435c69503541596d0d75 Mon Sep 17 00:00:00 2001 From: Pawan Date: Tue, 16 Jun 2026 07:21:54 +0530 Subject: [PATCH 38/38] Added loggers and exception handling while logging with captcha --- users/views.py | 94 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/users/views.py b/users/views.py index 29a74bbe..9aa23653 100644 --- a/users/views.py +++ b/users/views.py @@ -98,17 +98,46 @@ def post(self, request, *args, **kwargs): ) def verify_recaptcha(token): - response = requests.post( - "https://www.google.com/recaptcha/api/siteverify", - data={ - "secret": settings.RECAPTCHA_SECRET_KEY, - "response": token, - }, - ) + try: + response = requests.post( + "https://www.google.com/recaptcha/api/siteverify", + data={ + "secret": settings.RECAPTCHA_SECRET_KEY, + "response": token, + }, + timeout=10, + ) + + response.raise_for_status() + + result = response.json() + success = result.get("success", False) + + if not success: + logger.warning( + "reCAPTCHA verification failed. Response: %s", + result, + ) + + return success - result = response.json() + except requests.exceptions.Timeout: + logger.error("reCAPTCHA request timed out") + return False - return result.get("success", False) + except requests.exceptions.RequestException as exc: + logger.exception( + "Error while verifying reCAPTCHA: %s", + str(exc), + ) + return False + + except Exception as exc: + logger.exception( + "Unexpected error during reCAPTCHA verification: %s", + str(exc), + ) + return False class LoginView(TokenObtainPairView): """ @@ -120,30 +149,55 @@ class LoginView(TokenObtainPairView): def post(self, request, *args, **kwargs): # Call parent class method to validate credentials and get tokens - captcha_token = request.data.get("captcha") + try: + captcha_token = request.data.get("captcha") - if not captcha_token: + if not captcha_token: + logger.warning( + "Login attempt without captcha. Username: %s", + request.data.get("username"), + ) return Response( {"message": "Captcha is required"}, status=status.HTTP_400_BAD_REQUEST, ) - if not verify_recaptcha(captcha_token): + if not verify_recaptcha(captcha_token): + logger.warning( + "Invalid captcha during login. Username: %s", + request.data.get("username"), + ) return Response( {"message": "Invalid captcha"}, status=status.HTTP_400_BAD_REQUEST, - ) + ) + + response = super().post(request, *args, **kwargs) - response = super().post(request, *args, **kwargs) + token = response.data.get("access") + jwt_auth = JWTAuthentication() + validated_token = jwt_auth.get_validated_token(token) + user = jwt_auth.get_user(validated_token) - token = response.data.get("access") - jwt_auth = JWTAuthentication() - validated_token = jwt_auth.get_validated_token(token) - user = jwt_auth.get_user(validated_token) + logger.info( + "User logged in successfully. User ID: %s Username: %s", + user.id, + user.username, + ) + + response.data["user"] = UserSerializer(user).data + return response - response.data["user"] = UserSerializer(user).data + except Exception as exc: + logger.exception( + "Unexpected error during login: %s", + str(exc), + ) - return response + return Response( + {"message": "An error occurred during login"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) class LogoutView(generics.GenericAPIView):