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,351 @@
1
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ # Usage: Rscript prepare_future_layers.R <current_stack.tif> <future_layers_dir> <study_area.shp> <output_dir> [ssp_label] [year_label]
5
+
6
+ # ── Inline logger ─────────────────────────────────────────────────────────────
7
+ SKILL_NAME <- "species-distribution-modeling"
8
+ .log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
9
+ log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
10
+ log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
11
+ log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
12
+ log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
13
+ log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
14
+ dir.create("logs", recursive=TRUE, showWarnings=FALSE)
15
+
16
+ #
17
+ # Arguments:
18
+ # current_stack.tif : Reference calibration raster stack (sets CRS, resolution, extent)
19
+ # future_layers_dir : Directory containing future climate .tif files (one per variable)
20
+ # study_area.shp : Shapefile / GeoPackage defining the projection area (G area)
21
+ # output_dir : Directory to write the prepared future stack (created if absent)
22
+ # ssp_label : Optional SSP label for output filename (default: "ssp245")
23
+ # year_label : Optional time horizon label for output filename (default: "2050")
24
+ #
25
+ # Output:
26
+ # future_stack_{ssp_label}_{year_label}.tif — prepared stack ready for model projection
27
+
28
+ suppressPackageStartupMessages(library(terra))
29
+ suppressPackageStartupMessages(library(sf))
30
+
31
+ # ── 1. Parse arguments ──────────────────────────────────────────────────────
32
+ log_step(1, "Analisar argumentos da linha de comando")
33
+ args <- commandArgs(trailingOnly = TRUE)
34
+
35
+ if (length(args) < 4) {
36
+ log_warn("Menos de 4 argumentos. Usando caminhos padrao para teste.")
37
+ current_tif <- "data/predictors/env_train.tif"
38
+ future_dir <- "data/chelsa_future/ssp245_2050/"
39
+ study_area_path <- "data/study_area/g_area.shp"
40
+ output_dir <- "output/future_layers"
41
+ ssp_label <- "ssp245"
42
+ year_label <- "2050"
43
+ } else {
44
+ current_tif <- args[1]
45
+ future_dir <- args[2]
46
+ study_area_path <- args[3]
47
+ output_dir <- args[4]
48
+ ssp_label <- if (length(args) >= 5) args[5] else "ssp245"
49
+ year_label <- if (length(args) >= 6) args[6] else "2050"
50
+ }
51
+
52
+ log_info("Script: prepare_future_layers.R | Skill: %s", SKILL_NAME)
53
+ log_info("Stack de calibracao : %s", current_tif)
54
+ log_info("Diretorio futuro : %s", future_dir)
55
+ log_info("Area de estudo : %s", study_area_path)
56
+ log_info("Output dir : %s", output_dir)
57
+ log_info("SSP label : %s", ssp_label)
58
+ log_info("Year label : %s", year_label)
59
+
60
+ log_decision("ssp_label", ssp_label, "cenario SSP para rotular o arquivo de saida")
61
+ log_decision("year_label", year_label, "horizonte temporal para rotular o arquivo de saida")
62
+
63
+ # ── Input precondition checks ─────────────────────────────────────────────────
64
+ if (!file.exists(current_tif)) {
65
+ log_error(
66
+ "Input nao encontrado: %s\nCausa provavel: arquivo nao gerado pelo passo anterior.\nVerifique a saida de: species-distribution-modeling (prepare_predictors ou similar)\nSkill anterior: species-distribution-modeling",
67
+ current_tif
68
+ )
69
+ stop("Calibration stack not found: ", current_tif)
70
+ }
71
+
72
+ if (!file.exists(study_area_path)) {
73
+ log_error(
74
+ "Input nao encontrado: %s\nCausa provavel: shapefile de area de estudo ausente.\nVerifique a saida de: ecological-data-foundation ou etapa de definicao da G area.\nSkill anterior: species-distribution-modeling",
75
+ study_area_path
76
+ )
77
+ stop("Study area file not found: ", study_area_path)
78
+ }
79
+
80
+ if (!dir.exists(future_dir)) {
81
+ log_error(
82
+ "Diretorio de camadas futuras nao encontrado: %s\nCausa provavel: camadas CHELSA/WorldClim futuras nao baixadas.\nBaixe os GeoTIFFs futuros e coloque em: %s\nSkill anterior: species-distribution-modeling",
83
+ future_dir, future_dir
84
+ )
85
+ stop("Future layers directory not found: ", future_dir)
86
+ }
87
+
88
+ # ── 2. Create output directory ───────────────────────────────────────────────
89
+ log_step(2, "Criar diretorio de saida")
90
+ dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
91
+ log_info("Diretorio de saida pronto: %s", output_dir)
92
+
93
+ # ── 3. Load reference calibration stack ─────────────────────────────────────
94
+ log_step(3, "Carregar stack de calibracao de referencia")
95
+ ref_stack <- tryCatch({
96
+ rast(current_tif)
97
+ }, error = function(e) {
98
+ log_error(
99
+ "Falha ao carregar stack de calibracao '%s': %s\nCausa provavel: arquivo GeoTIFF corrompido ou formato nao suportado pelo terra.\nSkill anterior: species-distribution-modeling",
100
+ current_tif, conditionMessage(e)
101
+ )
102
+ stop(e)
103
+ })
104
+
105
+ log_info("CRS : %s", crs(ref_stack, describe = TRUE)$name)
106
+ log_info("Extent : %s", paste(round(as.vector(ext(ref_stack)), 3), collapse = ", "))
107
+ log_info("Res : %s", paste(res(ref_stack), collapse = " x "))
108
+ log_info("Layers : %s", paste(names(ref_stack), collapse = ", "))
109
+
110
+ # ── 4. Load study area (G area) ──────────────────────────────────────────────
111
+ log_step(4, "Carregar area de estudo (G area)")
112
+ study_area <- tryCatch({
113
+ vect(study_area_path)
114
+ }, error = function(e) {
115
+ log_error(
116
+ "Falha ao carregar area de estudo '%s': %s\nCausa provavel: shapefile corrompido, projecao invalida ou formato nao suportado.\nVerifique: ogrinfo '%s'\nSkill anterior: species-distribution-modeling",
117
+ study_area_path, conditionMessage(e), study_area_path
118
+ )
119
+ stop(e)
120
+ })
121
+
122
+ # Reproject study area to match calibration CRS if needed
123
+ if (!identical(crs(study_area), crs(ref_stack))) {
124
+ log_info("Reprojetando area de estudo para o CRS de calibracao...")
125
+ study_area <- tryCatch(
126
+ project(study_area, crs(ref_stack)),
127
+ error = function(e) {
128
+ log_error(
129
+ "Falha ao reprojetar area de estudo: %s\nCausa provavel: CRS invalido ou incompativel.\nSkill anterior: species-distribution-modeling",
130
+ conditionMessage(e)
131
+ )
132
+ stop(e)
133
+ }
134
+ )
135
+ log_info("Reprojecao concluida.")
136
+ } else {
137
+ log_info("CRS da area de estudo ja coincide com o de calibracao. Sem reprojecao necessaria.")
138
+ }
139
+
140
+ # ── 5. Load future climate layers ────────────────────────────────────────────
141
+ log_step(5, "Carregar camadas climaticas futuras")
142
+ future_files <- list.files(future_dir, pattern = "\\.tif$", full.names = TRUE,
143
+ recursive = FALSE)
144
+ if (length(future_files) == 0) {
145
+ log_error(
146
+ "Nenhum arquivo .tif encontrado em: %s\nCausa provavel: camadas futuras nao baixadas ou extensao diferente de .tif.\nVerifique o conteudo do diretorio.\nSkill anterior: species-distribution-modeling",
147
+ future_dir
148
+ )
149
+ stop("No .tif files found in: ", future_dir)
150
+ }
151
+
152
+ log_info("Arquivos de camada futura encontrados: %d", length(future_files))
153
+
154
+ # Stack all future layers
155
+ future_raw <- tryCatch({
156
+ rast(future_files)
157
+ }, error = function(e) {
158
+ log_error(
159
+ "Falha ao empilhar camadas futuras: %s\nCausa provavel: GeoTIFFs corrompidos ou com extents incompativeis.\nVerifique: gdalinfo nos arquivos em %s\nSkill anterior: species-distribution-modeling",
160
+ conditionMessage(e), future_dir
161
+ )
162
+ stop(e)
163
+ })
164
+ log_info("Nomes das camadas futuras (brutos): %s", paste(names(future_raw), collapse = ", "))
165
+
166
+ # ── 6. Rename future layers to match calibration ─────────────────────────────
167
+ log_step(6, "Renomear camadas futuras para coincidir com a calibracao")
168
+ # Strategy: if layer names differ but count matches, rename by position.
169
+ # If counts differ, attempt name matching. Fail clearly if neither works.
170
+
171
+ ref_names <- names(ref_stack)
172
+ future_names <- names(future_raw)
173
+
174
+ if (setequal(ref_names, future_names)) {
175
+ # Names match already — reorder to calibration order
176
+ future_raw <- future_raw[[ref_names]]
177
+ log_info("Nomes das camadas coincidentes. Reordenados conforme calibracao.")
178
+ log_decision("rename_strategy", "reorder", "nomes identicos, apenas reordenados")
179
+
180
+ } else if (length(future_names) == length(ref_names) &&
181
+ !setequal(ref_names, future_names)) {
182
+ # Same count but different names — rename by position (common with CHELSA long names)
183
+ log_warn(
184
+ "Nomes das camadas diferem da calibracao. Renomeando por posicao (%d camadas).",
185
+ length(ref_names)
186
+ )
187
+ log_info("Nomes antigos: %s", paste(future_names, collapse = ", "))
188
+ log_info("Novos nomes : %s", paste(ref_names, collapse = ", "))
189
+ log_decision("rename_strategy", "by_position", "mesmo numero de camadas mas nomes diferentes (comum com CHELSA)")
190
+ names(future_raw) <- ref_names
191
+
192
+ } else {
193
+ # Different count — try to find matching layers by partial name
194
+ log_warn("Numero de camadas difere. Tentando correspondencia por nome parcial...")
195
+ matched <- sapply(ref_names, function(rn) {
196
+ idx <- which(grepl(rn, future_names, fixed = TRUE))
197
+ if (length(idx) == 1) idx else NA_integer_
198
+ })
199
+
200
+ if (any(is.na(matched))) {
201
+ missing_layers <- ref_names[is.na(matched)]
202
+ log_error(
203
+ "Nao e possivel associar camadas futuras as de calibracao.\nCalibracao espera: %s\nCamadas futuras: %s\nSem correspondencia para: %s\nAcao: renomeie os .tif futuros para coincidir exatamente com os nomes de calibracao.\nSkill anterior: species-distribution-modeling",
204
+ paste(ref_names, collapse = ", "),
205
+ paste(future_names, collapse = ", "),
206
+ paste(missing_layers, collapse = ", ")
207
+ )
208
+ stop(
209
+ "Cannot match future layers to calibration layers.\n",
210
+ " Calibration expects: ", paste(ref_names, collapse = ", "), "\n",
211
+ " Future layers found: ", paste(future_names, collapse = ", "), "\n",
212
+ " Could not find match for: ", paste(missing_layers, collapse = ", "), "\n",
213
+ " Action: rename future .tif files to match calibration layer names exactly."
214
+ )
215
+ }
216
+
217
+ future_raw <- future_raw[[matched]]
218
+ names(future_raw) <- ref_names
219
+ log_info("Camadas associadas por nome parcial. Reordenadas conforme calibracao.")
220
+ log_decision("rename_strategy", "partial_name_match", "contagem diferente; correspondencia por substring")
221
+ }
222
+
223
+ # ── 7. Reproject to calibration CRS ──────────────────────────────────────────
224
+ log_step(7, "Reprojetar stack futuro para o CRS de calibracao")
225
+ if (!identical(crs(future_raw), crs(ref_stack))) {
226
+ log_info("Reprojetando stack futuro para CRS de calibracao...")
227
+ log_decision("resample_method_reproj", "bilinear", "interpolacao bilinear para dados continuos de clima")
228
+ future_raw <- tryCatch(
229
+ project(future_raw, crs(ref_stack), method = "bilinear"),
230
+ error = function(e) {
231
+ log_error(
232
+ "Falha ao reprojetar stack futuro: %s\nCausa provavel: CRS invalido ou falta de memoria para o raster.\nSkill anterior: species-distribution-modeling",
233
+ conditionMessage(e)
234
+ )
235
+ stop(e)
236
+ }
237
+ )
238
+ log_info("Reprojecao concluida.")
239
+ } else {
240
+ log_info("CRS ja coincide com calibracao. Sem reprojecao necessaria.")
241
+ }
242
+
243
+ # ── 8. Crop and mask to study area (G area) ───────────────────────────────────
244
+ log_step(8, "Recortar e mascarar para a area de estudo")
245
+ tryCatch({
246
+ future_cropped <- crop(future_raw, study_area)
247
+ future_masked <- mask(future_cropped, study_area)
248
+ log_info("Recorte e mascara aplicados. Celulas validas apos mascara: nao calculado (use global(future_masked, 'notNA')).")
249
+ }, error = function(e) {
250
+ log_error(
251
+ "Falha ao recortar/mascarar o stack futuro: %s\nCausa provavel: extent da area de estudo fora do extent do raster futuro.\nVerifique a projecao e o extent dos arquivos.\nSkill anterior: species-distribution-modeling",
252
+ conditionMessage(e)
253
+ )
254
+ stop(e)
255
+ })
256
+
257
+ # ── 9. Resample to exactly match calibration grid ────────────────────────────
258
+ log_step(9, "Reamostrar para coincidir exatamente com o grid de calibracao")
259
+ log_decision("resample_method", "bilinear", "interpolacao bilinear para dados continuos de clima")
260
+ future_resampled <- tryCatch(
261
+ resample(future_masked, ref_stack, method = "bilinear"),
262
+ error = function(e) {
263
+ log_error(
264
+ "Falha na reamostragem do stack futuro: %s\nCausa provavel: incompatibilidade de CRS ou extent entre stack futuro e de calibracao.\nVerifique os passos 7 e 8.\nSkill anterior: species-distribution-modeling",
265
+ conditionMessage(e)
266
+ )
267
+ stop(e)
268
+ }
269
+ )
270
+
271
+ # ── 10. Geometry verification ────────────────────────────────────────────────
272
+ log_step(10, "Verificar geometria do stack futuro contra o de calibracao")
273
+ geom_ok <- tryCatch(
274
+ compareGeom(ref_stack, future_resampled, stopOnError = FALSE,
275
+ res = TRUE, orig = TRUE, crs = TRUE),
276
+ error = function(e) {
277
+ log_warn("compareGeom retornou erro: %s. Prosseguindo com cautela.", conditionMessage(e))
278
+ FALSE
279
+ }
280
+ )
281
+
282
+ if (!geom_ok) {
283
+ log_error(
284
+ "Verificacao de geometria FALHOU apos reamostragem.\nCalibracao: ext=%s res=%s crs=%s\nFuturo : ext=%s res=%s crs=%s\nVerifique incompatibilidades de extent ou datum e re-execute.\nSkill anterior: species-distribution-modeling",
285
+ as.character(ext(ref_stack)),
286
+ paste(res(ref_stack), collapse = "x"),
287
+ crs(ref_stack, describe = TRUE)$name,
288
+ as.character(ext(future_resampled)),
289
+ paste(res(future_resampled), collapse = "x"),
290
+ crs(future_resampled, describe = TRUE)$name
291
+ )
292
+ stop(
293
+ "Geometry verification FAILED after resampling.\n",
294
+ " Calibration: ext=", as.character(ext(ref_stack)),
295
+ " res=", paste(res(ref_stack), collapse="x"),
296
+ " crs=", crs(ref_stack, describe=TRUE)$name, "\n",
297
+ " Future: ext=", as.character(ext(future_resampled)),
298
+ " res=", paste(res(future_resampled), collapse="x"),
299
+ " crs=", crs(future_resampled, describe=TRUE)$name, "\n",
300
+ " Check for extent or datum mismatches and re-run."
301
+ )
302
+ }
303
+ log_info("Verificacao de geometria PASSOU.")
304
+
305
+ # ── 11. Final layer name verification ────────────────────────────────────────
306
+ log_step(11, "Verificar nomes finais das camadas")
307
+ if (!identical(names(future_resampled), names(ref_stack))) {
308
+ name_diff <- setdiff(names(future_resampled), names(ref_stack))
309
+ log_error(
310
+ "Discrepancia de nomes de camadas no stack final.\nEsperado: %s\nObtido : %s\nDivergentes: %s\nSkill anterior: species-distribution-modeling",
311
+ paste(names(ref_stack), collapse = ", "),
312
+ paste(names(future_resampled), collapse = ", "),
313
+ paste(name_diff, collapse = ", ")
314
+ )
315
+ stop(
316
+ "Layer name mismatch in final stack.\n",
317
+ " Expected: ", paste(names(ref_stack), collapse = ", "), "\n",
318
+ " Got: ", paste(names(future_resampled), collapse = ", "), "\n",
319
+ " Differing layers: ", paste(name_diff, collapse = ", ")
320
+ )
321
+ }
322
+ log_info("Nomes das camadas verificados. Camadas: %s", paste(names(future_resampled), collapse = ", "))
323
+
324
+ # ── 12. Save output ───────────────────────────────────────────────────────────
325
+ log_step(12, "Gravar stack futuro preparado")
326
+ out_filename <- paste0("future_stack_", ssp_label, "_", year_label, ".tif")
327
+ out_path <- file.path(output_dir, out_filename)
328
+
329
+ tryCatch({
330
+ writeRaster(future_resampled, out_path, overwrite = TRUE)
331
+ log_info("Gravado: %s", out_path)
332
+ }, error = function(e) {
333
+ log_error(
334
+ "Falha ao gravar raster de saida '%s': %s\nCausa provavel: sem permissao de escrita ou espaco em disco insuficiente.\nSkill anterior: species-distribution-modeling",
335
+ out_path, conditionMessage(e)
336
+ )
337
+ stop(e)
338
+ })
339
+
340
+ # ── 13. Summary ───────────────────────────────────────────────────────────────
341
+ log_step(13, "Exibir resumo das camadas futuras preparadas")
342
+ log_info("========== RESUMO DAS CAMADAS FUTURAS ==========")
343
+ log_info("SSP : %s", ssp_label)
344
+ log_info("Horizonte temporal : %s", year_label)
345
+ log_info("Camadas preparadas : %d", nlyr(future_resampled))
346
+ log_info("Nomes das camadas : %s", paste(names(future_resampled), collapse = ", "))
347
+ log_info("CRS de saida : %s", crs(future_resampled, describe = TRUE)$name)
348
+ log_info("Resolucao de saida : %s unidades", paste(res(future_resampled), collapse = " x "))
349
+ log_info("Arquivo de saida : %s", out_path)
350
+ log_info("=================================================")
351
+ log_info("Pronto para: maxnet::predict(), biomod2::BIOMOD_Projection() ou sdm_pipeline.py")
@@ -0,0 +1,220 @@
1
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ # Usage: Rscript project_scenarios.R <model_rds> <scenarios_dir> <output_dir> [threshold_from_csv]
5
+ # Project a fitted SDM across multiple future climate scenario stacks.
6
+ # scenarios_dir must contain .tif files named: <ssp>_<year>.tif (e.g. ssp245_2050.tif)
7
+
8
+ # ── Inline logger ─────────────────────────────────────────────────────────────
9
+ SKILL_NAME <- "species-distribution-modeling"
10
+ .log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
11
+ log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
12
+ log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
13
+ log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
14
+ log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
15
+ log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
16
+ dir.create("logs", recursive=TRUE, showWarnings=FALSE)
17
+
18
+ suppressPackageStartupMessages({
19
+ library(terra)
20
+ })
21
+
22
+ # ── Arguments ─────────────────────────────────────────────────────────────────
23
+ args <- commandArgs(trailingOnly = TRUE)
24
+ model_rds <- if (length(args) >= 1) args[1] else stop("model_rds required")
25
+ scenarios_dir <- if (length(args) >= 2) args[2] else stop("scenarios_dir required")
26
+ output_dir <- if (length(args) >= 3) args[3] else "outputs/projections"
27
+ threshold_from_csv<- if (length(args) >= 4) args[4] else NULL # path to prediction_summary.csv
28
+
29
+ log_decision("scenarios_dir", scenarios_dir, "Directory containing SSP scenario stacks")
30
+ log_decision("threshold_from_csv", ifelse(is.null(threshold_from_csv), "NULL", threshold_from_csv),
31
+ "If provided, reuse threshold from predict_distribution.R output")
32
+
33
+ # ── Precondition checks ───────────────────────────────────────────────────────
34
+ if (!file.exists(model_rds)) {
35
+ log_error("Model RDS nao encontrado: %s\nCausa provavel: run_ensemble_sdm.R nao concluiu.\nVerifique: a saida de skills/species-distribution-modeling.\nSkill anterior: species-distribution-modeling", model_rds)
36
+ stop("Missing model: ", model_rds)
37
+ }
38
+ if (!dir.exists(scenarios_dir)) {
39
+ log_error("Scenarios dir nao encontrado: %s\nCausa provavel: prepare_future_layers.R nao foi executado.\nVerifique: CMIP6 stacks foram baixados e preparados.\nSkill anterior: geoprocessing-for-ecology", scenarios_dir)
40
+ stop("Missing scenarios dir: ", scenarios_dir)
41
+ }
42
+ dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
43
+
44
+ # ── Step 1: Load model ────────────────────────────────────────────────────────
45
+ log_step(1, "Loading model")
46
+ model_obj <- tryCatch(readRDS(model_rds), error = function(e) {
47
+ log_error("Falha ao ler model RDS: %s\nCausa provavel: arquivo corrompido.\nVerifique: Rscript gerou o modelo com saveRDS().", conditionMessage(e))
48
+ stop(e)
49
+ })
50
+ is_ensemble <- is.list(model_obj) && !inherits(model_obj, "MaxEnt")
51
+ log_info("Model class: %s | ensemble: %s", class(model_obj)[1], is_ensemble)
52
+
53
+ # ── Step 2: Get threshold ─────────────────────────────────────────────────────
54
+ log_step(2, "Resolving binary threshold")
55
+ threshold_val <- NA_real_
56
+ if (!is.null(threshold_from_csv) && file.exists(threshold_from_csv)) {
57
+ sum_df <- read.csv(threshold_from_csv, stringsAsFactors = FALSE)
58
+ threshold_val <- sum_df$threshold_value[1]
59
+ log_info("Threshold loaded from CSV: %.4f", threshold_val)
60
+ } else if (!is.null(model_obj$threshold_values)) {
61
+ threshold_val <- model_obj$threshold_values["MaxTSS"]
62
+ log_info("Threshold from model (MaxTSS): %.4f", threshold_val)
63
+ } else {
64
+ log_warn("No threshold source available; binary maps will use P10 of each scenario map")
65
+ }
66
+
67
+ # ── Helper: predict from one stack ────────────────────────────────────────────
68
+ predict_stack <- function(mod, preds) {
69
+ cls <- class(mod)[1]
70
+ if (cls == "maxnet") {
71
+ suppressPackageStartupMessages(library(maxnet))
72
+ pred_df <- as.data.frame(preds, na.rm = FALSE)
73
+ p <- predict(mod, pred_df, type = "cloglog")
74
+ r <- preds[[1]]; values(r) <- p; return(r)
75
+ } else if (cls %in% c("gbm", "BRT")) {
76
+ suppressPackageStartupMessages(library(gbm))
77
+ pred_df <- as.data.frame(preds, na.rm = FALSE)
78
+ p <- predict.gbm(mod, pred_df, n.trees = mod$n.trees, type = "response")
79
+ r <- preds[[1]]; values(r) <- p; return(r)
80
+ } else if (cls == "randomForest") {
81
+ suppressPackageStartupMessages(library(randomForest))
82
+ pred_df <- as.data.frame(preds, na.rm = FALSE)
83
+ p <- predict(mod, pred_df, type = "prob")[, 2]
84
+ r <- preds[[1]]; values(r) <- p; return(r)
85
+ } else {
86
+ return(predict(mod, preds))
87
+ }
88
+ }
89
+
90
+ # ── Step 3: Find scenario stacks ──────────────────────────────────────────────
91
+ log_step(3, "Scanning scenario stacks")
92
+ tif_files <- list.files(scenarios_dir, pattern = "\\.tif$", full.names = TRUE)
93
+ if (length(tif_files) == 0) {
94
+ log_error("Nenhum .tif encontrado em: %s\nCausa provavel: prepare_future_layers.R nao gerou os stacks.\nVerifique: arquivos .tif existem em scenarios_dir.", scenarios_dir)
95
+ stop("No .tif files in scenarios_dir")
96
+ }
97
+ log_info("Found %d scenario stack(s)", length(tif_files))
98
+
99
+ # Parse scenario labels from filenames (e.g. ssp245_2050.tif → ssp245_2050)
100
+ scenario_labels <- tools::file_path_sans_ext(basename(tif_files))
101
+ log_decision("scenario_labels", paste(scenario_labels, collapse=", "),
102
+ "Derived from .tif filenames in scenarios_dir")
103
+
104
+ # ── Step 4: Project each scenario ────────────────────────────────────────────
105
+ log_step(4, "Projecting across all scenarios")
106
+
107
+ results <- vector("list", length(tif_files))
108
+ current_suit <- NULL # will be set from first file for change map
109
+
110
+ for (i in seq_along(tif_files)) {
111
+ lbl <- scenario_labels[i]
112
+ tif <- tif_files[i]
113
+ log_info("Projecting scenario %d/%d: %s", i, length(tif_files), lbl)
114
+
115
+ preds <- tryCatch(rast(tif), error = function(e) {
116
+ log_error("Falha ao ler stack %s: %s\nCausa provavel: arquivo corrompido ou caminho errado.", lbl, conditionMessage(e))
117
+ return(NULL)
118
+ })
119
+ if (is.null(preds)) { results[[i]] <- NULL; next }
120
+
121
+ suit <- tryCatch({
122
+ if (is_ensemble) {
123
+ preds_list <- lapply(model_obj$models, predict_stack, preds = preds)
124
+ suit_stack <- rast(preds_list)
125
+ w <- if (!is.null(model_obj$auc_weights)) model_obj$auc_weights else
126
+ rep(1 / nlyr(suit_stack), nlyr(suit_stack))
127
+ app(suit_stack, function(x) weighted.mean(x, w, na.rm = TRUE))
128
+ } else {
129
+ predict_stack(model_obj, preds)
130
+ }
131
+ }, error = function(e) {
132
+ log_error("Falha na projecao do cenario %s: %s\nVerifique: stack tem os mesmos preditores do modelo.", lbl, conditionMessage(e))
133
+ NULL
134
+ })
135
+ if (is.null(suit)) { results[[i]] <- NULL; next }
136
+
137
+ # Save suitability raster
138
+ suit_file <- file.path(output_dir, paste0("suitability_", lbl, ".tif"))
139
+ writeRaster(suit, suit_file, overwrite = TRUE)
140
+
141
+ # Binary map
142
+ thr_use <- if (!is.na(threshold_val)) threshold_val else
143
+ quantile(values(suit), 0.10, na.rm = TRUE)
144
+ binary <- suit >= thr_use
145
+ bin_file <- file.path(output_dir, paste0("binary_", lbl, ".tif"))
146
+ writeRaster(binary, bin_file, overwrite = TRUE, datatype = "INT1U")
147
+
148
+ # Area stats
149
+ suitable_cells <- sum(values(binary) == 1, na.rm = TRUE)
150
+ total_cells <- sum(!is.na(values(suit)))
151
+ cell_area_km2 <- prod(res(suit)) / 1e6
152
+
153
+ results[[i]] <- data.frame(
154
+ scenario = lbl,
155
+ threshold_used = round(thr_use, 4),
156
+ suitable_area_km2 = round(suitable_cells * cell_area_km2, 1),
157
+ total_area_km2 = round(total_cells * cell_area_km2, 1),
158
+ pct_suitable = round(100 * suitable_cells / total_cells, 2),
159
+ mean_suitability = round(mean(values(suit), na.rm = TRUE), 4),
160
+ stringsAsFactors = FALSE
161
+ )
162
+
163
+ # Keep first scenario as "current" reference for change map
164
+ if (i == 1) current_suit <- suit
165
+
166
+ log_info(" %s: suitable = %.1f km2 (%.1f%%)",
167
+ lbl, results[[i]]$suitable_area_km2, results[[i]]$pct_suitable)
168
+ }
169
+
170
+ # ── Step 5: Scenario comparison CSV ───────────────────────────────────────────
171
+ log_step(5, "Writing scenario comparison table")
172
+ valid_results <- Filter(Negate(is.null), results)
173
+ comparison_df <- do.call(rbind, valid_results)
174
+ comp_file <- file.path(output_dir, "scenario_comparison.csv")
175
+ write.csv(comparison_df, comp_file, row.names = FALSE)
176
+ log_info("Scenario comparison saved: %s", comp_file)
177
+
178
+ # ── Step 6: Change map (vs. first scenario) ────────────────────────────────────
179
+ log_step(6, "Computing change map relative to first scenario")
180
+ if (!is.null(current_suit) && length(tif_files) > 1) {
181
+ tryCatch({
182
+ # Load and stack all suitability rasters
183
+ suit_files <- file.path(output_dir, paste0("suitability_", scenario_labels, ".tif"))
184
+ suit_files <- suit_files[file.exists(suit_files)]
185
+ if (length(suit_files) >= 2) {
186
+ all_suits <- rast(suit_files)
187
+ mean_future <- app(all_suits[[-1]], mean, na.rm = TRUE)
188
+ change_map <- (mean_future - current_suit) / (current_suit + 1e-6)
189
+ change_file <- file.path(output_dir, "scenario_change_map.tif")
190
+ writeRaster(change_map, change_file, overwrite = TRUE)
191
+ log_info("Change map saved (relative to first scenario): %s", change_file)
192
+ }
193
+ }, error = function(e) {
194
+ log_warn("Change map computation failed: %s. Skipping.", conditionMessage(e))
195
+ })
196
+ }
197
+
198
+ # ── Step 7: Summary plot ───────────────────────────────────────────────────────
199
+ log_step(7, "Generating scenario summary plot")
200
+ tryCatch({
201
+ suppressPackageStartupMessages(library(ggplot2))
202
+ p <- ggplot(comparison_df, aes(x = scenario, y = suitable_area_km2,
203
+ fill = pct_suitable)) +
204
+ geom_col() +
205
+ geom_text(aes(label = paste0(pct_suitable, "%")), vjust = -0.3, size = 3) +
206
+ scale_fill_gradient(low = "#d4e6f1", high = "#1a5276",
207
+ name = "% Suitable") +
208
+ labs(title = "Suitable Area by Scenario",
209
+ x = "Scenario", y = "Suitable Area (km²)") +
210
+ theme_bw() +
211
+ theme(axis.text.x = element_text(angle = 45, hjust = 1))
212
+
213
+ plot_file <- file.path(output_dir, "scenario_summary_plot.png")
214
+ ggsave(plot_file, p, width = max(6, length(tif_files) * 1.5), height = 5, dpi = 150)
215
+ log_info("Summary plot saved: %s", plot_file)
216
+ }, error = function(e) {
217
+ log_warn("ggplot2 summary plot failed: %s. Skipping.", conditionMessage(e))
218
+ })
219
+
220
+ log_step(8, "Done — all scenario projections in: %s", output_dir)
@@ -0,0 +1,99 @@
1
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ # Usage: Rscript run_ensemble_sdm.R <occurrences.csv> <predictors_stack.tif> <study_area.shp> <output_dir>
5
+
6
+ # ── Inline logger ─────────────────────────────────────────────────────────────
7
+ SKILL_NAME <- "species-distribution-modeling"
8
+ .log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
9
+ log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
10
+ log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
11
+ log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
12
+ log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
13
+ log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
14
+ dir.create("logs", recursive=TRUE, showWarnings=FALSE)
15
+
16
+ # Fit MaxEnt + BRT + RF ensemble SDM
17
+ # Usage: Rscript run_ensemble_sdm.R <params_yaml> <output_dir>
18
+ # Requires: terra, sf, maxnet, gbm, randomForest, dismo, blockCV, yaml
19
+
20
+ suppressPackageStartupMessages({
21
+ library(terra); library(sf); library(maxnet)
22
+ library(gbm); library(randomForest); library(yaml)
23
+ })
24
+
25
+ log_step(1, "Analisar argumentos e carregar parametros YAML")
26
+ args <- commandArgs(trailingOnly = TRUE)
27
+ params_f <- ifelse(length(args) >= 1, args[1], "params.yaml")
28
+ output_dir <- ifelse(length(args) >= 2, args[2], "outputs/sdm")
29
+
30
+ log_info("Script: run_ensemble_sdm.R | Skill: %s", SKILL_NAME)
31
+ log_info("Params file : %s", params_f)
32
+ log_info("Output dir : %s", output_dir)
33
+
34
+ # ── Input precondition check ──────────────────────────────────────────────────
35
+ if (!file.exists(params_f)) {
36
+ log_error(
37
+ "Input nao encontrado: %s\nCausa provavel: arquivo nao gerado pelo passo anterior.\nVerifique a saida de: species-distribution-modeling (tune_maxnet ou prepare_future_layers)\nSkill anterior: species-distribution-modeling",
38
+ params_f
39
+ )
40
+ stop("Missing: ", params_f)
41
+ }
42
+
43
+ dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
44
+ log_info("Diretorio de saida pronto: %s", output_dir)
45
+
46
+ log_step(2, "Ler parametros do arquivo YAML")
47
+ p <- tryCatch({
48
+ yaml::read_yaml(params_f)
49
+ }, error = function(e) {
50
+ log_error(
51
+ "Falha ao ler arquivo YAML '%s': %s\nCausa provavel: YAML malformado ou encoding incorreto.\nVerifique a sintaxe do arquivo.\nSkill anterior: species-distribution-modeling",
52
+ params_f, conditionMessage(e)
53
+ )
54
+ stop(e)
55
+ })
56
+
57
+ set.seed(p$random_seeds$global)
58
+ log_decision("random_seed", p$random_seeds$global, "semente global definida no params.yaml para reprodutibilidade")
59
+ log_decision("algorithms", paste(p$modeling$algorithms, collapse = ","), "algoritmos definidos em params.yaml$modeling$algorithms")
60
+ log_decision("cv_method", p$modeling$cv_method, "metodo de validacao cruzada definido em params.yaml")
61
+ log_decision("cv_folds", p$modeling$cv_folds, "numero de folds definido em params.yaml")
62
+
63
+ log_info("=== SDM Ensemble Pipeline ===")
64
+ log_info("Output : %s", output_dir)
65
+ log_info("Algoritmos: %s", paste(p$modeling$algorithms, collapse = ", "))
66
+ log_info("CV method : %s | Folds: %d", p$modeling$cv_method, p$modeling$cv_folds)
67
+
68
+ # NOTE: This is a scaffold. Load your data and call your modeling functions below.
69
+ # Example structure:
70
+ #
71
+ # occ <- read.csv("data/processed/occ_thinned.csv")
72
+ # bg <- read.csv("data/processed/background.csv")
73
+ # stack <- rast("data/predictors_stack.tif")
74
+ # predictors <- readLines("outputs/selected_predictors.txt")
75
+ #
76
+ # occ_env <- extract(stack[[predictors]], occ[, c("decimalLongitude","decimalLatitude")])
77
+ # bg_env <- extract(stack[[predictors]], bg[, c("lon","lat")])
78
+ #
79
+ # train_df <- rbind(
80
+ # cbind(pa = 1, occ_env),
81
+ # cbind(pa = 0, bg_env)
82
+ # ) |> na.omit()
83
+ #
84
+ # # MaxEnt
85
+ # mx <- maxnet(p = train_df$pa, data = train_df[,-1],
86
+ # regmult = p$hyperparameters$maxnet$regularization_multiplier[2])
87
+ #
88
+ # # Predict and ensemble — see biomod2 for full ensemble workflow
89
+
90
+ log_step(3, "Scaffold carregado — preencher com carregamento de dados e chamadas de modelo")
91
+ tryCatch({
92
+ log_info("Scaffold carregado. Adicione o carregamento de dados e as chamadas de modelo para seu estudo.")
93
+ }, error = function(e) {
94
+ log_error(
95
+ "Falha no bloco principal do scaffold: %s\nCausa provavel: erro de configuracao ou dados ausentes.\nVerifique os arquivos de dados e o params.yaml.\nSkill anterior: species-distribution-modeling",
96
+ conditionMessage(e)
97
+ )
98
+ stop(e)
99
+ })