@nahisaho/satori 0.14.0 → 0.16.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/README.md +72 -30
- package/package.json +1 -1
- package/src/.github/skills/scientific-advanced-imaging/SKILL.md +382 -0
- package/src/.github/skills/scientific-chembl-assay-mining/SKILL.md +509 -0
- package/src/.github/skills/scientific-data-submission/SKILL.md +357 -0
- package/src/.github/skills/scientific-deep-chemistry/SKILL.md +350 -0
- package/src/.github/skills/scientific-ensembl-genomics/SKILL.md +378 -0
- package/src/.github/skills/scientific-expression-comparison/SKILL.md +303 -0
- package/src/.github/skills/scientific-gpu-singlecell/SKILL.md +296 -0
- package/src/.github/skills/scientific-marine-ecology/SKILL.md +429 -0
- package/src/.github/skills/scientific-md-simulation/SKILL.md +315 -0
- package/src/.github/skills/scientific-model-organism-db/SKILL.md +329 -0
- package/src/.github/skills/scientific-nci60-screening/SKILL.md +307 -0
- package/src/.github/skills/scientific-perturbation-analysis/SKILL.md +297 -0
- package/src/.github/skills/scientific-plant-biology/SKILL.md +321 -0
- package/src/.github/skills/scientific-rrna-taxonomy/SKILL.md +379 -0
- package/src/.github/skills/scientific-scatac-signac/SKILL.md +300 -0
- package/src/.github/skills/scientific-scvi-integration/SKILL.md +344 -0
- package/src/.github/skills/scientific-string-network-api/SKILL.md +376 -0
- package/src/.github/skills/scientific-toxicology-env/SKILL.md +309 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scientific-marine-ecology
|
|
3
|
+
description: |
|
|
4
|
+
海洋生態学統合スキル。OBIS 海洋生物分布・WoRMS 海洋分類体系・
|
|
5
|
+
GBIF 生物多様性レコード・FishBase 魚類データ。ToolUniverse
|
|
6
|
+
連携: obis, worms, gbif。
|
|
7
|
+
tu_tools:
|
|
8
|
+
- key: obis
|
|
9
|
+
name: OBIS (Ocean Biodiversity Information System)
|
|
10
|
+
description: 海洋生物の出現・分布データを提供する国際プラットフォーム
|
|
11
|
+
- key: worms
|
|
12
|
+
name: WoRMS (World Register of Marine Species)
|
|
13
|
+
description: 海洋生物種の権威ある分類学的参照データベース
|
|
14
|
+
- key: gbif
|
|
15
|
+
name: GBIF (Global Biodiversity Information Facility)
|
|
16
|
+
description: 生物多様性データの国際的なオープンアクセスインフラ
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Scientific Marine Ecology
|
|
20
|
+
|
|
21
|
+
OBIS / WoRMS / GBIF / FishBase を活用した海洋生物多様性・
|
|
22
|
+
分布解析パイプラインを提供する。
|
|
23
|
+
|
|
24
|
+
## When to Use
|
|
25
|
+
|
|
26
|
+
- 海洋生物の地理的分布データを取得するとき
|
|
27
|
+
- 海洋生物の分類学的情報 (WoRMS) を検証するとき
|
|
28
|
+
- 生物多様性ホットスポットを解析するとき
|
|
29
|
+
- 魚類の生態・形態データ (FishBase) を取得するとき
|
|
30
|
+
- 海洋保全区域の生物多様性評価を行うとき
|
|
31
|
+
- 海洋環境変動と種分布の関係を分析するとき
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
## 1. OBIS 海洋生物分布
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import requests
|
|
41
|
+
import pandas as pd
|
|
42
|
+
import numpy as np
|
|
43
|
+
|
|
44
|
+
OBIS_BASE = "https://api.obis.org/v3"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def obis_occurrence_search(taxon_name=None, taxon_id=None,
|
|
48
|
+
geometry=None, year_range=None, limit=1000):
|
|
49
|
+
"""
|
|
50
|
+
OBIS — 海洋生物出現記録検索。
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
taxon_name: str — 学名 (例: "Delphinidae")
|
|
54
|
+
taxon_id: int — AphiaID
|
|
55
|
+
geometry: str — WKT ジオメトリ (例: "POLYGON((...))")
|
|
56
|
+
year_range: tuple — (start_year, end_year)
|
|
57
|
+
limit: int — 最大取得件数
|
|
58
|
+
"""
|
|
59
|
+
url = f"{OBIS_BASE}/occurrence"
|
|
60
|
+
params = {"size": min(limit, 5000)}
|
|
61
|
+
|
|
62
|
+
if taxon_name:
|
|
63
|
+
params["scientificname"] = taxon_name
|
|
64
|
+
if taxon_id:
|
|
65
|
+
params["taxonid"] = taxon_id
|
|
66
|
+
if geometry:
|
|
67
|
+
params["geometry"] = geometry
|
|
68
|
+
if year_range:
|
|
69
|
+
params["startdate"] = f"{year_range[0]}-01-01"
|
|
70
|
+
params["enddate"] = f"{year_range[1]}-12-31"
|
|
71
|
+
|
|
72
|
+
resp = requests.get(url, params=params, timeout=60)
|
|
73
|
+
resp.raise_for_status()
|
|
74
|
+
data = resp.json()
|
|
75
|
+
|
|
76
|
+
records = []
|
|
77
|
+
for rec in data.get("results", []):
|
|
78
|
+
records.append({
|
|
79
|
+
"scientific_name": rec.get("scientificName", ""),
|
|
80
|
+
"aphia_id": rec.get("aphiaID", ""),
|
|
81
|
+
"latitude": rec.get("decimalLatitude", None),
|
|
82
|
+
"longitude": rec.get("decimalLongitude", None),
|
|
83
|
+
"depth": rec.get("depth", None),
|
|
84
|
+
"date": rec.get("date_mid", ""),
|
|
85
|
+
"dataset_id": rec.get("dataset_id", ""),
|
|
86
|
+
"basis_of_record": rec.get("basisOfRecord", ""),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
df = pd.DataFrame(records)
|
|
90
|
+
print(f"OBIS: '{taxon_name or taxon_id}' → {len(df)} occurrences")
|
|
91
|
+
return df
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def obis_checklist(geometry=None, area_id=None):
|
|
95
|
+
"""
|
|
96
|
+
OBIS — 地域別種チェックリスト。
|
|
97
|
+
|
|
98
|
+
Parameters:
|
|
99
|
+
geometry: str — WKT ジオメトリ
|
|
100
|
+
area_id: int — OBIS エリア ID
|
|
101
|
+
"""
|
|
102
|
+
url = f"{OBIS_BASE}/checklist"
|
|
103
|
+
params = {"size": 5000}
|
|
104
|
+
if geometry:
|
|
105
|
+
params["geometry"] = geometry
|
|
106
|
+
if area_id:
|
|
107
|
+
params["areaid"] = area_id
|
|
108
|
+
|
|
109
|
+
resp = requests.get(url, params=params, timeout=60)
|
|
110
|
+
resp.raise_for_status()
|
|
111
|
+
data = resp.json()
|
|
112
|
+
|
|
113
|
+
species = []
|
|
114
|
+
for sp in data.get("results", []):
|
|
115
|
+
species.append({
|
|
116
|
+
"scientific_name": sp.get("scientificName", ""),
|
|
117
|
+
"aphia_id": sp.get("taxonID", ""),
|
|
118
|
+
"records": sp.get("records", 0),
|
|
119
|
+
"kingdom": sp.get("kingdom", ""),
|
|
120
|
+
"phylum": sp.get("phylum", ""),
|
|
121
|
+
"class": sp.get("class", ""),
|
|
122
|
+
"order": sp.get("order", ""),
|
|
123
|
+
"family": sp.get("family", ""),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
df = pd.DataFrame(species)
|
|
127
|
+
print(f"OBIS checklist: {len(df)} species")
|
|
128
|
+
return df
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 2. WoRMS 分類学検索
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
WORMS_BASE = "https://www.marinespecies.org/rest"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def worms_taxon_search(name, fuzzy=True, marine_only=True):
|
|
138
|
+
"""
|
|
139
|
+
WoRMS — 海洋生物分類学的検索。
|
|
140
|
+
|
|
141
|
+
Parameters:
|
|
142
|
+
name: str — 種名/属名
|
|
143
|
+
fuzzy: bool — ファジー検索
|
|
144
|
+
marine_only: bool — 海洋種のみ
|
|
145
|
+
"""
|
|
146
|
+
url = f"{WORMS_BASE}/AphiaRecordsByName/{name}"
|
|
147
|
+
params = {
|
|
148
|
+
"like": str(fuzzy).lower(),
|
|
149
|
+
"marine_only": str(marine_only).lower(),
|
|
150
|
+
}
|
|
151
|
+
resp = requests.get(url, params=params, timeout=30)
|
|
152
|
+
resp.raise_for_status()
|
|
153
|
+
data = resp.json()
|
|
154
|
+
|
|
155
|
+
results = []
|
|
156
|
+
for taxon in data if isinstance(data, list) else [data]:
|
|
157
|
+
results.append({
|
|
158
|
+
"aphia_id": taxon.get("AphiaID", ""),
|
|
159
|
+
"scientific_name": taxon.get("scientificname", ""),
|
|
160
|
+
"authority": taxon.get("authority", ""),
|
|
161
|
+
"status": taxon.get("status", ""),
|
|
162
|
+
"rank": taxon.get("rank", ""),
|
|
163
|
+
"valid_name": taxon.get("valid_name", ""),
|
|
164
|
+
"kingdom": taxon.get("kingdom", ""),
|
|
165
|
+
"phylum": taxon.get("phylum", ""),
|
|
166
|
+
"class": taxon.get("class", ""),
|
|
167
|
+
"order": taxon.get("order", ""),
|
|
168
|
+
"family": taxon.get("family", ""),
|
|
169
|
+
"genus": taxon.get("genus", ""),
|
|
170
|
+
"is_marine": taxon.get("isMarine", 0),
|
|
171
|
+
"is_brackish": taxon.get("isBrackish", 0),
|
|
172
|
+
"is_freshwater": taxon.get("isFreshwater", 0),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
df = pd.DataFrame(results)
|
|
176
|
+
print(f"WoRMS: '{name}' → {len(df)} taxa")
|
|
177
|
+
return df
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def worms_classification(aphia_id):
|
|
181
|
+
"""
|
|
182
|
+
WoRMS — 完全分類階層取得。
|
|
183
|
+
|
|
184
|
+
Parameters:
|
|
185
|
+
aphia_id: int — AphiaID
|
|
186
|
+
"""
|
|
187
|
+
url = f"{WORMS_BASE}/AphiaClassificationByAphiaID/{aphia_id}"
|
|
188
|
+
resp = requests.get(url, timeout=30)
|
|
189
|
+
resp.raise_for_status()
|
|
190
|
+
data = resp.json()
|
|
191
|
+
|
|
192
|
+
hierarchy = []
|
|
193
|
+
node = data
|
|
194
|
+
while node:
|
|
195
|
+
hierarchy.append({
|
|
196
|
+
"aphia_id": node.get("AphiaID", ""),
|
|
197
|
+
"name": node.get("scientificname", ""),
|
|
198
|
+
"rank": node.get("rank", ""),
|
|
199
|
+
})
|
|
200
|
+
node = node.get("child")
|
|
201
|
+
|
|
202
|
+
df = pd.DataFrame(hierarchy)
|
|
203
|
+
print(f"WoRMS classification: {len(df)} levels")
|
|
204
|
+
return df
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## 3. GBIF 生物多様性レコード
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
GBIF_BASE = "https://api.gbif.org/v1"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def gbif_species_search(name, limit=20):
|
|
214
|
+
"""
|
|
215
|
+
GBIF — 種名検索・分類マッチング。
|
|
216
|
+
|
|
217
|
+
Parameters:
|
|
218
|
+
name: str — 種名
|
|
219
|
+
limit: int — 結果上限
|
|
220
|
+
"""
|
|
221
|
+
url = f"{GBIF_BASE}/species/search"
|
|
222
|
+
params = {"q": name, "limit": limit}
|
|
223
|
+
resp = requests.get(url, params=params, timeout=30)
|
|
224
|
+
resp.raise_for_status()
|
|
225
|
+
data = resp.json()
|
|
226
|
+
|
|
227
|
+
results = []
|
|
228
|
+
for sp in data.get("results", []):
|
|
229
|
+
results.append({
|
|
230
|
+
"taxon_key": sp.get("key", ""),
|
|
231
|
+
"scientific_name": sp.get("scientificName", ""),
|
|
232
|
+
"canonical_name": sp.get("canonicalName", ""),
|
|
233
|
+
"status": sp.get("taxonomicStatus", ""),
|
|
234
|
+
"rank": sp.get("rank", ""),
|
|
235
|
+
"kingdom": sp.get("kingdom", ""),
|
|
236
|
+
"phylum": sp.get("phylum", ""),
|
|
237
|
+
"class": sp.get("class", ""),
|
|
238
|
+
"order": sp.get("order", ""),
|
|
239
|
+
"family": sp.get("family", ""),
|
|
240
|
+
"num_occurrences": sp.get("numOccurrences", 0),
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
df = pd.DataFrame(results)
|
|
244
|
+
print(f"GBIF species: '{name}' → {len(df)} taxa")
|
|
245
|
+
return df
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def gbif_occurrence_search(taxon_key=None, country=None,
|
|
249
|
+
year_range=None, limit=300):
|
|
250
|
+
"""
|
|
251
|
+
GBIF — 出現記録検索。
|
|
252
|
+
|
|
253
|
+
Parameters:
|
|
254
|
+
taxon_key: int — GBIF taxon key
|
|
255
|
+
country: str — ISO 国コード (例: "JP")
|
|
256
|
+
year_range: tuple — (start, end)
|
|
257
|
+
limit: int — 最大件数
|
|
258
|
+
"""
|
|
259
|
+
url = f"{GBIF_BASE}/occurrence/search"
|
|
260
|
+
params = {"limit": min(limit, 300)}
|
|
261
|
+
|
|
262
|
+
if taxon_key:
|
|
263
|
+
params["taxonKey"] = taxon_key
|
|
264
|
+
if country:
|
|
265
|
+
params["country"] = country
|
|
266
|
+
if year_range:
|
|
267
|
+
params["year"] = f"{year_range[0]},{year_range[1]}"
|
|
268
|
+
|
|
269
|
+
resp = requests.get(url, params=params, timeout=60)
|
|
270
|
+
resp.raise_for_status()
|
|
271
|
+
data = resp.json()
|
|
272
|
+
|
|
273
|
+
records = []
|
|
274
|
+
for rec in data.get("results", []):
|
|
275
|
+
records.append({
|
|
276
|
+
"gbif_id": rec.get("gbifID", ""),
|
|
277
|
+
"scientific_name": rec.get("scientificName", ""),
|
|
278
|
+
"latitude": rec.get("decimalLatitude", None),
|
|
279
|
+
"longitude": rec.get("decimalLongitude", None),
|
|
280
|
+
"country": rec.get("country", ""),
|
|
281
|
+
"year": rec.get("year", ""),
|
|
282
|
+
"basis_of_record": rec.get("basisOfRecord", ""),
|
|
283
|
+
"institution": rec.get("institutionCode", ""),
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
df = pd.DataFrame(records)
|
|
287
|
+
print(f"GBIF occurrences: {len(df)} records (total: {data.get('count', 0)})")
|
|
288
|
+
return df
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## 4. FishBase 魚類データ
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
FISHBASE_BASE = "https://fishbase.ropensci.org"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def fishbase_species(genus=None, species=None, family=None):
|
|
298
|
+
"""
|
|
299
|
+
FishBase — 魚類種データ取得。
|
|
300
|
+
|
|
301
|
+
Parameters:
|
|
302
|
+
genus: str — 属名
|
|
303
|
+
species: str — 種小名
|
|
304
|
+
family: str — 科名
|
|
305
|
+
"""
|
|
306
|
+
url = f"{FISHBASE_BASE}/species"
|
|
307
|
+
params = {"limit": 100}
|
|
308
|
+
if genus:
|
|
309
|
+
params["Genus"] = genus
|
|
310
|
+
if species:
|
|
311
|
+
params["Species"] = species
|
|
312
|
+
if family:
|
|
313
|
+
params["Family"] = family
|
|
314
|
+
|
|
315
|
+
resp = requests.get(url, params=params, timeout=30)
|
|
316
|
+
resp.raise_for_status()
|
|
317
|
+
data = resp.json()
|
|
318
|
+
|
|
319
|
+
records = []
|
|
320
|
+
for fish in data.get("data", []):
|
|
321
|
+
records.append({
|
|
322
|
+
"spec_code": fish.get("SpecCode", ""),
|
|
323
|
+
"genus": fish.get("Genus", ""),
|
|
324
|
+
"species": fish.get("Species", ""),
|
|
325
|
+
"family": fish.get("Family", ""),
|
|
326
|
+
"body_shape": fish.get("BodyShapeI", ""),
|
|
327
|
+
"max_length": fish.get("Length", None),
|
|
328
|
+
"vulnerability": fish.get("Vulnerability", None),
|
|
329
|
+
"importance": fish.get("Importance", ""),
|
|
330
|
+
"habitat": fish.get("DemersPelag", ""),
|
|
331
|
+
"depth_range": f"{fish.get('DepthRangeShallow', '')}-{fish.get('DepthRangeDeep', '')}",
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
df = pd.DataFrame(records)
|
|
335
|
+
print(f"FishBase: {len(df)} species")
|
|
336
|
+
return df
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## 5. 海洋生態学統合パイプライン
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
def marine_ecology_pipeline(taxon_name, region_wkt=None,
|
|
343
|
+
output_dir="results"):
|
|
344
|
+
"""
|
|
345
|
+
OBIS + WoRMS + GBIF + FishBase 統合パイプライン。
|
|
346
|
+
|
|
347
|
+
Parameters:
|
|
348
|
+
taxon_name: str — 分類群名
|
|
349
|
+
region_wkt: str — 調査海域 WKT
|
|
350
|
+
output_dir: str — 出力ディレクトリ
|
|
351
|
+
"""
|
|
352
|
+
from pathlib import Path
|
|
353
|
+
output_dir = Path(output_dir)
|
|
354
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
355
|
+
|
|
356
|
+
# 1) WoRMS 分類検証
|
|
357
|
+
taxonomy = worms_taxon_search(taxon_name)
|
|
358
|
+
taxonomy.to_csv(output_dir / "worms_taxonomy.csv", index=False)
|
|
359
|
+
|
|
360
|
+
aphia_id = None
|
|
361
|
+
if len(taxonomy) > 0:
|
|
362
|
+
aphia_id = taxonomy.iloc[0]["aphia_id"]
|
|
363
|
+
classification = worms_classification(aphia_id)
|
|
364
|
+
classification.to_csv(output_dir / "classification.csv", index=False)
|
|
365
|
+
|
|
366
|
+
# 2) OBIS 海洋分布
|
|
367
|
+
obis_data = obis_occurrence_search(
|
|
368
|
+
taxon_name=taxon_name,
|
|
369
|
+
taxon_id=aphia_id,
|
|
370
|
+
geometry=region_wkt,
|
|
371
|
+
)
|
|
372
|
+
obis_data.to_csv(output_dir / "obis_occurrences.csv", index=False)
|
|
373
|
+
|
|
374
|
+
# 3) GBIF 補完
|
|
375
|
+
gbif_sp = gbif_species_search(taxon_name)
|
|
376
|
+
if len(gbif_sp) > 0:
|
|
377
|
+
taxon_key = gbif_sp.iloc[0]["taxon_key"]
|
|
378
|
+
gbif_occ = gbif_occurrence_search(taxon_key=taxon_key)
|
|
379
|
+
gbif_occ.to_csv(output_dir / "gbif_occurrences.csv", index=False)
|
|
380
|
+
else:
|
|
381
|
+
gbif_occ = pd.DataFrame()
|
|
382
|
+
|
|
383
|
+
# 4) 多様性指標
|
|
384
|
+
if len(obis_data) > 0:
|
|
385
|
+
n_species = obis_data["scientific_name"].nunique()
|
|
386
|
+
lat_range = (obis_data["latitude"].min(), obis_data["latitude"].max())
|
|
387
|
+
depth_range = (obis_data["depth"].min(), obis_data["depth"].max())
|
|
388
|
+
print(f"Diversity: {n_species} species, "
|
|
389
|
+
f"lat {lat_range}, depth {depth_range}")
|
|
390
|
+
|
|
391
|
+
print(f"Marine ecology pipeline: {output_dir}")
|
|
392
|
+
return {
|
|
393
|
+
"taxonomy": taxonomy,
|
|
394
|
+
"obis_occurrences": obis_data,
|
|
395
|
+
"gbif_occurrences": gbif_occ,
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## ToolUniverse 連携
|
|
402
|
+
|
|
403
|
+
| TU Key | ツール名 | 連携内容 |
|
|
404
|
+
|--------|---------|---------|
|
|
405
|
+
| `obis` | OBIS | 海洋生物出現・分布レコード検索 |
|
|
406
|
+
| `worms` | WoRMS | 海洋種分類学的検証・階層取得 |
|
|
407
|
+
| `gbif` | GBIF | 生物多様性出現レコード検索 |
|
|
408
|
+
|
|
409
|
+
## パイプライン統合
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
environmental-ecology → marine-ecology → phylogenetics
|
|
413
|
+
(陸域生態学) (OBIS/WoRMS/GBIF) (系統解析)
|
|
414
|
+
│ │ ↓
|
|
415
|
+
biodiversity-db ───────────┘ species-distribution
|
|
416
|
+
(ENA/BOLD) │ (空間分布モデル)
|
|
417
|
+
↓
|
|
418
|
+
fisheries-management
|
|
419
|
+
(水産資源管理)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## パイプライン出力
|
|
423
|
+
|
|
424
|
+
| ファイル | 説明 | 次スキル |
|
|
425
|
+
|---------|------|---------|
|
|
426
|
+
| `results/worms_taxonomy.csv` | WoRMS 分類情報 | → phylogenetics |
|
|
427
|
+
| `results/obis_occurrences.csv` | OBIS 出現記録 | → species-distribution |
|
|
428
|
+
| `results/gbif_occurrences.csv` | GBIF 出現記録 | → biodiversity-db |
|
|
429
|
+
| `results/classification.csv` | 分類階層 | → environmental-ecology |
|