ecological-agent-skills 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/AGENT_CONTEXT.md +191 -0
  2. package/CATALOG.md +329 -0
  3. package/LICENSE +692 -0
  4. package/README.md +347 -0
  5. package/bin/install.mjs +168 -0
  6. package/docs/comparison-with-alternatives.md +38 -0
  7. package/docs/global-examples-index.md +103 -0
  8. package/docs/repository-statistics.md +101 -0
  9. package/docs/theoretical-foundations.md +188 -0
  10. package/environment.yaml +106 -0
  11. package/examples/community/arctic_tundra_vegetation_example.md +247 -0
  12. package/examples/community/bird_landuse_example.md +63 -0
  13. package/examples/community/phytoplankton_reservoir_example.md +60 -0
  14. package/examples/community/reef_fish_indopacific_example.md +221 -0
  15. package/examples/impact/baci_road_example.md +57 -0
  16. package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
  17. package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
  18. package/examples/occupancy/puma_camera_example.md +61 -0
  19. package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
  20. package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
  21. package/examples/sdm/anteater_cerrado_example.md +69 -0
  22. package/examples/sdm/jaguar_amazon_example.md +80 -0
  23. package/examples/sdm/koala_climate_change_example.md +170 -0
  24. package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
  25. package/package.json +43 -0
  26. package/renv.lock +194 -0
  27. package/skills/SKILL_INDEX.json +1020 -0
  28. package/skills/acoustic-monitoring/SKILL.md +163 -0
  29. package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
  30. package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
  31. package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
  32. package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
  33. package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
  34. package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
  35. package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
  36. package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
  37. package/skills/biostatistics-workbench/SKILL.md +140 -0
  38. package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
  39. package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
  40. package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
  41. package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
  42. package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
  43. package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
  44. package/skills/camera-trap-processing/SKILL.md +159 -0
  45. package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
  46. package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
  47. package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
  48. package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
  49. package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
  50. package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
  51. package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
  52. package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
  53. package/skills/community-ecology-ordination/SKILL.md +133 -0
  54. package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
  55. package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
  56. package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
  57. package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
  58. package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
  59. package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
  60. package/skills/ecological-data-foundation/SKILL.md +129 -0
  61. package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
  62. package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
  63. package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
  64. package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
  65. package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
  66. package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
  67. package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
  68. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
  69. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
  70. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
  71. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
  72. package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
  73. package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
  74. package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
  75. package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
  76. package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
  77. package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
  78. package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
  79. package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
  80. package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
  81. package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
  82. package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
  83. package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
  84. package/skills/ecological-impact-assessment/SKILL.md +123 -0
  85. package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
  86. package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
  87. package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
  88. package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
  89. package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
  90. package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
  91. package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
  92. package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
  93. package/skills/ecosystem-services-assessment/SKILL.md +125 -0
  94. package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
  95. package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
  96. package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
  97. package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
  98. package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
  99. package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
  100. package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
  101. package/skills/environmental-time-series/SKILL.md +125 -0
  102. package/skills/environmental-time-series/examples/example-prompts.md +33 -0
  103. package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
  104. package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
  105. package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
  106. package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
  107. package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
  108. package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
  109. package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
  110. package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
  111. package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
  112. package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
  113. package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
  114. package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
  115. package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
  116. package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
  117. package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
  118. package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
  119. package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
  120. package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
  121. package/skills/landscape-connectivity/SKILL.md +170 -0
  122. package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
  123. package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
  124. package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
  125. package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
  126. package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
  127. package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
  128. package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
  129. package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
  130. package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
  131. package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
  132. package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
  133. package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
  134. package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
  135. package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
  136. package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
  137. package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
  138. package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
  139. package/skills/occupancy-and-detection/SKILL.md +126 -0
  140. package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
  141. package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
  142. package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
  143. package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
  144. package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
  145. package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
  146. package/skills/population-viability-analysis/SKILL.md +161 -0
  147. package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
  148. package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
  149. package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
  150. package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
  151. package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
  152. package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
  153. package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
  154. package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
  155. package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
  156. package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
  157. package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
  158. package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
  159. package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
  160. package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
  161. package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
  162. package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
  163. package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
  164. package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
  165. package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
  166. package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
  167. package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
  168. package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
  169. package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
  170. package/skills/spatial-prioritization/SKILL.md +162 -0
  171. package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
  172. package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
  173. package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
  174. package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
  175. package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
  176. package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
  177. package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
  178. package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
  179. package/skills/species-distribution-modeling/SKILL.md +139 -0
  180. package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
  181. package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
  182. package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
  183. package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
  184. package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
  185. package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
  186. package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
  187. package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
  188. package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
  189. package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
  190. package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
  191. package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
  192. package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
  193. package/templates/SKILL_TEMPLATE.md +225 -0
  194. package/templates/checklists/data-submission-checklist.md +38 -0
  195. package/templates/checklists/post-analysis-checklist.md +55 -0
  196. package/templates/checklists/pre-analysis-checklist.md +31 -0
  197. package/templates/prompts/debug-skill.md +47 -0
  198. package/templates/prompts/invoke-skill.md +34 -0
  199. package/templates/prompts/invoke-workflow.md +45 -0
  200. package/templates/reports/technical-report-template.md +80 -0
  201. package/templates/scripts/logger_setup.R +79 -0
  202. package/templates/scripts/logger_setup.py +119 -0
  203. package/templates/scripts/params_loader.R +28 -0
  204. package/templates/scripts/params_loader.py +38 -0
  205. package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
  206. package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
  207. package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
  208. package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
  209. package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
  210. package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
  211. package/workflows/produce-technical-report/WORKFLOW.md +113 -0
  212. package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
  213. package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
  214. package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
  215. package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
  216. package/workflows/run-population-viability/WORKFLOW.md +90 -0
  217. package/workflows/run-sdm-study/WORKFLOW.md +99 -0
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env python3
2
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ """
6
+ compute_es.py
7
+ Compute basic ecosystem service indicators from land cover + biophysical data.
8
+ Usage: python compute_es.py <landcover_tif> <carbon_pools_csv> <output_dir>
9
+ Services: carbon storage, erosion control (RUSLE C-factor), pollination habitat index
10
+ Requires: rasterio, numpy, pandas, geopandas
11
+ """
12
+ import logging
13
+ import sys
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+
17
+ SKILL_NAME = "ecosystem-services-assessment"
18
+ _LOG_DIR = Path("logs")
19
+ _LOG_DIR.mkdir(parents=True, exist_ok=True)
20
+ _log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
21
+ logging.basicConfig(
22
+ level=logging.INFO,
23
+ format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
24
+ datefmt="%Y-%m-%d %H:%M:%S",
25
+ handlers=[
26
+ logging.StreamHandler(sys.stdout),
27
+ logging.FileHandler(_log_file, encoding="utf-8"),
28
+ ],
29
+ )
30
+ logger = logging.getLogger(SKILL_NAME)
31
+
32
+ def log_step(n: int, desc: str) -> None:
33
+ logger.info("-- STEP %d: %s", n, desc)
34
+
35
+ def log_decision(var: str, val, why: str) -> None:
36
+ logger.info("DECISION | %s = %s | %s", var, val, why)
37
+
38
+ import numpy as np
39
+ import pandas as pd
40
+ import rasterio
41
+ from rasterio.transform import rowcol
42
+
43
+
44
+ def load_raster(path: str) -> tuple:
45
+ with rasterio.open(path) as src:
46
+ data = src.read(1).astype(float)
47
+ meta = src.meta.copy()
48
+ return data, meta
49
+
50
+ def zonal_summary(lc: np.ndarray, es_layer: np.ndarray,
51
+ class_codes: list, class_names: dict) -> pd.DataFrame:
52
+ rows = []
53
+ for code in class_codes:
54
+ mask = lc == code
55
+ if mask.sum() > 0:
56
+ rows.append({"lulc_code": code,
57
+ "lulc_name": class_names.get(code, str(code)),
58
+ "n_pixels": int(mask.sum()),
59
+ "mean_es": round(float(np.nanmean(es_layer[mask])), 4),
60
+ "total_es": round(float(np.nansum(es_layer[mask])), 2)})
61
+ return pd.DataFrame(rows).sort_values("lulc_code")
62
+
63
+ def main():
64
+ lc_file = sys.argv[1] if len(sys.argv) > 1 else "data/landcover.tif"
65
+ carbon_file = sys.argv[2] if len(sys.argv) > 2 else "data/carbon_pools.csv"
66
+ output_dir = Path(sys.argv[3]) if len(sys.argv) > 3 else Path("outputs/ecosystem_services")
67
+ output_dir.mkdir(parents=True, exist_ok=True)
68
+
69
+ log_decision("lc_file", lc_file, "Input land cover raster")
70
+ log_decision("carbon_file", carbon_file, "Carbon pools lookup CSV")
71
+ log_decision("output_dir", str(output_dir), "Directory for ecosystem service outputs")
72
+
73
+ if not Path(lc_file).exists():
74
+ logger.error(
75
+ "Input nao encontrado: %s\n"
76
+ " Causa provavel: passo anterior nao concluiu.\n"
77
+ " Skill anterior que deveria ter produzido este input: geoprocessing-for-ecology",
78
+ lc_file
79
+ )
80
+ sys.exit(1)
81
+
82
+ if not Path(carbon_file).exists():
83
+ logger.error(
84
+ "Input nao encontrado: %s\n"
85
+ " Causa provavel: passo anterior nao concluiu.\n"
86
+ " Skill anterior que deveria ter produzido este input: reproducible-ecology-pipeline",
87
+ carbon_file
88
+ )
89
+ sys.exit(1)
90
+
91
+ try:
92
+ log_step(1, "Loading land cover raster")
93
+ lc, meta = load_raster(lc_file)
94
+ n_classes = len(np.unique(lc[~np.isnan(lc)]))
95
+ logger.info("Land cover raster: %s | Classes: %d", lc.shape, n_classes)
96
+
97
+ log_step(2, "Loading and validating carbon pools CSV")
98
+ carbon_df = pd.read_csv(carbon_file)
99
+ required_cols = ["lucode", "C_above", "C_below", "C_soil", "C_dead"]
100
+ missing = [c for c in required_cols if c not in carbon_df.columns]
101
+ if missing:
102
+ logger.warning(
103
+ "Carbon pools CSV missing columns: %s. Using zeros for missing fields.", missing
104
+ )
105
+ for c in missing:
106
+ carbon_df[c] = 0
107
+
108
+ # ── 1. Carbon storage ──────────────────────────────────────────────────
109
+ log_step(3, "Computing carbon storage layer")
110
+ carbon_map = np.full(lc.shape, np.nan)
111
+ class_names = {}
112
+ for _, row in carbon_df.iterrows():
113
+ mask = lc == row["lucode"]
114
+ total_c = row["C_above"] + row["C_below"] + row["C_soil"] + row["C_dead"]
115
+ carbon_map[mask] = total_c
116
+ if "LULC_name" in carbon_df.columns:
117
+ class_names[int(row["lucode"])] = row["LULC_name"]
118
+
119
+ carbon_out = output_dir / "carbon_storage_MgCha.tif"
120
+ with rasterio.open(carbon_out, "w", **meta) as dst:
121
+ dst.write(carbon_map.astype(np.float32), 1)
122
+ logger.info("Carbon: mean = %.1f MgC/ha | Output: %s", np.nanmean(carbon_map), carbon_out)
123
+
124
+ # ── 2. RUSLE C-factor (erosion control proxy) ─────────────────────────
125
+ log_step(4, "Computing RUSLE C-factor erosion control layer")
126
+ # Default C-factors (add your own mapping)
127
+ c_factor_defaults = {1: 0.001, 2: 0.005, 3: 0.01, 4: 0.15, 5: 0.30, 6: 1.0}
128
+ log_decision("c_factor_defaults", c_factor_defaults,
129
+ "Default RUSLE C-factors per LULC class; replace with site-specific values")
130
+ c_map = np.full(lc.shape, np.nan)
131
+ for code, c_val in c_factor_defaults.items():
132
+ c_map[lc == code] = c_val
133
+ # Erosion control ES = avoided erosion = (1 - C_factor); higher = better service
134
+ erosion_control = 1.0 - c_map
135
+ unmapped = np.sum(np.isnan(c_map) & ~np.isnan(lc))
136
+ if unmapped > 0:
137
+ logger.warning(
138
+ "%d pixels have no C-factor mapping (not in c_factor_defaults). "
139
+ "Erosion control will be NaN for those pixels.",
140
+ unmapped
141
+ )
142
+ erosion_out = output_dir / "erosion_control_index.tif"
143
+ with rasterio.open(erosion_out, "w", **meta) as dst:
144
+ dst.write(erosion_control.astype(np.float32), 1)
145
+ logger.info("Erosion control layer written: %s", erosion_out)
146
+
147
+ # ── 3. Pollination habitat index ─────────────────────────────────────
148
+ log_step(5, "Computing pollination habitat index layer")
149
+ # Natural / semi-natural = high value (1); crops = partial (0.5); urban/bare = 0
150
+ pollination_suitability = {1: 1.0, 2: 0.8, 3: 0.7, 4: 0.3, 5: 0.1, 6: 0.0}
151
+ log_decision("pollination_suitability", pollination_suitability,
152
+ "Expert-based pollination suitability scores per LULC class")
153
+ poll_map = np.full(lc.shape, np.nan)
154
+ for code, val in pollination_suitability.items():
155
+ poll_map[lc == code] = val
156
+ poll_out = output_dir / "pollination_habitat.tif"
157
+ with rasterio.open(poll_out, "w", **meta) as dst:
158
+ dst.write(poll_map.astype(np.float32), 1)
159
+ logger.info("Pollination habitat layer written: %s", poll_out)
160
+
161
+ # ── Summary table ─────────────────────────────────────────────────────
162
+ log_step(6, "Building zonal summary table")
163
+ class_codes = [int(c) for c in np.unique(lc[~np.isnan(lc)])]
164
+ summary = zonal_summary(lc, carbon_map, class_codes, class_names)
165
+ summary["erosion_control_mean"] = [
166
+ round(float(np.nanmean(erosion_control[lc == c])), 4)
167
+ if np.any(lc == c) else np.nan for c in class_codes]
168
+ summary["pollination_mean"] = [
169
+ round(float(np.nanmean(poll_map[lc == c])), 4)
170
+ if np.any(lc == c) else np.nan for c in class_codes]
171
+ summary_out = output_dir / "es_summary_table.csv"
172
+ summary.to_csv(summary_out, index=False)
173
+ logger.info("ES summary:\n%s", summary.to_string(index=False))
174
+ logger.info("Outputs written to: %s", output_dir)
175
+
176
+ except FileNotFoundError as e:
177
+ logger.error(
178
+ "Input file not found: %s\n"
179
+ " Expected output from: geoprocessing-for-ecology\n"
180
+ " Check that previous step completed.",
181
+ e
182
+ )
183
+ raise
184
+ except Exception as e:
185
+ logger.error("Unexpected error in ecosystem services assessment: %s", e)
186
+ raise
187
+
188
+ if __name__ == "__main__":
189
+ main()
@@ -0,0 +1,161 @@
1
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ # Usage: Rscript tradeoff_analysis.R <es_summary_table.csv> <output_dir>
5
+ # ES trade-off and synergy analysis across pixels or land cover units
6
+ # Usage: Rscript tradeoff_analysis.R <es_summary_csv> <output_dir>
7
+ # Requires: dplyr, ggplot2, corrplot, tidyr
8
+
9
+ # ── Inline logger ─────────────────────────────────────────────────────────────
10
+ SKILL_NAME <- "ecosystem-services-assessment"
11
+ .log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
12
+ log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
13
+ log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
14
+ log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
15
+ log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
16
+ log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
17
+ dir.create("logs", recursive=TRUE, showWarnings=FALSE)
18
+
19
+ suppressPackageStartupMessages({
20
+ library(dplyr)
21
+ library(ggplot2)
22
+ library(corrplot)
23
+ library(tidyr)
24
+ })
25
+
26
+ args <- commandArgs(trailingOnly = TRUE)
27
+ es_file <- ifelse(length(args) >= 1, args[1], "outputs/ecosystem_services/es_summary_table.csv")
28
+ output_dir <- ifelse(length(args) >= 2, args[2], "outputs/ecosystem_services")
29
+ dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
30
+
31
+ log_info("Skill: %s | es_file=%s | output_dir=%s", SKILL_NAME, es_file, output_dir)
32
+
33
+ # ── Input precondition check ──────────────────────────────────────────────────
34
+ if (!file.exists(es_file)) {
35
+ log_error(
36
+ "Input nao encontrado: %s\nCausa provavel: o script de quantificacao de servicos ecossistemicos nao foi executado ou o caminho esta errado.\nVerifique: execute primeiro o script de mapeamento/quantificacao de ES.\nSkill anterior: ecosystem-services-assessment (quantification step).",
37
+ es_file
38
+ )
39
+ stop("Missing: ", es_file)
40
+ }
41
+
42
+ log_step(1, "Carregar tabela de servicos ecossistemicos")
43
+ es <- tryCatch({
44
+ read.csv(es_file)
45
+ }, error = function(e) {
46
+ log_error(
47
+ "Falha ao ler CSV de servicos ecossistemicos: %s\nCausa provavel: arquivo corrompido ou com separador incorreto.\nVerifique: abra o arquivo em editor de texto e confira o formato.\nSkill anterior: ecosystem-services-assessment (quantification step).",
48
+ conditionMessage(e)
49
+ )
50
+ stop(e)
51
+ })
52
+
53
+ log_info("Tabela de ES carregada: %d classes de uso do solo", nrow(es))
54
+
55
+ n_na_total <- sum(is.na(es))
56
+ if (n_na_total > 0) {
57
+ log_warn("Tabela de ES contem %d valores NA no total — correlacoes serao calculadas com 'complete.obs'.", n_na_total)
58
+ }
59
+
60
+ # ── Identify numeric ES columns ───────────────────────────────────────────────
61
+ log_step(2, "Identificar colunas de indicadores de ES e normalizar 0-1")
62
+ es_cols <- names(es)[sapply(es, is.numeric) & !names(es) %in% c("lulc_code", "n_pixels")]
63
+ log_info("Indicadores de ES: %s", paste(es_cols, collapse = ", "))
64
+ log_decision("es_cols", paste(es_cols, collapse = ", "),
65
+ "colunas numericas excluindo lulc_code e n_pixels sao tratadas como indicadores de ES")
66
+
67
+ if (length(es_cols) < 2) {
68
+ log_warn("Menos de 2 indicadores de ES encontrados — analise de trade-off nao e possivel com apenas %d coluna(s).", length(es_cols))
69
+ log_info("Encerrando sem erro. Adicione mais indicadores ao CSV de entrada.")
70
+ quit(status = 0)
71
+ }
72
+
73
+ # ── Normalise to 0-1 ──────────────────────────────────────────────────────────
74
+ es_norm <- tryCatch({
75
+ es |>
76
+ mutate(across(all_of(es_cols),
77
+ ~ (. - min(., na.rm=TRUE)) / (max(., na.rm=TRUE) - min(., na.rm=TRUE) + 1e-10)))
78
+ }, error = function(e) {
79
+ log_error(
80
+ "Falha na normalizacao 0-1 dos indicadores: %s\nCausa provavel: colunas nao numericas identificadas incorretamente.\nVerifique: os tipos de coluna no CSV de entrada.\nSkill anterior: ecosystem-services-assessment (quantification step).",
81
+ conditionMessage(e)
82
+ )
83
+ stop(e)
84
+ })
85
+
86
+ log_decision("normalization", "min-max [0,1] com epsilon 1e-10",
87
+ "evita divisao por zero quando todos os valores de um indicador sao iguais")
88
+
89
+ # ── Correlation matrix (Spearman) ─────────────────────────────────────────────
90
+ log_step(3, "Calcular matriz de correlacao de Spearman entre indicadores de ES")
91
+ cor_mat <- tryCatch({
92
+ cor(es_norm[es_cols], method = "spearman", use = "complete.obs")
93
+ }, error = function(e) {
94
+ log_error(
95
+ "Falha ao calcular matriz de correlacao: %s\nCausa provavel: todos os valores de alguma coluna sao NA apos normalizacao.\nVerifique: presenca de variacao nos indicadores de ES.\nSkill anterior: ecosystem-services-assessment (quantification step).",
96
+ conditionMessage(e)
97
+ )
98
+ stop(e)
99
+ })
100
+
101
+ log_decision("correlation_method", "Spearman",
102
+ "metodo nao-parametrico robusto a distribuicoes assimetricas comuns em dados de ES")
103
+
104
+ tryCatch({
105
+ write.csv(as.data.frame(cor_mat), file.path(output_dir, "tradeoff_matrix.csv"))
106
+ log_info("tradeoff_matrix.csv salvo em: %s", output_dir)
107
+ }, error = function(e) {
108
+ log_error(
109
+ "Falha ao salvar tradeoff_matrix.csv: %s\nCausa provavel: permissao negada ou disco cheio.\nVerifique: permissoes do diretorio de saida.\nSkill anterior: nenhuma.",
110
+ conditionMessage(e)
111
+ )
112
+ stop(e)
113
+ })
114
+
115
+ # ── Correlation heatmap ────────────────────────────────────────────────────────
116
+ log_step(4, "Gerar heatmap de trade-offs (corrplot)")
117
+ tryCatch({
118
+ png(file.path(output_dir, "tradeoff_heatmap.png"), width = 800, height = 700, res = 150)
119
+ corrplot(cor_mat, method = "color", type = "upper", tl.cex = 0.8,
120
+ addCoef.col = "black", number.cex = 0.7, cl.cex = 0.7,
121
+ title = "ES Trade-offs (Spearman r)", mar = c(0, 0, 2, 0))
122
+ dev.off()
123
+ log_info("tradeoff_heatmap.png salvo em: %s", output_dir)
124
+ }, error = function(e) {
125
+ log_error(
126
+ "Falha ao gerar heatmap de trade-offs: %s\nCausa provavel: corrplot nao instalado ou matriz de correlacao invalida.\nVerifique: se o pacote corrplot esta disponivel e a matriz tem pelo menos 2 variaveis.\nSkill anterior: nenhuma.",
127
+ conditionMessage(e)
128
+ )
129
+ stop(e)
130
+ })
131
+
132
+ # ── Scatter plots for top pairs ───────────────────────────────────────────────
133
+ log_step(5, "Gerar graficos de dispersao para pares de indicadores de ES")
134
+ pair_combos <- combn(es_cols, 2, simplify = FALSE)
135
+ n_pairs <- min(6, length(pair_combos))
136
+ log_info("Gerando %d graficos de dispersao (de %d pares possiveis)", n_pairs, length(pair_combos))
137
+ log_decision("max_scatter_plots", as.character(n_pairs),
138
+ "limitado a 6 pares para evitar geracao excessiva de arquivos")
139
+
140
+ for (pr in pair_combos[seq_len(n_pairs)]) {
141
+ tryCatch({
142
+ p <- ggplot(es |> mutate(label = lulc_code),
143
+ aes(x = .data[[pr[1]]], y = .data[[pr[2]]], label = label)) +
144
+ geom_point(size = 3, colour = "#2166ac") +
145
+ ggrepel::geom_text_repel(size = 2.5, max.overlaps = 10) +
146
+ labs(x = pr[1], y = pr[2],
147
+ title = paste("Trade-off:", pr[1], "vs", pr[2])) +
148
+ theme_bw()
149
+ fname <- paste0("scatter_", pr[1], "_vs_", pr[2], ".png")
150
+ ggsave(file.path(output_dir, fname), p, width = 5, height = 4, dpi = 150)
151
+ log_info("Salvo: %s", fname)
152
+ }, error = function(e) {
153
+ log_error(
154
+ "Falha ao gerar grafico de dispersao para par %s vs %s: %s\nCausa provavel: coluna ausente apos filtragem ou problema com ggrepel.\nVerifique: se as colunas '%s' e '%s' existem e possuem dados validos.\nSkill anterior: nenhuma.",
155
+ pr[1], pr[2], conditionMessage(e), pr[1], pr[2]
156
+ )
157
+ stop(e)
158
+ })
159
+ }
160
+
161
+ log_info("Analise de trade-off concluida. Saidas em: %s", output_dir)
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: environmental-time-series
3
+ description: "Detects trends, breakpoints, and recovery trajectories in environmental time series data from remote sensing or field measurements. Use this skill when the user mentions time series analysis, NDVI/EVI/LST trends, Mann-Kendall tests, Sen slope, BFAST breakpoints, structural change detection, seasonal decomposition (STL), anomaly detection, recovery trajectories, regime shifts, or pixel-wise trend analysis."
4
+ skill_version: 1.0.0
5
+ ---
6
+
7
+ # Skill: environmental-time-series
8
+
9
+ **Domain:** Trend · Seasonality · Breakpoints · Anomalies · Recovery
10
+ **Phase:** 2 — Modeling
11
+ **Used by:** build-fire-risk-map, analyze-environmental-change
12
+
13
+ ---
14
+
15
+ ## Purpose
16
+
17
+ Guides the agent through the analysis of environmental and ecological time series: trend detection, seasonal decomposition, structural breakpoint identification, anomaly detection, and post-disturbance recovery trajectory estimation.
18
+
19
+ ---
20
+
21
+ ## When to Invoke
22
+
23
+ - Analysing NDVI, EVI, LST, rainfall, or any environmental time series
24
+ - Detecting trend direction and magnitude in ecological indicators
25
+ - Identifying regime shifts or abrupt changes in a time series
26
+ - Characterising anomalies relative to historical baseline
27
+ - Estimating recovery time after a disturbance event
28
+
29
+ ---
30
+
31
+ ## Inputs
32
+
33
+ | Input | Format | Required |
34
+ |-------|--------|----------|
35
+ | Time-indexed environmental variable | CSV or GeoTIFF time stack | Yes |
36
+ | Time series frequency (daily, monthly, annual) | Text | Yes |
37
+ | Disturbance event date (for recovery) | Date | Conditional |
38
+ | Historical baseline period | Date range | Recommended |
39
+
40
+ ---
41
+
42
+ ## Outputs
43
+
44
+ | Output | Description |
45
+ |--------|-------------|
46
+ | `trend_results.csv` | Trend slope, magnitude, p-value per pixel or site |
47
+ | `seasonal_decomposition.png` | STL decomposition plot |
48
+ | `breakpoints.csv` | Detected breakpoint dates with confidence intervals |
49
+ | `anomaly_series.csv` | Standardised anomaly per time step |
50
+ | `recovery_metrics.csv` | Recovery rate and 80/100% recovery date |
51
+ | `timeseries_report.md` | Analysis narrative |
52
+
53
+ ---
54
+
55
+ ## Steps
56
+
57
+ ### 1. Inspect and Clean the Series
58
+ - Plot raw series; identify obvious outliers, data gaps, sensor artifacts
59
+ - Interpolate short gaps (≤ 3 steps) if justified; document method
60
+ - Report temporal extent, frequency, and missing value rate
61
+
62
+ ### 2. Trend Detection
63
+ - Mann-Kendall test for monotonic trend (non-parametric, handles non-normality)
64
+ - Sen's slope estimator for trend magnitude
65
+ - For raster: pixel-wise MK + Sen's slope
66
+ - Report τ (Kendall's tau), p-value, and slope in original units/year
67
+
68
+ ### 3. Seasonal Decomposition
69
+ - STL decomposition (Seasonal-Trend decomposition using LOESS): robust to outliers
70
+ - Report seasonal amplitude, trend component, and remainder
71
+ - For annual data with no seasonality, skip this step
72
+
73
+ ### 4. Breakpoint Detection
74
+ - BFAST (Breaks For Additive Season and Trend): simultaneous trend and breakpoint
75
+ - `strucchange` (R) for structural change tests
76
+ - Report: number of breakpoints, dates, 95% CIs
77
+ - Verify detected breakpoints against known events (fire, drought, deforestation)
78
+
79
+ ### 5. Anomaly Detection
80
+ - Compute historical baseline (mean ± SD or percentiles) from reference period
81
+ - Standardised anomaly: z = (x − μ) / σ
82
+ - Flag values beyond ±2σ as anomalous
83
+ - For rainfall: SPI (Standardised Precipitation Index)
84
+ - For vegetation: VCI (Vegetation Condition Index) or zNDVI
85
+
86
+ ### 6. Recovery Trajectory (post-disturbance)
87
+ - Define pre-disturbance baseline from N years before event
88
+ - Fit recovery curve: linear, exponential, or logistic
89
+ - Estimate: time to 80% recovery, time to full recovery
90
+ - Compute Recovery Indicator (RI) = (post − min) / (pre − min)
91
+
92
+ ---
93
+
94
+ ## Key Decisions to Document
95
+
96
+ - Baseline period definition
97
+ - Gap interpolation method
98
+ - Seasonal decomposition method and parameters
99
+ - Breakpoint detection method and significance level
100
+ - Anomaly threshold (z-score cutoff)
101
+ - Recovery model type
102
+
103
+ ---
104
+
105
+ ## Tools and Libraries
106
+
107
+ **R:** `bfast`, `strucchange`, `trend`, `zoo`, `terra`, `ggplot2`
108
+ **Python:** `pymannkendall`, `ruptures`, `statsmodels.tsa`, `xarray`
109
+ **Remote sensing:** Google Earth Engine (pixel-wise BFAST, trend)
110
+
111
+ ---
112
+
113
+ ## Resources
114
+
115
+ - `resources/bfast-parameter-guide.md` — how to set h, season, and type in BFAST
116
+ - `resources/anomaly-indices-reference.md` — SPI, VCI, zNDVI definitions
117
+ - `examples/` — worked BFAST and recovery analysis example
118
+
119
+ ---
120
+
121
+ ## Notes
122
+
123
+ - Autocorrelation in time series violates independence assumptions of standard tests; use MK which handles it
124
+ - BFAST requires at least 3 full seasonal cycles to be reliable
125
+ - For raster time series > 10 years monthly, consider GEE for computational efficiency
@@ -0,0 +1,33 @@
1
+ # Example Invocation Prompts — environmental-time-series
2
+
3
+ ## NDVI Trend and Breakpoint Analysis
4
+
5
+ ```
6
+ Load skill: environmental-time-series
7
+ Task: Analyse long-term NDVI trends in the Cerrado (2001–2023) from MODIS MOD13A3.
8
+
9
+ Input: data/ndvi_monthly_cerrado.csv
10
+ Columns: date (YYYY-MM-DD), pixel_id, ndvi (0–1 scaled)
11
+
12
+ Steps:
13
+ 1. STL decomposition (period = 12 months).
14
+ 2. Mann-Kendall trend test + Sen's slope per pixel (or aggregated if single site).
15
+ 3. BFAST breakpoint detection (h = 0.15, season = "harmonic").
16
+ 4. Standardised anomaly relative to 2001–2010 baseline.
17
+ 5. Report: trend slope (NDVI/year), breakpoint dates, anomaly time series.
18
+ Output: trend_results.csv, breakpoints.csv, decomposition_plot.png, anomaly_series.csv
19
+ ```
20
+
21
+ ## Post-Fire Recovery
22
+
23
+ ```
24
+ Load skill: environmental-time-series
25
+ Task: Estimate vegetation recovery trajectory after the 2020 fires in the Pantanal.
26
+ Fire event date: 2020-07-01.
27
+ Pre-fire baseline: 2015-01-01 to 2020-06-30.
28
+ Data: data/ndvi_pantanal_recovery.csv (monthly NDVI per burned polygon, 2015–2023)
29
+
30
+ Fit recovery curve (logistic or exponential).
31
+ Compute: Recovery Indicator (RI) per year, estimated time to 80% and 100% recovery.
32
+ Output: recovery_metrics.csv, recovery_curves.png
33
+ ```
@@ -0,0 +1,88 @@
1
+ # Environmental Anomaly Indices Reference
2
+
3
+ ## Standardised Precipitation Index (SPI)
4
+
5
+ Measures rainfall deficit/surplus relative to a long-term distribution, at multiple time scales.
6
+
7
+ | SPI value | Category |
8
+ |-----------|---------|
9
+ | > 2.0 | Extremely wet |
10
+ | 1.5 to 2.0 | Very wet |
11
+ | 1.0 to 1.5 | Moderately wet |
12
+ | -1.0 to 1.0 | Near normal |
13
+ | -1.5 to -1.0 | Moderately dry |
14
+ | -2.0 to -1.5 | Severely dry |
15
+ | < -2.0 | Extremely dry |
16
+
17
+ Time scales: SPI-1 (monthly), SPI-3 (seasonal), SPI-6, SPI-12 (annual drought).
18
+
19
+ ```r
20
+ library(SPEI)
21
+ data(wichita) # example dataset
22
+ spi_3 <- spi(wichita$PRCP, scale = 3)
23
+ plot(spi_3)
24
+ ```
25
+
26
+ ## Standardised Precipitation Evapotranspiration Index (SPEI)
27
+
28
+ Like SPI but accounts for evapotranspiration (temperature effect). Better for drought assessment under warming climate.
29
+
30
+ ```r
31
+ spei_12 <- spei(wichita$PRCP - wichita$PET, scale = 12)
32
+ ```
33
+
34
+ ## Vegetation Condition Index (VCI)
35
+
36
+ Normalises NDVI relative to historical minimum and maximum for the same period of year.
37
+
38
+ VCI = 100 × (NDVI − NDVI_min) / (NDVI_max − NDVI_min)
39
+
40
+ | VCI | Vegetation condition |
41
+ |-----|---------------------|
42
+ | 0–10 | Extreme stress |
43
+ | 10–20 | Severe stress |
44
+ | 20–40 | Moderate stress |
45
+ | 40–60 | Good condition |
46
+ | 60–80 | Very good |
47
+ | 80–100 | Excellent |
48
+
49
+ ```python
50
+ import numpy as np
51
+ # NDVI time stack (time × lat × lon)
52
+ vci = 100 * (ndvi - ndvi.min(axis=0)) / (ndvi.max(axis=0) - ndvi.min(axis=0) + 1e-6)
53
+ ```
54
+
55
+ ## Standardised NDVI Anomaly (zNDVI)
56
+
57
+ zNDVI = (NDVI_i − μ_NDVI) / σ_NDVI
58
+
59
+ where μ and σ are computed per pixel over the baseline period (same calendar period).
60
+
61
+ Interpretation: zNDVI < -1.5 indicates vegetation stress; zNDVI < -2.0 indicates severe anomaly.
62
+
63
+ ## Temperature Anomaly
64
+
65
+ T_anomaly = T_i − T_climatological_mean (for the same month/season)
66
+
67
+ ## Recovery Indicator (RI)
68
+
69
+ Post-disturbance vegetation recovery relative to pre-disturbance baseline:
70
+
71
+ RI_t = (NDVI_t − NDVI_min) / (NDVI_pre − NDVI_min)
72
+
73
+ - RI = 0: at the lowest post-disturbance value
74
+ - RI = 1: full recovery to pre-disturbance level
75
+ - RI < 0: further decline after disturbance
76
+ - RI > 1: exceeds pre-disturbance level (e.g., fire-stimulated flush)
77
+
78
+ ## Reference Datasets
79
+
80
+ | Variable | Product | Resolution | Source |
81
+ |----------|---------|-----------|--------|
82
+ | NDVI (8-day) | MODIS MOD13Q1 | 250 m | NASA LP DAAC |
83
+ | NDVI (monthly) | MODIS MOD13A3 | 1 km | NASA LP DAAC |
84
+ | NDVI (daily, 10 m) | Sentinel-2 (Harmonized) | 10 m | Copernicus / GEE |
85
+ | Rainfall | CHIRPS v2.0 | 5 km | UCSB Climate Hazards Group |
86
+ | Rainfall | ERA5-Land | 9 km | ECMWF |
87
+ | LST | MODIS MOD11A2 | 1 km | NASA LP DAAC |
88
+ | SPI | Global SPEI Database | 0.5° | CSIC Spain |
@@ -0,0 +1,69 @@
1
+ # BFAST Parameter Guide
2
+
3
+ BFAST (Breaks For Additive Season and Trend) detects breakpoints in both the seasonal and trend components of a time series simultaneously.
4
+
5
+ ## Key Parameters
6
+
7
+ ### `h` — Minimum Segment Size
8
+ Minimum proportion of observations between two consecutive breakpoints.
9
+ - Default: `h = 0.15` (15% of n observations)
10
+ - Example: for n = 120 monthly observations, h = 0.15 means ≥ 18 observations per segment (≥ 1.5 years)
11
+ - **Set smaller** if breakpoints may occur close together
12
+ - **Set larger** to avoid detecting noise as breakpoints
13
+
14
+ ### `season` — Seasonal Model
15
+ - `"harmonic"`: Fourier terms (sine + cosine); recommended for MODIS NDVI, temperature
16
+ - `"dummy"`: Dummy variables for each period; less flexible
17
+ - `"none"`: No seasonal component; for annual time series
18
+
19
+ ### `max.iter` — Maximum Iterations
20
+ - Default: `max.iter = 10`
21
+ - Increase to 20–50 for complex time series that do not converge quickly
22
+
23
+ ### `breaks` — Maximum Number of Breakpoints
24
+ - Default: `NULL` (auto-select by BIC)
25
+ - Set `breaks = 1` to force detection of at most 1 breakpoint
26
+ - Set `breaks = 5` for long series with multiple expected events
27
+
28
+ ### `type` — Type of Breakpoint Test
29
+ - `"OLS-MOSUM"` (default): Fast; appropriate for most cases
30
+ - `"OLS-CUSUM"`: More sensitive to gradual changes
31
+ - `"DYNP"`: Dynamic programming; exact but slow for large n
32
+
33
+ ## Typical MODIS NDVI Configuration
34
+
35
+ ```r
36
+ library(bfast)
37
+
38
+ # Monthly NDVI time series (16-day composites aggregated to monthly)
39
+ ts_ndvi <- ts(ndvi_vector, start = c(2000, 1), frequency = 12)
40
+
41
+ fit <- bfast(ts_ndvi,
42
+ h = 0.15,
43
+ season = "harmonic",
44
+ max.iter = 20,
45
+ breaks = NULL)
46
+
47
+ # Extract results
48
+ if (fit$output[[1]]$Tt.bp > 0) { # breakpoints detected
49
+ bp_dates <- fit$output[[1]]$bp.Vt$breakpoints
50
+ cat("Breakpoints at observations:", bp_dates, "\n")
51
+ }
52
+
53
+ plot(fit)
54
+ ```
55
+
56
+ ## Interpreting Outputs
57
+
58
+ | Component | Meaning |
59
+ |-----------|---------|
60
+ | `Tt` | Trend component with breakpoints |
61
+ | `St` | Seasonal component with breakpoints |
62
+ | `Nt` | Remainder (noise) |
63
+ | `bp.Vt` | Trend breakpoints (indices) |
64
+ | `bp.Wt` | Seasonal breakpoints (indices) |
65
+
66
+ ## Minimum Series Length
67
+ - At least **3 full seasonal cycles** are required
68
+ - For monthly data: ≥ 36 observations (3 years)
69
+ - For 16-day MODIS: ≥ 69 observations (≥ 3 years)