Skip to content

Commit a049643

Browse files
committed
Analytics_in_Fused_Copy: 2026_04_02_2025_after_edits
1 parent 1760ae7 commit a049643

File tree

17 files changed

+24
-28
lines changed

17 files changed

+24
-28
lines changed

public/Analytics_in_Fused/collection.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/Analytics_in_Fused/fetch_ndvi_monthly/fetch_ndvi_monthly.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def udf(
2727

2828
@fused.cache
2929
def search_items(bounds, date_range, max_cloud_cover):
30+
3031
items = catalog.search(
3132
collections=["sentinel-2-l2a"],
3233
bbox=bounds,
@@ -44,7 +45,7 @@ def search_items(bounds, date_range, max_cloud_cover):
4445

4546
# ── Load & compute NDVI (cached to avoid repeated Planetary Computer requests)
4647
@fused.cache
47-
def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution):
48+
def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution, items):
4849
ds = odc.stac.load(
4950
items,
5051
bands=["B08", "B04"],
@@ -68,7 +69,7 @@ def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution):
6869
arr = ndvi_composite.values.astype("float32")
6970
return np.clip(arr, 0, 1)
7071

71-
arr = load_ndvi_composite(tuple(bounds), date_range, max_cloud_cover, resolution)
72+
arr = load_ndvi_composite(tuple(bounds), date_range, max_cloud_cover, resolution, items)
7273
print(f"NDVI array shape: {arr.shape}")
7374
valid = arr[~np.isnan(arr)]
7475
print(f"Valid pixels: {len(valid)} / {arr.size} min={valid.min():.3f} max={valid.max():.3f} mean={valid.mean():.3f}")

public/Analytics_in_Fused/fetch_ndvi_monthly/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"fused:udfType": "auto"
5959
},
60-
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Monthly cloud-free NDVI composite for a given bounds using\n Sentinel-2 L2A from the Microsoft Planetary Computer.\n\n Returns a numpy array of mean NDVI (float32, values -1 to 1).\n \"\"\"\n import numpy as np\n import odc.stac\n import planetary_computer\n import pystac_client\n\n # \u2500\u2500 STAC search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n catalog = pystac_client.Client.open(\n \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n modifier=planetary_computer.sign_inplace,\n )\n\n date_range = f\"{year}-{month:02d}-01/{year}-{month:02d}-28\"\n\n @fused.cache\n def search_items(bounds, date_range, max_cloud_cover):\n items = catalog.search(\n collections=[\"sentinel-2-l2a\"],\n bbox=bounds,\n datetime=date_range,\n query={\"eo:cloud_cover\": {\"lt\": max_cloud_cover}},\n ).item_collection()\n print(f\"Found {len(items)} scenes for {date_range} (cloud < {max_cloud_cover}%)\")\n return items\n\n items = search_items(tuple(bounds), date_range, max_cloud_cover)\n\n if len(items) == 0:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return None\n\n # \u2500\u2500 Load & compute NDVI (cached to avoid repeated Planetary Computer requests)\n @fused.cache\n def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution):\n ds = odc.stac.load(\n items,\n bands=[\"B08\", \"B04\"],\n crs=\"EPSG:4326\",\n resolution=resolution / 111_320, # degrees per pixel (approx)\n bbox=list(bounds),\n groupby=\"solar_day\",\n )\n\n nir_raw = ds[\"B08\"].astype(\"float32\")\n red_raw = ds[\"B04\"].astype(\"float32\")\n\n nir_raw = nir_raw.where(nir_raw > 0)\n red_raw = red_raw.where(red_raw > 0)\n\n nir = nir_raw / 10_000.0\n red = red_raw / 10_000.0\n\n ndvi = (nir - red) / (nir + red + 1e-6)\n ndvi_composite = ndvi.median(dim=\"time\")\n arr = ndvi_composite.values.astype(\"float32\")\n return np.clip(arr, 0, 1)\n\n arr = load_ndvi_composite(tuple(bounds), date_range, max_cloud_cover, resolution)\n print(f\"NDVI array shape: {arr.shape}\")\n valid = arr[~np.isnan(arr)]\n print(f\"Valid pixels: {len(valid)} / {arr.size} min={valid.min():.3f} max={valid.max():.3f} mean={valid.mean():.3f}\")\n\n return arr\n",
60+
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Monthly cloud-free NDVI composite for a given bounds using\n Sentinel-2 L2A from the Microsoft Planetary Computer.\n\n Returns a numpy array of mean NDVI (float32, values -1 to 1).\n \"\"\"\n import numpy as np\n import odc.stac\n import planetary_computer\n import pystac_client\n\n # \u2500\u2500 STAC search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n catalog = pystac_client.Client.open(\n \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n modifier=planetary_computer.sign_inplace,\n )\n\n date_range = f\"{year}-{month:02d}-01/{year}-{month:02d}-28\"\n\n @fused.cache\n def search_items(bounds, date_range, max_cloud_cover):\n \n items = catalog.search(\n collections=[\"sentinel-2-l2a\"],\n bbox=bounds,\n datetime=date_range,\n query={\"eo:cloud_cover\": {\"lt\": max_cloud_cover}},\n ).item_collection()\n print(f\"Found {len(items)} scenes for {date_range} (cloud < {max_cloud_cover}%)\")\n return items\n\n items = search_items(tuple(bounds), date_range, max_cloud_cover)\n\n if len(items) == 0:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return None\n\n # \u2500\u2500 Load & compute NDVI (cached to avoid repeated Planetary Computer requests)\n @fused.cache\n def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution, items):\n ds = odc.stac.load(\n items,\n bands=[\"B08\", \"B04\"],\n crs=\"EPSG:4326\",\n resolution=resolution / 111_320, # degrees per pixel (approx)\n bbox=list(bounds),\n groupby=\"solar_day\",\n )\n\n nir_raw = ds[\"B08\"].astype(\"float32\")\n red_raw = ds[\"B04\"].astype(\"float32\")\n\n nir_raw = nir_raw.where(nir_raw > 0)\n red_raw = red_raw.where(red_raw > 0)\n\n nir = nir_raw / 10_000.0\n red = red_raw / 10_000.0\n\n ndvi = (nir - red) / (nir + red + 1e-6)\n ndvi_composite = ndvi.median(dim=\"time\")\n arr = ndvi_composite.values.astype(\"float32\")\n return np.clip(arr, 0, 1)\n\n arr = load_ndvi_composite(tuple(bounds), date_range, max_cloud_cover, resolution, items)\n print(f\"NDVI array shape: {arr.shape}\")\n valid = arr[~np.isnan(arr)]\n print(f\"Valid pixels: {len(valid)} / {arr.size} min={valid.min():.3f} max={valid.max():.3f} mean={valid.mean():.3f}\")\n\n return arr\n",
6161
"headers": []
6262
}
6363
}

