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,318 @@
1
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ """Download marine occurrence records from OBIS (Ocean Biodiversity Information System).
5
+
6
+ Usage: python download_from_obis.py <species_name_or_list_csv> <output_dir> [year_from] [year_to] [wkt_geometry]
7
+
8
+ Arguments:
9
+ species_name_or_list_csv : Species name (e.g., "Chelonia mydas") or path to CSV
10
+ with column 'scientificName'
11
+ output_dir : Directory for outputs (created if absent)
12
+ year_from : Minimum year of observation (default: 1950)
13
+ year_to : Maximum year of observation (default: current year)
14
+ wkt_geometry : WKT polygon to restrict query (optional)
15
+
16
+ Outputs (per species):
17
+ occurrences_raw_OBIS_{species}_{date}.csv — standardised occurrence records
18
+ download_metadata_OBIS_{species}.txt — download provenance and citation
19
+
20
+ Standard output schema:
21
+ species, decimalLatitude, decimalLongitude, eventDate, countryCode,
22
+ basisOfRecord, coordinateUncertaintyInMeters, datasetName, occurrenceID,
23
+ source, download_doi
24
+ Extra OBIS columns:
25
+ depth, marine
26
+ """
27
+
28
+ import logging
29
+ import sys
30
+ from datetime import datetime
31
+ from pathlib import Path
32
+
33
+ SKILL_NAME = "ecological-data-foundation"
34
+ _LOG_DIR = Path("logs")
35
+ _LOG_DIR.mkdir(parents=True, exist_ok=True)
36
+ _log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
37
+ logging.basicConfig(
38
+ level=logging.INFO,
39
+ format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
40
+ datefmt="%Y-%m-%d %H:%M:%S",
41
+ handlers=[
42
+ logging.StreamHandler(sys.stdout),
43
+ logging.FileHandler(_log_file, encoding="utf-8"),
44
+ ],
45
+ )
46
+ logger = logging.getLogger(SKILL_NAME)
47
+
48
+ def log_step(n: int, desc: str) -> None:
49
+ logger.info("-- STEP %d: %s", n, desc)
50
+
51
+ def log_decision(var: str, val, why: str) -> None:
52
+ logger.info("DECISION | %s = %s | %s", var, val, why)
53
+
54
+
55
+ from datetime import date
56
+
57
+ try:
58
+ import requests
59
+ import pandas as pd
60
+ except ImportError as e:
61
+ logger.error(
62
+ "Dependencia ausente: %s\n Instale com: pip install requests pandas\n Skill anterior: ecological-data-foundation",
63
+ e,
64
+ )
65
+ sys.exit(1)
66
+
67
+
68
+ # ── Constants ─────────────────────────────────────────────────────────────────
69
+ OBIS_API_BASE = "https://api.obis.org/v3"
70
+ PAGE_SIZE = 5000 # OBIS max per request
71
+ BAD_FLAGS = {"NO_COORD", "ZERO_COORD", "ON_LAND", "DEPTH_EXCEEDS_BATH"}
72
+
73
+
74
+ # ── Helper functions ──────────────────────────────────────────────────────────
75
+
76
+ def fetch_obis(scientificname: str, year_from: int, year_to: int,
77
+ wkt_geometry: str | None) -> list[dict]:
78
+ """Fetch all OBIS records for a species, handling pagination."""
79
+ url = f"{OBIS_API_BASE}/occurrence"
80
+ offset = 0
81
+ all_results: list[dict] = []
82
+
83
+ while True:
84
+ params: dict = {
85
+ "scientificname": scientificname,
86
+ "absence": "exclude",
87
+ "startdate": f"{year_from}-01-01",
88
+ "enddate": f"{year_to}-12-31",
89
+ "size": PAGE_SIZE,
90
+ "offset": offset,
91
+ }
92
+ if wkt_geometry:
93
+ params["geometry"] = wkt_geometry
94
+
95
+ try:
96
+ resp = requests.get(url, params=params, timeout=60)
97
+ resp.raise_for_status()
98
+ data = resp.json()
99
+ except requests.RequestException as e:
100
+ logger.error(
101
+ "Falha na requisicao OBIS (offset=%d, especie='%s'): %s\n Causa provavel: sem conexao com a internet ou API OBIS indisponivel.\n Verifique: https://api.obis.org/\n Skill anterior: ecological-data-foundation",
102
+ offset, scientificname, e,
103
+ )
104
+ raise
105
+
106
+ results = data.get("results", [])
107
+ if not results:
108
+ break
109
+
110
+ all_results.extend(results)
111
+ logger.info("Pagina offset=%d: %d registros recuperados (total: %d)",
112
+ offset, len(results), len(all_results))
113
+
114
+ total = data.get("total", 0)
115
+ if len(all_results) >= total:
116
+ break
117
+ if len(results) < PAGE_SIZE:
118
+ break
119
+ offset += PAGE_SIZE
120
+
121
+ return all_results
122
+
123
+
124
+ def apply_quality_flags(records: list[dict]) -> list[dict]:
125
+ """Remove records with known OBIS quality issues."""
126
+ filtered = []
127
+ n_flagged = 0
128
+ for rec in records:
129
+ flags_raw = rec.get("flags", "") or ""
130
+ flags = set(str(flags_raw).upper().split(","))
131
+ if flags & BAD_FLAGS:
132
+ n_flagged += 1
133
+ continue
134
+ filtered.append(rec)
135
+ if n_flagged > 0:
136
+ logger.warning("%d registros removidos por flags OBIS de qualidade.", n_flagged)
137
+ return filtered
138
+
139
+
140
+ def standardise_records(records: list[dict], species_name: str) -> "pd.DataFrame":
141
+ """Convert OBIS record dicts to the standard occurrence schema."""
142
+ rows = []
143
+ for rec in records:
144
+ rows.append({
145
+ "species": species_name,
146
+ "decimalLatitude": rec.get("decimalLatitude"),
147
+ "decimalLongitude": rec.get("decimalLongitude"),
148
+ "eventDate": rec.get("eventDate") or rec.get("date_start", ""),
149
+ "countryCode": rec.get("countryCode", ""),
150
+ "basisOfRecord": rec.get("basisOfRecord", "OCCURRENCE"),
151
+ "coordinateUncertaintyInMeters": rec.get("coordinateUncertaintyInMeters"),
152
+ "datasetName": rec.get("datasetName", ""),
153
+ "occurrenceID": str(rec.get("occurrenceID", rec.get("id", ""))),
154
+ "source": "OBIS",
155
+ "download_doi": None,
156
+ "depth": rec.get("depth"),
157
+ "marine": True,
158
+ })
159
+
160
+ df = pd.DataFrame(rows)
161
+ df["decimalLatitude"] = pd.to_numeric(df["decimalLatitude"], errors="coerce")
162
+ df["decimalLongitude"] = pd.to_numeric(df["decimalLongitude"], errors="coerce")
163
+ df = df.dropna(subset=["decimalLatitude", "decimalLongitude"])
164
+ return df.reset_index(drop=True)
165
+
166
+
167
+ def save_metadata(output_dir: Path, species_name: str, n_records: int,
168
+ year_from: int, year_to: int, wkt_geometry: str | None) -> None:
169
+ safe_name = species_name.replace(" ", "_")
170
+ today = date.today().isoformat()
171
+ year = date.today().year
172
+ lines = [
173
+ f"Species: {species_name}",
174
+ f"Source: Ocean Biodiversity Information System (OBIS) — https://obis.org",
175
+ f"API endpoint: {OBIS_API_BASE}/occurrence",
176
+ f"Absence records excluded: True",
177
+ f"OBIS quality flags applied: True (NO_COORD, ZERO_COORD, ON_LAND, DEPTH_EXCEEDS_BATH removed)",
178
+ f"Year range: {year_from} - {year_to}",
179
+ f"WKT geometry: {wkt_geometry or 'none (global)'}",
180
+ f"n_records: {n_records}",
181
+ f"Download date: {today}",
182
+ (f"Citation: OBIS ({year}) Ocean Biodiversity Information System. "
183
+ "Intergovernmental Oceanographic Commission of UNESCO. "
184
+ f"www.obis.org. Accessed on {today}."),
185
+ "License: CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)",
186
+ ]
187
+ meta_path = output_dir / f"download_metadata_OBIS_{safe_name}.txt"
188
+ try:
189
+ meta_path.write_text("\n".join(lines), encoding="utf-8")
190
+ logger.info("Metadados gravados: %s", meta_path)
191
+ except OSError as e:
192
+ logger.error(
193
+ "Falha ao gravar metadados: %s\n Skill anterior: ecological-data-foundation", e,
194
+ )
195
+ raise
196
+
197
+
198
+ # ── Main download logic ────────────────────────────────────────────────────────
199
+
200
+ def download_species(species_name: str, output_dir: Path,
201
+ year_from: int, year_to: int,
202
+ wkt_geometry: str | None) -> None:
203
+ logger.info("--- Iniciando download OBIS: %s ---", species_name)
204
+ today_str = date.today().strftime("%Y%m%d")
205
+ safe_name = species_name.replace(" ", "_")
206
+
207
+ log_step(1, f"Buscar registros OBIS para '{species_name}'")
208
+ records = fetch_obis(species_name, year_from, year_to, wkt_geometry)
209
+ logger.info("Registros brutos recuperados: %d", len(records))
210
+
211
+ if not records:
212
+ logger.warning("Nenhum registro OBIS encontrado para '%s'.", species_name)
213
+ return
214
+
215
+ log_step(2, "Aplicar filtros de qualidade OBIS")
216
+ records = apply_quality_flags(records)
217
+ logger.info("Registros apos filtros OBIS: %d", len(records))
218
+
219
+ log_step(3, "Padronizar registros para schema de saida")
220
+ df = standardise_records(records, species_name)
221
+ n_final = len(df)
222
+ logger.info("Registros com coordenadas validas: %d", n_final)
223
+
224
+ if n_final < 30:
225
+ logger.warning(
226
+ "Registros insuficientes para analise confiavel (n = %d). Considere relaxar filtros.",
227
+ n_final,
228
+ )
229
+
230
+ log_step(4, "Gravar CSV de ocorrencias")
231
+ csv_path = output_dir / f"occurrences_raw_OBIS_{safe_name}_{today_str}.csv"
232
+ try:
233
+ df.to_csv(csv_path, index=False)
234
+ logger.info("Gravado: %s (%d registros)", csv_path, n_final)
235
+ except OSError as e:
236
+ logger.error(
237
+ "Falha ao gravar CSV: %s\n Skill anterior: ecological-data-foundation", e,
238
+ )
239
+ raise
240
+
241
+ log_step(5, "Gravar metadados do download")
242
+ save_metadata(output_dir, species_name, n_final, year_from, year_to, wkt_geometry)
243
+
244
+
245
+ # ── Entry point ────────────────────────────────────────────────────────────────
246
+
247
+ def main():
248
+ logger.info("Script: download_from_obis.py | Skill: %s", SKILL_NAME)
249
+
250
+ argv = sys.argv[1:]
251
+
252
+ if len(argv) < 2:
253
+ species_input = "Chelonia mydas"
254
+ output_dir = Path("output/obis")
255
+ year_from = 1950
256
+ year_to = date.today().year
257
+ wkt_geometry = None
258
+ logger.warning("Menos de 2 argumentos fornecidos. Usando valores padrao para teste.")
259
+ else:
260
+ species_input = argv[0]
261
+ output_dir = Path(argv[1])
262
+ year_from = int(argv[2]) if len(argv) >= 3 else 1950
263
+ year_to = int(argv[3]) if len(argv) >= 4 else date.today().year
264
+ wkt_geometry = argv[4] if len(argv) >= 5 and argv[4] else None
265
+
266
+ logger.info("Species input : %s", species_input)
267
+ logger.info("Output dir : %s", output_dir)
268
+ logger.info("Year range : %d - %d", year_from, year_to)
269
+ logger.info("WKT geometry : %s", wkt_geometry or "nenhum (global)")
270
+
271
+ log_decision("absence", "exclude",
272
+ "apenas registros de presenca confirmada")
273
+ log_decision("quality_flags", str(BAD_FLAGS),
274
+ "registros com flags de qualidade OBIS sao removidos")
275
+
276
+ output_dir.mkdir(parents=True, exist_ok=True)
277
+
278
+ # Build species list
279
+ log_step(0, "Construir lista de especies")
280
+ if species_input.endswith(".csv") and Path(species_input).exists():
281
+ try:
282
+ df_sp = pd.read_csv(species_input)
283
+ if "scientificName" not in df_sp.columns:
284
+ logger.error(
285
+ "Coluna 'scientificName' nao encontrada em: %s\n Skill anterior: ecological-data-foundation",
286
+ species_input,
287
+ )
288
+ sys.exit(1)
289
+ species_list = df_sp["scientificName"].dropna().unique().tolist()
290
+ logger.info("Modo batch: %d especies carregadas", len(species_list))
291
+ except Exception as e:
292
+ logger.error(
293
+ "Falha ao ler lista de especies: %s\n Skill anterior: ecological-data-foundation", e,
294
+ )
295
+ sys.exit(1)
296
+ else:
297
+ species_list = [species_input.strip()]
298
+ logger.info("Modo especie unica: %s", species_list[0])
299
+
300
+ for sp in species_list:
301
+ try:
302
+ download_species(sp, output_dir, year_from, year_to, wkt_geometry)
303
+ except FileNotFoundError as e:
304
+ logger.error(
305
+ "Arquivo de entrada nao encontrado ao processar '%s': %s\n Skill anterior: ecological-data-foundation",
306
+ sp, e,
307
+ )
308
+ except Exception as e:
309
+ logger.error(
310
+ "Falha ao baixar '%s' do OBIS: %s\n Causa provavel: problema de rede ou especie nao encontrada.\n Skill anterior: ecological-data-foundation",
311
+ sp, e,
312
+ )
313
+
314
+ logger.info("Todos os downloads OBIS concluidos. Verifique: %s", output_dir)
315
+
316
+
317
+ if __name__ == "__main__":
318
+ main()
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: ecological-impact-assessment
3
+ description: "Quantifies ecological impacts using BACI designs, landscape fragmentation metrics, and pressure indices. Use this skill when the user mentions BACI analysis, before-after-control-impact, impact assessment, disturbance effects, land use change impacts, fragmentation metrics, landscape metrics, pressure or threat indices, intervention effectiveness, management outcome evaluation, or control-impact comparisons."
4
+ skill_version: 1.0.0
5
+ ---
6
+
7
+ # Skill: ecological-impact-assessment
8
+
9
+ **Domain:** BACI · Before/After · Fragmentation · Pressure · Impact synthesis
10
+ **Phase:** 2 — Modeling
11
+ **Used by:** assess-ecological-impact, build-fire-risk-map, analyze-environmental-change
12
+
13
+ ---
14
+
15
+ ## Purpose
16
+
17
+ Guides the agent through the design and analysis of ecological impact assessments: BACI frameworks, before/after comparisons, landscape fragmentation analysis, anthropogenic pressure quantification, and synthesis of impact magnitude.
18
+
19
+ ---
20
+
21
+ ## When to Invoke
22
+
23
+ - Quantifying the effect of a disturbance, land-use change, or management intervention
24
+ - Designing a BACI study or interpreting BACI data
25
+ - Computing landscape fragmentation or connectivity metrics
26
+ - Building a composite pressure or threat index
27
+
28
+ ---
29
+
30
+ ## Inputs
31
+
32
+ | Input | Format | Required |
33
+ |-------|--------|----------|
34
+ | Ecological indicator time series (control and impact sites) | CSV | Yes |
35
+ | Disturbance event date(s) | Text / date | Yes |
36
+ | Land cover maps (pre/post) | GeoTIFF | Conditional |
37
+ | Pressure / threat layers | GeoTIFF, SHP | Optional |
38
+
39
+ ---
40
+
41
+ ## Outputs
42
+
43
+ | Output | Description |
44
+ |--------|-------------|
45
+ | `baci_results.csv` | BACI interaction term with CI and p-value |
46
+ | `fragmentation_metrics.csv` | Patch area, perimeter, connectivity indices |
47
+ | `pressure_index.tif` | Composite pressure map |
48
+ | `impact_synthesis.md` | Narrative summary of impact magnitude |
49
+ | `impact_plots/` | Before/after and control/impact comparison plots |
50
+
51
+ ---
52
+
53
+ ## Steps
54
+
55
+ ### 1. Define the Impact Hypothesis
56
+ - Identify the disturbance and its expected effect (direction, magnitude, timing)
57
+ - Define the ecological indicator(s) to measure
58
+ - Define control sites (unaffected by disturbance, similar baseline conditions)
59
+
60
+ ### 2. BACI Design Assessment
61
+ - Verify: measurements Before AND After disturbance, at Control AND Impact sites
62
+ - Compute the BACI interaction: (After−Before)_Impact − (After−Before)_Control
63
+ - If multiple control or impact sites: use mixed model with site as random effect
64
+ - Test for parallel trends in the pre-disturbance period (assumption check)
65
+
66
+ ### 3. Statistical Analysis
67
+ - Linear model: `indicator ~ period * treatment + (1|site)`
68
+ - The interaction term (`period:treatment`) is the BACI effect
69
+ - Report coefficient, 95% CI, and p-value for the interaction
70
+ - Effect size: Cohen's d or partial η²
71
+
72
+ ### 4. Landscape Fragmentation (if applicable)
73
+ - Compute patch-level metrics: area, perimeter/area ratio, shape index
74
+ - Compute landscape-level metrics: largest patch index, edge density, COHESION, MESH
75
+ - Compare pre/post land cover using transition matrix
76
+ - Report habitat loss and fragmentation separately
77
+
78
+ ### 5. Pressure Index (if applicable)
79
+ - Identify pressure layers: road density, agricultural intensity, human footprint, fire frequency
80
+ - Normalise each layer (0–1)
81
+ - Compute weighted sum (document weights and rationale)
82
+ - Classify pressure zones (low / moderate / high / critical)
83
+
84
+ ### 6. Synthesis
85
+ - Combine BACI results, fragmentation metrics, and pressure index
86
+ - Classify overall impact as: none / minor / moderate / major / critical
87
+ - Describe spatial distribution of impact
88
+ - Identify most impacted areas and most important drivers
89
+
90
+ ---
91
+
92
+ ## Key Decisions to Document
93
+
94
+ - Control site selection criteria
95
+ - BACI model specification (fixed vs. random effects)
96
+ - Fragmentation metrics chosen and software version
97
+ - Pressure layer sources and weighting scheme
98
+ - Impact classification thresholds
99
+
100
+ ---
101
+
102
+ ## Tools and Libraries
103
+
104
+ **R:** `lme4`, `emmeans`, `landscapemetrics`, `lsm`, `ggplot2`
105
+ **Python:** `pylandstats`, `statsmodels`, `rasterio`
106
+ **CLI:** FRAGSTATS (landscape metrics)
107
+
108
+ ---
109
+
110
+ ## Resources
111
+
112
+ - `resources/baci-design-guide.md` — BACI design requirements and common pitfalls
113
+ - `resources/fragmentation-metrics-reference.md` — FRAGSTATS metric definitions
114
+ - `resources/pressure-index-template.md` — standard pressure layer sources
115
+ - `examples/impact/` — worked BACI and fragmentation example
116
+
117
+ ---
118
+
119
+ ## Notes
120
+
121
+ - BACI requires pre-disturbance data; post-hoc designs without pre-disturbance are not BACI
122
+ - Multiple control sites are strongly preferred; single control site severely limits inference
123
+ - Landscape metrics are scale-dependent; always report the spatial resolution used
@@ -0,0 +1,32 @@
1
+ # Example Invocation Prompts — ecological-impact-assessment
2
+
3
+ ## BACI Analysis
4
+
5
+ ```
6
+ Load skill: ecological-impact-assessment
7
+ Task: BACI analysis of deforestation impact on stream macroinvertebrate richness.
8
+
9
+ Data: data/baci_invertebrates.csv
10
+ Columns: site_id, treatment (control/impact), period (before/after), richness (count), date
11
+
12
+ Model: richness ~ period * treatment + (1|site), family = negative binomial
13
+ Check parallel trends using pre-disturbance data only.
14
+ Report: BACI interaction estimate, 95% CI, p-value, % change on original scale.
15
+ Generate: before/after comparison plots for control and impact sites.
16
+ ```
17
+
18
+ ## Fragmentation Analysis
19
+
20
+ ```
21
+ Load skill: ecological-impact-assessment
22
+ Task: Compute landscape fragmentation metrics for Atlantic Forest before (2000) and after (2022) period.
23
+
24
+ Input layers:
25
+ - data/landcover_2000.tif (MapBiomas class 3 = Atlantic Forest)
26
+ - data/landcover_2022.tif (same classification)
27
+ - data/spatial/study_area.shp
28
+
29
+ Metrics: total area (ha), number of patches, mean patch size, largest patch index,
30
+ edge density, COHESION, MESH (effective mesh size).
31
+ Report change between years. Output: fragmentation_metrics.csv, fragmentation_plots.png
32
+ ```
@@ -0,0 +1,55 @@
1
+ # BACI Design Requirements and Common Pitfalls
2
+
3
+ ## Core Requirements
4
+
5
+ BACI (Before-After-Control-Impact) is the gold standard for ecological impact assessment.
6
+
7
+ ### Minimum design:
8
+ - **Before:** ≥ 1 sampling period before the disturbance
9
+ - **After:** ≥ 1 sampling period after the disturbance
10
+ - **Control:** ≥ 1 site not affected by the disturbance, similar to impact site
11
+ - **Impact:** ≥ 1 site affected by the disturbance
12
+
13
+ ### Recommended design (for inference):
14
+ - Multiple control sites (≥ 3 preferred)
15
+ - Multiple impact sites (≥ 3 preferred)
16
+ - ≥ 3 time points before and after
17
+ - Sites monitored for the same duration before and after
18
+
19
+ ## Statistical Model
20
+
21
+ ```
22
+ indicator ~ period * treatment + (1 | site)
23
+ ```
24
+
25
+ - `period`: factor with levels `before` / `after`
26
+ - `treatment`: factor with levels `control` / `impact`
27
+ - `period:treatment`: **the BACI interaction — the effect of interest**
28
+ - `(1 | site)`: random intercept for repeated measures at sites
29
+
30
+ The BACI effect = difference in before-after change between impact and control sites.
31
+
32
+ ## Assumptions
33
+
34
+ 1. **Parallel trends:** Control and impact sites had similar trajectories *before* the disturbance. **Test this** by checking the significance of `time × treatment` interaction in the pre-disturbance period only.
35
+ 2. **No spillover:** The disturbance does not affect control sites (e.g., no upstream pollution affecting downstream controls).
36
+ 3. **Independence among sites** (partially relaxed by random effects).
37
+
38
+ ## Common Pitfalls
39
+
40
+ | Pitfall | Consequence | Fix |
41
+ |---------|------------|-----|
42
+ | Only one control site | Cannot distinguish site effect from treatment effect | Use ≥ 3 control sites |
43
+ | No pre-disturbance data | Cannot implement BACI; only Before-After | Document limitation explicitly |
44
+ | Control site affected by disturbance | BACI estimate biased toward zero | Verify spatial separation |
45
+ | Very short pre-disturbance baseline | Parallel trend assumption unverifiable | Report as limitation |
46
+ | Pseudoreplication (subsamples as sites) | Inflated degrees of freedom, false significance | Use random effects or aggregate |
47
+ | No random effects for repeated sites | Underestimated standard errors | Always include `(1|site)` |
48
+
49
+ ## Effect Size Reporting
50
+
51
+ For the BACI interaction (β_BACI):
52
+
53
+ - **Linear scale (Gaussian):** Report β, 95% CI, and Cohen's d
54
+ - **Log scale (Poisson/lognormal):** Back-transform: exp(β) = multiplicative factor
55
+ - **Example:** β_BACI = -0.48 on log scale → impact sites had exp(−0.48) = 0.62× the abundance of control sites after disturbance, i.e. a 38% reduction
@@ -0,0 +1,86 @@
1
+ # Landscape Fragmentation Metrics Reference
2
+
3
+ Computed with `landscapemetrics` (R) or FRAGSTATS. Always report the spatial resolution used.
4
+
5
+ ## Patch-Level Metrics
6
+
7
+ | Metric | Abbreviation | Unit | Description |
8
+ |--------|-------------|------|-------------|
9
+ | Patch area | AREA | ha | Area of each patch |
10
+ | Perimeter | PERIM | m | Total perimeter of patch |
11
+ | Perimeter-area ratio | PARA | m/ha | Shape complexity; higher = more irregular |
12
+ | Shape index | SHAPE | — | PERIM / (4√AREA); 1 = square; higher = complex |
13
+ | Fractal dimension index | FRAC | — | 1–2; higher = more complex shape |
14
+ | Core area | CORE | ha | Area > edge distance from patch edge |
15
+ | Core area index | CAI | % | % of patch that is core area |
16
+ | Proximity index | PROX | — | Area/distance to nearest same-class patch |
17
+
18
+ ## Class-Level Metrics
19
+
20
+ | Metric | Abbreviation | Unit | Description |
21
+ |--------|-------------|------|-------------|
22
+ | Total class area | CA | ha | Sum of all patch areas |
23
+ | Number of patches | NP | count | Count of patches |
24
+ | Patch density | PD | n/100 ha | NP per 100 ha landscape |
25
+ | Largest patch index | LPI | % | % of landscape in largest patch |
26
+ | Mean patch area | AREA_MN | ha | Average patch size |
27
+ | Mean patch shape index | SHAPE_MN | — | Average patch shape complexity |
28
+ | Edge density | ED | m/ha | Total edge length per ha |
29
+ | Mean nearest-neighbour distance | ENN_MN | m | Isolation between patches |
30
+ | Aggregation index | AI | % | 0–100; 100 = fully aggregated |
31
+ | Splitting index | SPLIT | — | Fragmentation; 1 = unfragmented |
32
+ | Effective mesh size | MESH | ha | Probability two random points in same patch × landscape area |
33
+ | Landscape shape index | LSI | — | Total edge / min edge for same total area |
34
+
35
+ ## Connectivity Metrics
36
+
37
+ | Metric | Description | R package |
38
+ |--------|-------------|----------|
39
+ | COHESION | Patch cohesion index; physical connectedness | landscapemetrics |
40
+ | CONNECT | Functional connectivity based on distance threshold | landscapemetrics |
41
+ | IIC | Integral Index of Connectivity (graph-based) | conefor / igraph |
42
+ | PC | Probability of Connectivity | conefor |
43
+ | dPC, dIIC | Importance of each patch to overall connectivity | conefor |
44
+
45
+ ## Key Metrics for Impact Assessment
46
+
47
+ Recommended minimum set for before/after comparison:
48
+
49
+ 1. **CA** — total habitat area (has it declined?)
50
+ 2. **NP** — number of patches (has fragmentation increased?)
51
+ 3. **LPI** — largest patch index (is the main mass intact?)
52
+ 4. **ED** — edge density (are edge effects increasing?)
53
+ 5. **MESH** — effective mesh size (functional fragmentation — most comprehensive single metric)
54
+ 6. **COHESION** — physical connectivity of patches
55
+
56
+ ## R Code Template
57
+
58
+ ```r
59
+ library(terra)
60
+ library(landscapemetrics)
61
+
62
+ lc <- rast("landcover.tif")
63
+ habitat_class <- 3 # class code for the habitat of interest
64
+
65
+ # Patch-level
66
+ patch_metrics <- calculate_lsm(lc, what = c("lsm_p_area", "lsm_p_shape", "lsm_p_core"),
67
+ class = habitat_class)
68
+
69
+ # Class-level
70
+ class_metrics <- calculate_lsm(lc, what = c("lsm_c_ca", "lsm_c_np", "lsm_c_lpi",
71
+ "lsm_c_ed", "lsm_c_mesh", "lsm_c_cohesion"),
72
+ class = habitat_class)
73
+ print(class_metrics)
74
+ ```
75
+
76
+ ## Interpreting MESH (Effective Mesh Size)
77
+
78
+ MESH (m_eff) = Σ(Ai²) / A_total
79
+
80
+ where Ai = area of patch i, A_total = total landscape area.
81
+
82
+ - **High MESH** = landscape dominated by large, connected patches → low functional fragmentation
83
+ - **Low MESH** = landscape cut into many small patches → high fragmentation
84
+ - MESH is the **most recommended single metric** for summarising fragmentation because it is sensitive to both patch size and patch number and is interpretable in area units (ha).
85
+
86
+ Example: MESH drops from 5,000 ha to 800 ha → a person starting at a random point is now 6× more likely to be confined to a smaller patch.