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.
- package/AGENT_CONTEXT.md +191 -0
- package/CATALOG.md +329 -0
- package/LICENSE +692 -0
- package/README.md +347 -0
- package/bin/install.mjs +168 -0
- package/docs/comparison-with-alternatives.md +38 -0
- package/docs/global-examples-index.md +103 -0
- package/docs/repository-statistics.md +101 -0
- package/docs/theoretical-foundations.md +188 -0
- package/environment.yaml +106 -0
- package/examples/community/arctic_tundra_vegetation_example.md +247 -0
- package/examples/community/bird_landuse_example.md +63 -0
- package/examples/community/phytoplankton_reservoir_example.md +60 -0
- package/examples/community/reef_fish_indopacific_example.md +221 -0
- package/examples/impact/baci_road_example.md +57 -0
- package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
- package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
- package/examples/occupancy/puma_camera_example.md +61 -0
- package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
- package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
- package/examples/sdm/anteater_cerrado_example.md +69 -0
- package/examples/sdm/jaguar_amazon_example.md +80 -0
- package/examples/sdm/koala_climate_change_example.md +170 -0
- package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
- package/package.json +43 -0
- package/renv.lock +194 -0
- package/skills/SKILL_INDEX.json +1020 -0
- package/skills/acoustic-monitoring/SKILL.md +163 -0
- package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
- package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
- package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
- package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
- package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
- package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
- package/skills/biostatistics-workbench/SKILL.md +140 -0
- package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
- package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
- package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
- package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
- package/skills/camera-trap-processing/SKILL.md +159 -0
- package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
- package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
- package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
- package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
- package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
- package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
- package/skills/community-ecology-ordination/SKILL.md +133 -0
- package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
- package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
- package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
- package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
- package/skills/ecological-data-foundation/SKILL.md +129 -0
- package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
- package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
- package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
- package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
- package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
- package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
- package/skills/ecological-impact-assessment/SKILL.md +123 -0
- package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
- package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
- package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
- package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
- package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
- package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
- package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
- package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
- package/skills/ecosystem-services-assessment/SKILL.md +125 -0
- package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
- package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
- package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
- package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
- package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
- package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
- package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
- package/skills/environmental-time-series/SKILL.md +125 -0
- package/skills/environmental-time-series/examples/example-prompts.md +33 -0
- package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
- package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
- package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
- package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
- package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
- package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
- package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
- package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
- package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
- package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
- package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
- package/skills/landscape-connectivity/SKILL.md +170 -0
- package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
- package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
- package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
- package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
- package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
- package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
- package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
- package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
- package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
- package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
- package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
- package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
- package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
- package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
- package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
- package/skills/occupancy-and-detection/SKILL.md +126 -0
- package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
- package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
- package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
- package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
- package/skills/population-viability-analysis/SKILL.md +161 -0
- package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
- package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
- package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
- package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
- package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
- package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
- package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
- package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
- package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
- package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
- package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
- package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
- package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
- package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
- package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
- package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
- package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
- package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
- package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
- package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
- package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
- package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
- package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
- package/skills/spatial-prioritization/SKILL.md +162 -0
- package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
- package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
- package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
- package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
- package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
- package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
- package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
- package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
- package/skills/species-distribution-modeling/SKILL.md +139 -0
- package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
- package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
- package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
- package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
- package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
- package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
- package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
- package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
- package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
- package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
- package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
- package/templates/SKILL_TEMPLATE.md +225 -0
- package/templates/checklists/data-submission-checklist.md +38 -0
- package/templates/checklists/post-analysis-checklist.md +55 -0
- package/templates/checklists/pre-analysis-checklist.md +31 -0
- package/templates/prompts/debug-skill.md +47 -0
- package/templates/prompts/invoke-skill.md +34 -0
- package/templates/prompts/invoke-workflow.md +45 -0
- package/templates/reports/technical-report-template.md +80 -0
- package/templates/scripts/logger_setup.R +79 -0
- package/templates/scripts/logger_setup.py +119 -0
- package/templates/scripts/params_loader.R +28 -0
- package/templates/scripts/params_loader.py +38 -0
- package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
- package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
- package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
- package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
- package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
- package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
- package/workflows/produce-technical-report/WORKFLOW.md +113 -0
- package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
- package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
- package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
- package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
- package/workflows/run-population-viability/WORKFLOW.md +90 -0
- package/workflows/run-sdm-study/WORKFLOW.md +99 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
"""Download occurrence records from iNaturalist via pyinaturalist.
|
|
5
|
+
|
|
6
|
+
Usage: python download_from_inat.py <species_name_or_list_csv> <output_dir> [year_from] [year_to] [quality_grade]
|
|
7
|
+
|
|
8
|
+
Arguments:
|
|
9
|
+
species_name_or_list_csv : Species name (e.g., "Panthera onca") or path to CSV
|
|
10
|
+
with column 'scientificName'
|
|
11
|
+
output_dir : Directory for outputs (created if absent)
|
|
12
|
+
year_from : Minimum observation year (default: 2000)
|
|
13
|
+
year_to : Maximum observation year (default: current year)
|
|
14
|
+
quality_grade : 'research' or 'any' (default: 'research')
|
|
15
|
+
|
|
16
|
+
Outputs (per species):
|
|
17
|
+
occurrences_raw_iNat_{species}_{date}.csv — standardised occurrence records
|
|
18
|
+
download_metadata_iNat_{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
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
SKILL_NAME = "ecological-data-foundation"
|
|
32
|
+
_LOG_DIR = Path("logs")
|
|
33
|
+
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
_log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
35
|
+
logging.basicConfig(
|
|
36
|
+
level=logging.INFO,
|
|
37
|
+
format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
|
|
38
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
39
|
+
handlers=[
|
|
40
|
+
logging.StreamHandler(sys.stdout),
|
|
41
|
+
logging.FileHandler(_log_file, encoding="utf-8"),
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
logger = logging.getLogger(SKILL_NAME)
|
|
45
|
+
|
|
46
|
+
def log_step(n: int, desc: str) -> None:
|
|
47
|
+
logger.info("-- STEP %d: %s", n, desc)
|
|
48
|
+
|
|
49
|
+
def log_decision(var: str, val, why: str) -> None:
|
|
50
|
+
logger.info("DECISION | %s = %s | %s", var, val, why)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
from datetime import date
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
import pandas as pd
|
|
57
|
+
import pyinaturalist
|
|
58
|
+
from pyinaturalist import get_observations
|
|
59
|
+
except ImportError as e:
|
|
60
|
+
logger.error(
|
|
61
|
+
"Dependencia ausente: %s\n Instale com: pip install pyinaturalist pandas\n Skill anterior: ecological-data-foundation",
|
|
62
|
+
e,
|
|
63
|
+
)
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ── Constants ─────────────────────────────────────────────────────────────────
|
|
68
|
+
MAX_RESULTS_PER_PAGE = 200 # iNaturalist API page size limit
|
|
69
|
+
MAX_TOTAL_RESULTS = 10000 # practical cap for single-species query
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ── Helper functions ──────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
def fetch_observations(taxon_name: str, year_from: int, year_to: int,
|
|
75
|
+
quality_grade: str) -> list[dict]:
|
|
76
|
+
"""Fetch all research-grade observations for a taxon, handling pagination."""
|
|
77
|
+
all_results = []
|
|
78
|
+
page = 1
|
|
79
|
+
while True:
|
|
80
|
+
try:
|
|
81
|
+
response = get_observations(
|
|
82
|
+
taxon_name=taxon_name,
|
|
83
|
+
quality_grade=quality_grade,
|
|
84
|
+
geo=True,
|
|
85
|
+
captive=False,
|
|
86
|
+
d1=f"{year_from}-01-01",
|
|
87
|
+
d2=f"{year_to}-12-31",
|
|
88
|
+
per_page=MAX_RESULTS_PER_PAGE,
|
|
89
|
+
page=page,
|
|
90
|
+
order="desc",
|
|
91
|
+
order_by="created_at",
|
|
92
|
+
)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(
|
|
95
|
+
"Falha em get_observations (pagina=%d, taxon='%s'): %s\n Causa provavel: sem conexao com a internet ou API iNaturalist indisponivel.\n Skill anterior: ecological-data-foundation",
|
|
96
|
+
page, taxon_name, e,
|
|
97
|
+
)
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
results = response.get("results", [])
|
|
101
|
+
if not results:
|
|
102
|
+
break
|
|
103
|
+
all_results.extend(results)
|
|
104
|
+
logger.info("Pagina %d: %d registros recuperados (total acumulado: %d)",
|
|
105
|
+
page, len(results), len(all_results))
|
|
106
|
+
|
|
107
|
+
total_results = response.get("total_results", 0)
|
|
108
|
+
if len(all_results) >= min(total_results, MAX_TOTAL_RESULTS):
|
|
109
|
+
break
|
|
110
|
+
if len(results) < MAX_RESULTS_PER_PAGE:
|
|
111
|
+
break
|
|
112
|
+
page += 1
|
|
113
|
+
|
|
114
|
+
return all_results
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def standardise_records(observations: list[dict], species_name: str) -> "pd.DataFrame":
|
|
118
|
+
"""Convert iNaturalist observation dicts to the standard occurrence schema."""
|
|
119
|
+
rows = []
|
|
120
|
+
for obs in observations:
|
|
121
|
+
loc = obs.get("location", "")
|
|
122
|
+
lat, lon = None, None
|
|
123
|
+
if loc and "," in str(loc):
|
|
124
|
+
parts = str(loc).split(",")
|
|
125
|
+
try:
|
|
126
|
+
lat, lon = float(parts[0]), float(parts[1])
|
|
127
|
+
except ValueError:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
taxon = obs.get("taxon", {}) or {}
|
|
131
|
+
place = obs.get("place_guess", "")
|
|
132
|
+
|
|
133
|
+
rows.append({
|
|
134
|
+
"species": species_name,
|
|
135
|
+
"decimalLatitude": lat,
|
|
136
|
+
"decimalLongitude": lon,
|
|
137
|
+
"eventDate": obs.get("observed_on", ""),
|
|
138
|
+
"countryCode": place,
|
|
139
|
+
"basisOfRecord": "HUMAN_OBSERVATION",
|
|
140
|
+
"coordinateUncertaintyInMeters": obs.get("positional_accuracy"),
|
|
141
|
+
"datasetName": "iNaturalist",
|
|
142
|
+
"occurrenceID": str(obs.get("id", "")),
|
|
143
|
+
"source": "iNaturalist",
|
|
144
|
+
"download_doi": None,
|
|
145
|
+
})
|
|
146
|
+
df = pd.DataFrame(rows)
|
|
147
|
+
# Drop records missing coordinates
|
|
148
|
+
df = df.dropna(subset=["decimalLatitude", "decimalLongitude"])
|
|
149
|
+
return df
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def save_metadata(output_dir: Path, species_name: str, n_records: int,
|
|
153
|
+
year_from: int, year_to: int, quality_grade: str) -> None:
|
|
154
|
+
safe_name = species_name.replace(" ", "_")
|
|
155
|
+
today = date.today().isoformat()
|
|
156
|
+
year = date.today().year
|
|
157
|
+
lines = [
|
|
158
|
+
f"Species: {species_name}",
|
|
159
|
+
f"Source: iNaturalist (https://www.inaturalist.org)",
|
|
160
|
+
f"Quality grade: {quality_grade}",
|
|
161
|
+
f"Year range: {year_from} - {year_to}",
|
|
162
|
+
f"Captive excluded: True",
|
|
163
|
+
f"Geo-referenced only: True",
|
|
164
|
+
f"n_records: {n_records}",
|
|
165
|
+
f"Download date: {today}",
|
|
166
|
+
(f"Citation: iNaturalist contributors and the California Academy of Sciences ({year}). "
|
|
167
|
+
f"iNaturalist Research-grade Observations. iNaturalist.org. Accessed {today}."),
|
|
168
|
+
"License: CC BY-NC (individual records may vary; see iNaturalist for details)",
|
|
169
|
+
"Note: iNaturalist does not issue download DOIs; record the access date for reproducibility.",
|
|
170
|
+
]
|
|
171
|
+
meta_path = output_dir / f"download_metadata_iNat_{safe_name}.txt"
|
|
172
|
+
try:
|
|
173
|
+
meta_path.write_text("\n".join(lines), encoding="utf-8")
|
|
174
|
+
logger.info("Metadados gravados: %s", meta_path)
|
|
175
|
+
except OSError as e:
|
|
176
|
+
logger.error(
|
|
177
|
+
"Falha ao gravar metadados em '%s': %s\n Causa provavel: sem permissao de escrita.\n Skill anterior: ecological-data-foundation",
|
|
178
|
+
meta_path, e,
|
|
179
|
+
)
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ── Main download logic ────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
def download_species(species_name: str, output_dir: Path,
|
|
186
|
+
year_from: int, year_to: int, quality_grade: str) -> None:
|
|
187
|
+
logger.info("--- Iniciando download iNaturalist: %s ---", species_name)
|
|
188
|
+
today_str = date.today().strftime("%Y%m%d")
|
|
189
|
+
safe_name = species_name.replace(" ", "_")
|
|
190
|
+
|
|
191
|
+
log_step(1, f"Buscar observacoes iNaturalist para '{species_name}'")
|
|
192
|
+
observations = fetch_observations(species_name, year_from, year_to, quality_grade)
|
|
193
|
+
logger.info("Total de observacoes recuperadas: %d", len(observations))
|
|
194
|
+
|
|
195
|
+
if not observations:
|
|
196
|
+
logger.warning("Nenhum registro encontrado para '%s'.", species_name)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
log_step(2, "Padronizar registros para schema de saida")
|
|
200
|
+
df = standardise_records(observations, species_name)
|
|
201
|
+
n_final = len(df)
|
|
202
|
+
logger.info("Registros com coordenadas validas: %d", n_final)
|
|
203
|
+
|
|
204
|
+
if n_final < 30:
|
|
205
|
+
logger.warning(
|
|
206
|
+
"Registros insuficientes para SDM confiavel (n = %d). Considere: (1) ampliar periodo, (2) usar quality='any', (3) combinar com GBIF.",
|
|
207
|
+
n_final,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
log_step(3, "Gravar CSV de ocorrencias")
|
|
211
|
+
csv_path = output_dir / f"occurrences_raw_iNat_{safe_name}_{today_str}.csv"
|
|
212
|
+
try:
|
|
213
|
+
df.to_csv(csv_path, index=False)
|
|
214
|
+
logger.info("Gravado: %s (%d registros)", csv_path, n_final)
|
|
215
|
+
except OSError as e:
|
|
216
|
+
logger.error(
|
|
217
|
+
"Falha ao gravar CSV para '%s': %s\n Causa provavel: sem permissao de escrita em '%s'.\n Skill anterior: ecological-data-foundation",
|
|
218
|
+
species_name, e, output_dir,
|
|
219
|
+
)
|
|
220
|
+
raise
|
|
221
|
+
|
|
222
|
+
log_step(4, "Gravar metadados do download")
|
|
223
|
+
save_metadata(output_dir, species_name, n_final, year_from, year_to, quality_grade)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
def main():
|
|
229
|
+
logger.info("Script: download_from_inat.py | Skill: %s", SKILL_NAME)
|
|
230
|
+
|
|
231
|
+
argv = sys.argv[1:]
|
|
232
|
+
|
|
233
|
+
if len(argv) < 2:
|
|
234
|
+
species_input = "Panthera onca"
|
|
235
|
+
output_dir = Path("output/inat")
|
|
236
|
+
year_from = 2000
|
|
237
|
+
year_to = date.today().year
|
|
238
|
+
quality_grade = "research"
|
|
239
|
+
logger.warning("Menos de 2 argumentos fornecidos. Usando valores padrao para teste.")
|
|
240
|
+
else:
|
|
241
|
+
species_input = argv[0]
|
|
242
|
+
output_dir = Path(argv[1])
|
|
243
|
+
year_from = int(argv[2]) if len(argv) >= 3 else 2000
|
|
244
|
+
year_to = int(argv[3]) if len(argv) >= 4 else date.today().year
|
|
245
|
+
quality_grade = argv[4] if len(argv) >= 5 else "research"
|
|
246
|
+
|
|
247
|
+
logger.info("Species input : %s", species_input)
|
|
248
|
+
logger.info("Output dir : %s", output_dir)
|
|
249
|
+
logger.info("Year range : %d - %d", year_from, year_to)
|
|
250
|
+
logger.info("Quality grade : %s", quality_grade)
|
|
251
|
+
|
|
252
|
+
log_decision("quality_grade", quality_grade,
|
|
253
|
+
"research = comunidade validou ID + geo; recomendado para SDM")
|
|
254
|
+
log_decision("captive", False,
|
|
255
|
+
"excluir organismos em cativeiro/cultivados")
|
|
256
|
+
log_decision("year_from", year_from,
|
|
257
|
+
"filtro temporal; 2000 equilibra dataset e qualidade de GPS")
|
|
258
|
+
|
|
259
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
|
|
261
|
+
# Build species list
|
|
262
|
+
log_step(0, "Construir lista de especies")
|
|
263
|
+
if species_input.endswith(".csv") and Path(species_input).exists():
|
|
264
|
+
try:
|
|
265
|
+
df_sp = pd.read_csv(species_input)
|
|
266
|
+
if "scientificName" not in df_sp.columns:
|
|
267
|
+
logger.error(
|
|
268
|
+
"Coluna 'scientificName' nao encontrada em: %s\n Causa provavel: CSV mal formatado.\n Skill anterior: ecological-data-foundation",
|
|
269
|
+
species_input,
|
|
270
|
+
)
|
|
271
|
+
sys.exit(1)
|
|
272
|
+
species_list = df_sp["scientificName"].dropna().unique().tolist()
|
|
273
|
+
logger.info("Modo batch: %d especies carregadas", len(species_list))
|
|
274
|
+
log_decision("mode", "batch", "CSV valido com coluna scientificName")
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.error(
|
|
277
|
+
"Falha ao ler lista de especies '%s': %s\n Skill anterior: ecological-data-foundation",
|
|
278
|
+
species_input, e,
|
|
279
|
+
)
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
else:
|
|
282
|
+
species_list = [species_input.strip()]
|
|
283
|
+
logger.info("Modo especie unica: %s", species_list[0])
|
|
284
|
+
log_decision("mode", "single_species", "argumento nao e arquivo CSV")
|
|
285
|
+
|
|
286
|
+
for sp in species_list:
|
|
287
|
+
try:
|
|
288
|
+
download_species(sp, output_dir, year_from, year_to, quality_grade)
|
|
289
|
+
except FileNotFoundError as e:
|
|
290
|
+
logger.error(
|
|
291
|
+
"Arquivo de entrada nao encontrado ao processar '%s': %s\n Esperado como saida de: ecological-data-foundation\n Skill anterior: ecological-data-foundation",
|
|
292
|
+
sp, e,
|
|
293
|
+
)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.error(
|
|
296
|
+
"Falha ao baixar '%s' do iNaturalist: %s\n Causa provavel: problema de rede ou especie nao encontrada.\n Skill anterior: ecological-data-foundation",
|
|
297
|
+
sp, e,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
logger.info("Todos os downloads iNaturalist concluidos. Verifique: %s", output_dir)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
main()
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript download_from_iucn.R <species_name_or_list_csv> <output_dir> [include_range_maps]
|
|
5
|
+
|
|
6
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
7
|
+
SKILL_NAME <- "ecological-data-foundation"
|
|
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
|
+
# species_name_or_list_csv : Species name (e.g., "Panthera onca") or path to CSV
|
|
19
|
+
# with column "scientificName"
|
|
20
|
+
# output_dir : Directory to write outputs (created if absent)
|
|
21
|
+
# include_range_maps : "TRUE" or "FALSE" — download range map data (default: FALSE)
|
|
22
|
+
#
|
|
23
|
+
# Requires:
|
|
24
|
+
# IUCN_REDLIST_KEY environment variable — obtain at: https://apiv3.iucnredlist.org/
|
|
25
|
+
#
|
|
26
|
+
# Outputs (per species):
|
|
27
|
+
# iucn_status_{species}.csv — Red List category, criteria, population trend, threats
|
|
28
|
+
# iucn_habitats_{species}.csv — suitable habitats by species
|
|
29
|
+
# download_metadata_IUCN_{species}.txt — provenance and citation
|
|
30
|
+
# If include_range_maps=TRUE:
|
|
31
|
+
# range_maps/{species}_range.gpkg — range polygons (if available via API)
|
|
32
|
+
#
|
|
33
|
+
# Standard output schema (iucn_status CSV):
|
|
34
|
+
# species, decimalLatitude, decimalLongitude, eventDate, countryCode,
|
|
35
|
+
# basisOfRecord, coordinateUncertaintyInMeters, datasetName, occurrenceID,
|
|
36
|
+
# source, download_doi
|
|
37
|
+
# + IUCN-specific: rl_category, rl_criteria, population_trend, assessment_year
|
|
38
|
+
|
|
39
|
+
suppressPackageStartupMessages(library(rredlist))
|
|
40
|
+
suppressPackageStartupMessages(library(dplyr))
|
|
41
|
+
suppressPackageStartupMessages(library(readr))
|
|
42
|
+
|
|
43
|
+
# ── 1. Parse arguments ───────────────────────────────────────────────────────
|
|
44
|
+
log_step(1, "Analisar argumentos da linha de comando")
|
|
45
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
46
|
+
|
|
47
|
+
if (length(args) < 2) {
|
|
48
|
+
species_input <- "Panthera onca"
|
|
49
|
+
output_dir <- "output/iucn"
|
|
50
|
+
include_range_maps <- FALSE
|
|
51
|
+
log_warn("Menos de 2 argumentos fornecidos. Usando valores padrao para teste.")
|
|
52
|
+
} else {
|
|
53
|
+
species_input <- args[1]
|
|
54
|
+
output_dir <- args[2]
|
|
55
|
+
include_range_maps <- ifelse(
|
|
56
|
+
length(args) >= 3 && toupper(args[3]) == "TRUE", TRUE, FALSE
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log_info("Script: download_from_iucn.R | Skill: %s", SKILL_NAME)
|
|
61
|
+
log_info("Species input : %s", species_input)
|
|
62
|
+
log_info("Output dir : %s", output_dir)
|
|
63
|
+
log_info("Include range maps : %s", include_range_maps)
|
|
64
|
+
|
|
65
|
+
log_decision("api_version", "v3", "IUCN Red List API v3 — requer chave em IUCN_REDLIST_KEY")
|
|
66
|
+
|
|
67
|
+
# ── 2. Check API key ─────────────────────────────────────────────────────────
|
|
68
|
+
log_step(2, "Verificar chave da API IUCN")
|
|
69
|
+
iucn_key <- Sys.getenv("IUCN_REDLIST_KEY")
|
|
70
|
+
if (is.null(iucn_key) || iucn_key == "") {
|
|
71
|
+
log_error(
|
|
72
|
+
"Falha em verificar chave API IUCN: variavel IUCN_REDLIST_KEY nao definida.\nCausa provavel: chave nao configurada no ambiente.\nVerifique: adicione IUCN_REDLIST_KEY ao seu .Renviron via usethis::edit_r_environ()\nSkill anterior: ecological-data-foundation"
|
|
73
|
+
)
|
|
74
|
+
stop("IUCN_REDLIST_KEY environment variable not set.")
|
|
75
|
+
}
|
|
76
|
+
log_info("Chave IUCN detectada (primeiros 4 chars): %s...", substr(iucn_key, 1, 4))
|
|
77
|
+
|
|
78
|
+
# ── 3. Create output directory ───────────────────────────────────────────────
|
|
79
|
+
log_step(3, "Criar diretorio de saida")
|
|
80
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
81
|
+
|
|
82
|
+
# ── 4. Build species list ────────────────────────────────────────────────────
|
|
83
|
+
log_step(4, "Construir lista de especies")
|
|
84
|
+
if (grepl("\\.csv$", species_input, ignore.case = TRUE) && file.exists(species_input)) {
|
|
85
|
+
tryCatch({
|
|
86
|
+
species_df <- read_csv(species_input, show_col_types = FALSE)
|
|
87
|
+
if (!"scientificName" %in% names(species_df)) {
|
|
88
|
+
log_error(
|
|
89
|
+
"Coluna 'scientificName' nao encontrada em: %s\nCausa provavel: CSV mal formatado.\nSkill anterior: ecological-data-foundation",
|
|
90
|
+
species_input
|
|
91
|
+
)
|
|
92
|
+
stop("Missing column 'scientificName'")
|
|
93
|
+
}
|
|
94
|
+
species_list <- unique(trimws(species_df$scientificName))
|
|
95
|
+
log_info("Modo batch: %d especies carregadas", length(species_list))
|
|
96
|
+
log_decision("mode", "batch", "CSV valido com coluna scientificName")
|
|
97
|
+
}, error = function(e) {
|
|
98
|
+
log_error(
|
|
99
|
+
"Falha ao ler lista de especies: %s\nSkill anterior: ecological-data-foundation",
|
|
100
|
+
conditionMessage(e)
|
|
101
|
+
)
|
|
102
|
+
stop(e)
|
|
103
|
+
})
|
|
104
|
+
} else {
|
|
105
|
+
species_list <- trimws(species_input)
|
|
106
|
+
log_info("Modo especie unica: %s", species_list)
|
|
107
|
+
log_decision("mode", "single_species", "argumento nao e arquivo CSV")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# ── 5. Download function ─────────────────────────────────────────────────────
|
|
111
|
+
download_iucn_species <- function(sp_name) {
|
|
112
|
+
log_info("--- Iniciando download IUCN: %s ---", sp_name)
|
|
113
|
+
safe_name <- gsub(" ", "_", sp_name)
|
|
114
|
+
|
|
115
|
+
# ── Fetch species assessment ───────────────────────────────────────────────
|
|
116
|
+
assessment <- tryCatch({
|
|
117
|
+
rredlist::rl_search(sp_name, key = iucn_key)
|
|
118
|
+
}, error = function(e) {
|
|
119
|
+
log_error(
|
|
120
|
+
"Falha em rl_search para '%s': %s\nCausa provavel: chave IUCN invalida ou API indisponivel.\nVerifique: https://apiv3.iucnredlist.org/\nSkill anterior: ecological-data-foundation",
|
|
121
|
+
sp_name, conditionMessage(e)
|
|
122
|
+
)
|
|
123
|
+
stop(e)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (is.null(assessment$result) || length(assessment$result) == 0) {
|
|
127
|
+
log_warn("Nenhum resultado IUCN para '%s'. Especie pode nao estar avaliada.", sp_name)
|
|
128
|
+
return(invisible(NULL))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
res <- assessment$result[[1]]
|
|
132
|
+
category <- res$category %||% NA_character_
|
|
133
|
+
criteria <- res$criteria %||% NA_character_
|
|
134
|
+
pop_trend<- res$population_trend %||% NA_character_
|
|
135
|
+
assess_yr<- res$assessment_year %||% NA_integer_
|
|
136
|
+
taxon_id <- res$taxonid %||% NA_integer_
|
|
137
|
+
|
|
138
|
+
log_info("Categoria IUCN para '%s': %s (criterios: %s, tendencia: %s, ano: %s)",
|
|
139
|
+
sp_name, category, criteria, pop_trend, assess_yr)
|
|
140
|
+
|
|
141
|
+
if (category %in% c("CR", "EN")) {
|
|
142
|
+
log_warn("Especie '%s' e %s — dados de distribuicao podem ser restritos por razoes de seguranca.",
|
|
143
|
+
sp_name, category)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ── Fetch country occurrences ──────────────────────────────────────────────
|
|
147
|
+
country_occ <- tryCatch({
|
|
148
|
+
rredlist::rl_occ_country(sp_name, key = iucn_key)
|
|
149
|
+
}, error = function(e) {
|
|
150
|
+
log_warn("Falha ao buscar paises de ocorrencia para '%s': %s", sp_name, conditionMessage(e))
|
|
151
|
+
list(result = NULL)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
# ── Fetch habitats ─────────────────────────────────────────────────────────
|
|
155
|
+
habitats <- tryCatch({
|
|
156
|
+
rredlist::rl_habitats(sp_name, key = iucn_key)
|
|
157
|
+
}, error = function(e) {
|
|
158
|
+
log_warn("Falha ao buscar habitats para '%s': %s", sp_name, conditionMessage(e))
|
|
159
|
+
list(result = NULL)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
# ── Build standardised occurrence records (one row per country) ────────────
|
|
163
|
+
if (!is.null(country_occ$result) && length(country_occ$result) > 0) {
|
|
164
|
+
country_df <- as.data.frame(country_occ$result)
|
|
165
|
+
std <- data.frame(
|
|
166
|
+
species = sp_name,
|
|
167
|
+
decimalLatitude = NA_real_,
|
|
168
|
+
decimalLongitude = NA_real_,
|
|
169
|
+
eventDate = NA_character_,
|
|
170
|
+
countryCode = as.character(country_df$code),
|
|
171
|
+
basisOfRecord = "LITERATURE",
|
|
172
|
+
coordinateUncertaintyInMeters = NA_real_,
|
|
173
|
+
datasetName = "IUCN Red List",
|
|
174
|
+
occurrenceID = paste0("IUCN:", taxon_id, ":", as.character(country_df$code)),
|
|
175
|
+
source = "IUCN",
|
|
176
|
+
download_doi = NA_character_,
|
|
177
|
+
rl_category = category,
|
|
178
|
+
rl_criteria = criteria,
|
|
179
|
+
population_trend = pop_trend,
|
|
180
|
+
assessment_year = assess_yr,
|
|
181
|
+
stringsAsFactors = FALSE
|
|
182
|
+
)
|
|
183
|
+
} else {
|
|
184
|
+
log_warn("Sem dados de ocorrencia por pais para '%s'. Criando registro sumario.", sp_name)
|
|
185
|
+
std <- data.frame(
|
|
186
|
+
species = sp_name,
|
|
187
|
+
decimalLatitude = NA_real_,
|
|
188
|
+
decimalLongitude = NA_real_,
|
|
189
|
+
eventDate = NA_character_,
|
|
190
|
+
countryCode = NA_character_,
|
|
191
|
+
basisOfRecord = "LITERATURE",
|
|
192
|
+
coordinateUncertaintyInMeters = NA_real_,
|
|
193
|
+
datasetName = "IUCN Red List",
|
|
194
|
+
occurrenceID = paste0("IUCN:", taxon_id),
|
|
195
|
+
source = "IUCN",
|
|
196
|
+
download_doi = NA_character_,
|
|
197
|
+
rl_category = category,
|
|
198
|
+
rl_criteria = criteria,
|
|
199
|
+
population_trend = pop_trend,
|
|
200
|
+
assessment_year = assess_yr,
|
|
201
|
+
stringsAsFactors = FALSE
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Save status CSV
|
|
206
|
+
csv_path <- file.path(output_dir, paste0("iucn_status_", safe_name, ".csv"))
|
|
207
|
+
tryCatch({
|
|
208
|
+
write_csv(std, csv_path)
|
|
209
|
+
log_info("Gravado: %s (%d linhas)", csv_path, nrow(std))
|
|
210
|
+
}, error = function(e) {
|
|
211
|
+
log_error(
|
|
212
|
+
"Falha ao gravar CSV para '%s': %s\nSkill anterior: ecological-data-foundation",
|
|
213
|
+
sp_name, conditionMessage(e)
|
|
214
|
+
)
|
|
215
|
+
stop(e)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
# Save habitats CSV
|
|
219
|
+
if (!is.null(habitats$result) && length(habitats$result) > 0) {
|
|
220
|
+
hab_df <- as.data.frame(habitats$result)
|
|
221
|
+
hab_df$species <- sp_name
|
|
222
|
+
hab_path <- file.path(output_dir, paste0("iucn_habitats_", safe_name, ".csv"))
|
|
223
|
+
tryCatch({
|
|
224
|
+
write_csv(hab_df, hab_path)
|
|
225
|
+
log_info("Habitats gravados: %s", hab_path)
|
|
226
|
+
}, error = function(e) {
|
|
227
|
+
log_warn("Falha ao gravar habitats para '%s': %s", sp_name, conditionMessage(e))
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Save metadata
|
|
232
|
+
meta_lines <- c(
|
|
233
|
+
paste("Species:", sp_name),
|
|
234
|
+
paste("IUCN Taxon ID:", taxon_id),
|
|
235
|
+
paste("Source: IUCN Red List (https://www.iucnredlist.org)"),
|
|
236
|
+
paste("API version: v3 (https://apiv3.iucnredlist.org)"),
|
|
237
|
+
paste("Red List category:", category),
|
|
238
|
+
paste("Assessment year:", assess_yr),
|
|
239
|
+
paste("Population trend:", pop_trend),
|
|
240
|
+
paste("Download date:", Sys.Date()),
|
|
241
|
+
paste("Citation: IUCN", format(Sys.Date(), "%Y"), ". The IUCN Red List of Threatened Species. Version",
|
|
242
|
+
format(Sys.Date(), "%Y-%m"), ". https://www.iucnredlist.org Accessed on", Sys.Date()),
|
|
243
|
+
paste("License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)")
|
|
244
|
+
)
|
|
245
|
+
meta_path <- file.path(output_dir, paste0("download_metadata_IUCN_", safe_name, ".txt"))
|
|
246
|
+
tryCatch({
|
|
247
|
+
writeLines(meta_lines, meta_path)
|
|
248
|
+
log_info("Gravado: %s", meta_path)
|
|
249
|
+
}, error = function(e) {
|
|
250
|
+
log_warn("Falha ao gravar metadados para '%s': %s", sp_name, conditionMessage(e))
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
return(invisible(csv_path))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Null-coalescing operator
|
|
257
|
+
`%||%` <- function(a, b) if (!is.null(a) && !is.na(a) && length(a) > 0) a else b
|
|
258
|
+
|
|
259
|
+
# ── 6. Run for all species ───────────────────────────────────────────────────
|
|
260
|
+
log_step(5, "Executar download IUCN para todas as especies")
|
|
261
|
+
for (sp in species_list) {
|
|
262
|
+
tryCatch(
|
|
263
|
+
download_iucn_species(sp),
|
|
264
|
+
error = function(e) {
|
|
265
|
+
log_error(
|
|
266
|
+
"Falha ao baixar '%s' do IUCN: %s\nCausa provavel: chave invalida, especie nao avaliada ou API indisponivel.\nSkill anterior: ecological-data-foundation",
|
|
267
|
+
sp, conditionMessage(e)
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
log_info("Todos os downloads IUCN concluidos. Verifique: %s", output_dir)
|