public/Analytics_in_Fused/ndvi_map_2/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"fused:udfType": "auto"
5959
},
60-
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500,\n):\n import numpy as np\n map_utils = fused.load(\"https://github.com/fusedio/udfs/tree/6800334/community/milind/map_utils/\")\n\n # \u2500\u2500 Fetch NDVI array from the compute UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ndvi_udf = fused.load(\"fetch_ndvi_monthly\")\n ndvi_arr = ndvi_udf(\n bounds=bounds,\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_arr is None:\n return \"<p style='color:red'>No scenes found \u2014 try raising max_cloud_cover or changing month/year.</p>\"\n\n print(f\"NDVI array shape: {ndvi_arr.shape}, min={np.nanmin(ndvi_arr):.3f}, max={np.nanmax(ndvi_arr):.3f}\")\n\n # \u2500\u2500 Colour map: NDVI -0.2 \u2192 1.0 mapped to RGBA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n # Clamp to [-0.2, 1.0] then normalise to [0, 1]\n lo, hi = -0.2, 1.0\n norm = np.clip((ndvi_arr - lo) / (hi - lo), 0, 1) # (H, W)\n\n # Green colour ramp: brown (low) \u2192 yellow \u2192 bright green (high)\n r = np.interp(norm, [0, 0.3, 0.6, 1.0], [139, 210, 120, 34]).astype(np.uint8)\n g = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 90, 180, 200, 139]).astype(np.uint8)\n b = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 43, 60, 60, 34]).astype(np.uint8)\n a = np.where(np.isnan(ndvi_arr), 0, 220).astype(np.uint8)\n\n rgba = np.stack([r, g, b, a], axis=-1) # (H, W, 4)\n print(f\"RGBA image shape: {rgba.shape}\")\n\n return map_utils.deckgl_raster(\n image_data=rgba,\n bounds=list(bounds),\n basemap=\"light\",\n config={\n \"rasterLayer\": {\"opacity\": 0.85, \"pickable\": True},\n },\n )",
60+
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500,\n):\n import numpy as np\n map_utils = fused.load(\"https://github.com/fusedio/udfs/tree/6800334/community/milind/map_utils/\")\n\n # \u2500\u2500 Fetch NDVI array from the compute UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ndvi_udf = fused.load(\"fetch_ndvi_monthly\")\n ndvi_arr = ndvi_udf(\n bounds=bounds,\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_arr is None:\n return \"<p style='color:red'>No scenes found \u2014 try raising max_cloud_cover or changing month/year.</p>\"\n\n print(f\"NDVI array shape: {ndvi_arr.shape}, min={np.nanmin(ndvi_arr):.3f}, max={np.nanmax(ndvi_arr):.3f}\")\n\n # \u2500\u2500 Colour map: NDVI -0.2 \u2192 1.0 mapped to RGBA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n # Clamp to [-0.2, 1.0] then normalise to [0, 1]\n lo, hi = -0.2, 1.0\n norm = np.clip((ndvi_arr - lo) / (hi - lo), 0, 1) # (H, W)\n\n # Green colour ramp: brown (low) \u2192 yellow \u2192 bright green (high)\n r = np.interp(norm, [0, 0.3, 0.6, 1.0], [139, 210, 120, 34]).astype(np.uint8)\n g = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 90, 180, 200, 139]).astype(np.uint8)\n b = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 43, 60, 60, 34]).astype(np.uint8)\n a = np.where(np.isnan(ndvi_arr), 0, 220).astype(np.uint8)\n\n rgba = np.stack([r, g, b, a], axis=-1) # (H, W, 4)\n print(f\"RGBA image shape: {rgba.shape}\")\n\n return map_utils.deckgl_raster(\n image_data=rgba,\n bounds=list(bounds),\n basemap=\"light\",\n config={\n \"rasterLayer\": {\"opacity\": 0.85, \"pickable\": True},\n },\n )\n",
6161
"headers": []
6262
}
6363
}

public/Analytics_in_Fused/ndvi_map_2/ndvi_map_2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ def udf(
4545
config={
4646
"rasterLayer": {"opacity": 0.85, "pickable": True},
4747
},
48-
)
48+
)

public/Analytics_in_Fused/ndvi_monthly_mean/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"fused:udfType": "auto"
5959
},
60-
"code": "@fused.udf\ndef udf(\n state_fips: str = \"53\", # San Juan County, WA\n county_fips: str = \"055\",\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Input: US county (state_fips + county_fips) + month/year\n Output: single-row DataFrame with mean NDVI clipped to the exact county geometry\n\n Steps:\n 1. Load county geometry from us_counties_from_api UDF\n 2. Fetch Sentinel-2 NDVI composite for the county bbox\n 3. Clip raster to exact county polygon mask\n 4. Return mean NDVI across valid pixels inside the county\n \"\"\"\n import numpy as np\n import geopandas as gpd\n import pandas as pd\n from rasterio.transform import from_bounds\n from rasterio.features import geometry_mask\n\n # \u2500\u2500 1. Load county geometry from us_counties_from_api \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n counties_udf = fused.load(\"us_counties_from_api\")\n county = counties_udf(state_fips=state_fips, county_fips=county_fips)\n\n geom = county.geometry.iloc[0]\n bounds = county.total_bounds # [minx, miny, maxx, maxy]\n print(f\"County bounds: {bounds}\")\n print(f\"County geometry type: {geom.geom_type}\")\n\n # \u2500\u2500 2. Fetch NDVI composite via fetch_ndvi_monthly UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n fetch_ndvi = fused.load(\"fetch_ndvi_monthly\")\n ndvi_composite = fetch_ndvi(\n bounds=list(bounds),\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_composite is None:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return pd.DataFrame({\"state_fips\": [state_fips], \"county_fips\": [county_fips],\n \"year\": [year], \"month\": [month],\n \"mean_ndvi\": [None], \"min_ndvi\": [None], \"max_ndvi\": [None]})\n\n print(f\"NDVI composite shape: {ndvi_composite.shape}\")\n\n # \u2500\u2500 3. Clip to exact county polygon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n h, w = ndvi_composite.shape\n transform = from_bounds(*bounds, width=w, height=h)\n\n # geometry_mask returns True OUTSIDE the geometry \u2014 invert it\n poly_mask = geometry_mask(\n [geom.__geo_interface__],\n transform=transform,\n invert=True, # True = inside county\n out_shape=(h, w),\n )\n\n clipped = ndvi_composite.copy()\n clipped[~poly_mask] = np.nan # zero out pixels outside county\n\n # \u2500\u2500 4. Return mean NDVI inside county \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n valid = clipped[~np.isnan(clipped)]\n mean_ndvi = float(np.mean(valid)) if len(valid) > 0 else None\n min_ndvi = float(np.min(valid)) if len(valid) > 0 else None\n max_ndvi = float(np.max(valid)) if len(valid) > 0 else None\n pct_valid = len(valid) / clipped.size * 100\n\n print(f\"Pixels inside county: {len(valid)} / {clipped.size} ({pct_valid:.1f}%)\")\n print(f\"Mean NDVI: {mean_ndvi:.4f}, Min: {min_ndvi:.4f}, Max: {max_ndvi:.4f}\")\n\n return pd.DataFrame({\n \"state_fips\": [state_fips],\n \"county_fips\": [county_fips],\n \"year\": [year],\n \"month\": [month],\n \"mean_ndvi\": [round(mean_ndvi, 4)],\n \"min_ndvi\": [round(min_ndvi, 4)],\n \"max_ndvi\": [round(max_ndvi, 4)],\n \"valid_pixels\":[len(valid)],\n \"total_pixels\":[clipped.size],\n })\n",
60+
"code": "@fused.udf\ndef udf(\n state_fips: str = \"53\", # San Juan County, WA\n county_fips: str = \"055\",\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Input: US county (state_fips + county_fips) + month/year\n Output: single-row DataFrame with mean NDVI clipped to the exact county geometry\n\n Steps:\n 1. Load county geometry from us_counties_from_api UDF\n 2. Fetch Sentinel-2 NDVI composite for the county bbox\n 3. Clip raster to exact county polygon mask\n 4. Return mean NDVI across valid pixels inside the county\n \"\"\"\n import numpy as np\n import geopandas as gpd\n import pandas as pd\n from rasterio.transform import from_bounds\n from rasterio.features import geometry_mask\n\n # \u2500\u2500 1. Load county geometry from us_counties_from_api \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n counties_udf = fused.load(\"us_counties_from_api\")\n county = counties_udf(state_fips=state_fips, county_fips=county_fips)\n\n geom = county.geometry.iloc[0]\n bounds = county.total_bounds # [minx, miny, maxx, maxy]\n print(f\"County bounds: {bounds}\")\n print(f\"County geometry type: {geom.geom_type}\")\n\n # \u2500\u2500 2. Fetch NDVI composite via fetch_ndvi_monthly UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n fetch_ndvi = fused.load(\"fetch_ndvi_monthly\")\n ndvi_composite = fetch_ndvi(\n bounds=list(bounds),\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_composite is None:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return pd.DataFrame({\"state_fips\": [state_fips], \"county_fips\": [county_fips],\n \"year\": [year], \"month\": [month],\n \"mean_ndvi\": [None], \"min_ndvi\": [None], \"max_ndvi\": [None]})\n\n print(f\"NDVI composite shape: {ndvi_composite.shape}\")\n\n # \u2500\u2500 3. Clip to exact county polygon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n h, w = ndvi_composite.shape\n transform = from_bounds(*bounds, width=w, height=h)\n\n # geometry_mask returns True OUTSIDE the geometry \u2014 invert it\n poly_mask = geometry_mask(\n [geom.__geo_interface__],\n transform=transform,\n invert=True, # True = inside county\n out_shape=(h, w),\n )\n\n clipped = ndvi_composite.copy()\n clipped[~poly_mask] = np.nan # mask out pixels outside county\n\n # \u2500\u2500 4. Return mean NDVI inside county \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n valid = clipped[~np.isnan(clipped)]\n mean_ndvi = float(np.mean(valid)) if len(valid) > 0 else None\n min_ndvi = float(np.min(valid)) if len(valid) > 0 else None\n max_ndvi = float(np.max(valid)) if len(valid) > 0 else None\n pct_valid = len(valid) / clipped.size * 100\n\n print(f\"Pixels inside county: {len(valid)} / {clipped.size} ({pct_valid:.1f}%)\")\n print(f\"Mean NDVI: {mean_ndvi:.4f}, Min: {min_ndvi:.4f}, Max: {max_ndvi:.4f}\")\n\n return pd.DataFrame({\n \"state_fips\": [state_fips],\n \"county_fips\": [county_fips],\n \"year\": [year],\n \"month\": [month],\n \"mean_ndvi\": [round(mean_ndvi, 4)],\n \"min_ndvi\": [round(min_ndvi, 4)],\n \"max_ndvi\": [round(max_ndvi, 4)],\n \"valid_pixels\":[len(valid)],\n \"total_pixels\":[clipped.size],\n })\n",
6161
"headers": []
6262
}
6363
}

0 commit comments

Comments
 (0)