@nahisaho/satori 0.15.0 → 0.17.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.
@@ -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 |
@@ -0,0 +1,263 @@
1
+ ---
2
+ name: scientific-metabolic-atlas
3
+ description: |
4
+ 代謝アトラススキル。Metabolic Atlas / Human-GEM REST API による
5
+ 代謝反応・代謝産物・コンパートメント検索、フラックス解析統合、
6
+ 代謝ネットワーク可視化。K-Dense 連携: metabolic-atlas。
7
+ tu_tools: []
8
+ kdense_ref: metabolic-atlas
9
+ ---
10
+
11
+ # Scientific Metabolic Atlas
12
+
13
+ Metabolic Atlas REST API を活用したゲノムスケール代謝モデル
14
+ (GEM) 解析パイプラインを提供する。
15
+
16
+ ## When to Use
17
+
18
+ - ヒト代謝反応・代謝産物を検索するとき
19
+ - Human-GEM のコンパートメント情報を取得するとき
20
+ - 代謝経路のネットワーク構造を解析するとき
21
+ - フラックスバランス解析 (FBA) の入力を準備するとき
22
+ - 代謝産物コネクティビティを可視化するとき
23
+ - 組織特異的代謝モデルを構築するとき
24
+
25
+ ---
26
+
27
+ ## Quick Start
28
+
29
+ ## 1. 代謝反応検索
30
+
31
+ ```python
32
+ import requests
33
+ import pandas as pd
34
+ import numpy as np
35
+
36
+ MA_BASE = "https://metabolicatlas.org/api/v2"
37
+
38
+
39
+ def metabolic_atlas_search_reactions(query, model="Human-GEM",
40
+ compartment=None, limit=50):
41
+ """
42
+ Metabolic Atlas — 代謝反応検索。
43
+
44
+ Parameters:
45
+ query: str — 検索クエリ (例: "glycolysis", "citrate")
46
+ model: str — GEM モデル名
47
+ compartment: str — コンパートメント (例: "cytosol", "mitochondria")
48
+ limit: int — 最大結果数
49
+ """
50
+ url = f"{MA_BASE}/search"
51
+ params = {
52
+ "query": query,
53
+ "model": model,
54
+ "type": "reaction",
55
+ "limit": limit,
56
+ }
57
+ resp = requests.get(url, params=params, timeout=30)
58
+ resp.raise_for_status()
59
+ data = resp.json()
60
+
61
+ results = []
62
+ for r in data.get("results", data) if isinstance(data, dict) else data:
63
+ rxn = r if isinstance(r, dict) else {}
64
+ row = {
65
+ "reaction_id": rxn.get("id", ""),
66
+ "name": rxn.get("name", ""),
67
+ "equation": rxn.get("equation", ""),
68
+ "subsystem": rxn.get("subsystem", ""),
69
+ "compartment": rxn.get("compartment", ""),
70
+ "gene_rule": rxn.get("geneRule", ""),
71
+ "lower_bound": rxn.get("lowerBound", None),
72
+ "upper_bound": rxn.get("upperBound", None),
73
+ }
74
+ if compartment and compartment.lower() not in str(
75
+ row.get("compartment", "")).lower():
76
+ continue
77
+ results.append(row)
78
+
79
+ df = pd.DataFrame(results[:limit])
80
+ print(f"Metabolic Atlas reactions: {len(df)} results "
81
+ f"(query={query})")
82
+ return df
83
+ ```
84
+
85
+ ## 2. 代謝産物検索
86
+
87
+ ```python
88
+ def metabolic_atlas_search_metabolites(query, model="Human-GEM",
89
+ limit=50):
90
+ """
91
+ Metabolic Atlas — 代謝産物検索。
92
+
93
+ Parameters:
94
+ query: str — 検索クエリ (例: "glucose", "ATP")
95
+ model: str — GEM モデル名
96
+ limit: int — 最大結果数
97
+ """
98
+ url = f"{MA_BASE}/search"
99
+ params = {
100
+ "query": query,
101
+ "model": model,
102
+ "type": "metabolite",
103
+ "limit": limit,
104
+ }
105
+ resp = requests.get(url, params=params, timeout=30)
106
+ resp.raise_for_status()
107
+ data = resp.json()
108
+
109
+ results = []
110
+ for m in data.get("results", data) if isinstance(data, dict) else data:
111
+ met = m if isinstance(m, dict) else {}
112
+ results.append({
113
+ "metabolite_id": met.get("id", ""),
114
+ "name": met.get("name", ""),
115
+ "formula": met.get("formula", ""),
116
+ "charge": met.get("charge", None),
117
+ "compartment": met.get("compartment", ""),
118
+ "chebi_id": met.get("chebiId", ""),
119
+ "kegg_id": met.get("keggId", ""),
120
+ })
121
+
122
+ df = pd.DataFrame(results[:limit])
123
+ print(f"Metabolic Atlas metabolites: {len(df)} results "
124
+ f"(query={query})")
125
+ return df
126
+ ```
127
+
128
+ ## 3. 代謝ネットワーク解析
129
+
130
+ ```python
131
+ import networkx as nx
132
+
133
+
134
+ def metabolic_atlas_network(subsystem, model="Human-GEM"):
135
+ """
136
+ Metabolic Atlas — サブシステム代謝ネットワーク構築。
137
+
138
+ Parameters:
139
+ subsystem: str — サブシステム名 (例: "Glycolysis")
140
+ model: str — GEM モデル名
141
+ """
142
+ reactions = metabolic_atlas_search_reactions(
143
+ subsystem, model=model, limit=200)
144
+
145
+ G = nx.DiGraph()
146
+
147
+ for _, rxn in reactions.iterrows():
148
+ rxn_id = rxn["reaction_id"]
149
+ equation = str(rxn.get("equation", ""))
150
+
151
+ # 簡易パーサ: "A + B => C + D"
152
+ if "=>" in equation:
153
+ substrates_str, products_str = equation.split("=>", 1)
154
+ elif "=" in equation:
155
+ substrates_str, products_str = equation.split("=", 1)
156
+ else:
157
+ continue
158
+
159
+ substrates = [s.strip() for s in substrates_str.split("+")
160
+ if s.strip()]
161
+ products = [p.strip() for p in products_str.split("+")
162
+ if p.strip()]
163
+
164
+ G.add_node(rxn_id, type="reaction",
165
+ name=rxn.get("name", ""))
166
+
167
+ for s in substrates:
168
+ G.add_node(s, type="metabolite")
169
+ G.add_edge(s, rxn_id)
170
+
171
+ for p in products:
172
+ G.add_node(p, type="metabolite")
173
+ G.add_edge(rxn_id, p)
174
+
175
+ # ネットワーク統計
176
+ n_reactions = sum(1 for _, d in G.nodes(data=True)
177
+ if d.get("type") == "reaction")
178
+ n_metabolites = sum(1 for _, d in G.nodes(data=True)
179
+ if d.get("type") == "metabolite")
180
+
181
+ print(f"Metabolic network: {n_reactions} reactions, "
182
+ f"{n_metabolites} metabolites, {G.number_of_edges()} edges")
183
+ return G
184
+ ```
185
+
186
+ ## 4. 代謝アトラス統合パイプライン
187
+
188
+ ```python
189
+ def metabolic_atlas_pipeline(query, model="Human-GEM",
190
+ output_dir="results"):
191
+ """
192
+ 代謝アトラス統合パイプライン。
193
+
194
+ Parameters:
195
+ query: str — 代謝経路/サブシステム名
196
+ model: str — GEM モデル名
197
+ output_dir: str — 出力ディレクトリ
198
+ """
199
+ from pathlib import Path
200
+ output_dir = Path(output_dir)
201
+ output_dir.mkdir(parents=True, exist_ok=True)
202
+
203
+ # 1) 反応検索
204
+ reactions = metabolic_atlas_search_reactions(query, model=model)
205
+ reactions.to_csv(output_dir / "reactions.csv", index=False)
206
+
207
+ # 2) 代謝産物検索
208
+ metabolites = metabolic_atlas_search_metabolites(query, model=model)
209
+ metabolites.to_csv(output_dir / "metabolites.csv", index=False)
210
+
211
+ # 3) ネットワーク構築
212
+ G = metabolic_atlas_network(query, model=model)
213
+ nx.write_graphml(G, str(output_dir / "metabolic_network.graphml"))
214
+
215
+ # 4) ハブ代謝産物
216
+ met_nodes = [n for n, d in G.nodes(data=True)
217
+ if d.get("type") == "metabolite"]
218
+ hub_scores = {n: G.degree(n) for n in met_nodes}
219
+ hub_df = pd.DataFrame([
220
+ {"metabolite": k, "degree": v}
221
+ for k, v in sorted(hub_scores.items(),
222
+ key=lambda x: -x[1])[:20]
223
+ ])
224
+ hub_df.to_csv(output_dir / "hub_metabolites.csv", index=False)
225
+
226
+ print(f"Metabolic Atlas pipeline: {output_dir}")
227
+ return {
228
+ "reactions": reactions,
229
+ "metabolites": metabolites,
230
+ "network": G,
231
+ "hubs": hub_df,
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ## K-Dense 連携
238
+
239
+ | K-Dense Key | 参照内容 |
240
+ |-------------|---------|
241
+ | `metabolic-atlas` | 代謝モデル構造・反応データベース |
242
+
243
+ ## パイプライン統合
244
+
245
+ ```
246
+ metabolic-modeling → metabolic-atlas → systems-biology
247
+ (COBRA/FBA) (Human-GEM) (統合モデリング)
248
+ │ │ ↓
249
+ pathway-enrichment ─────┘ gene-expression
250
+ (KEGG/Reactome) │ (発現データ)
251
+
252
+ multi-omics
253
+ (メタボロミクス統合)
254
+ ```
255
+
256
+ ## パイプライン出力
257
+
258
+ | ファイル | 説明 | 次スキル |
259
+ |---------|------|---------|
260
+ | `results/reactions.csv` | 代謝反応一覧 | → metabolic-modeling |
261
+ | `results/metabolites.csv` | 代謝産物一覧 | → pathway-enrichment |
262
+ | `results/metabolic_network.graphml` | 代謝ネットワーク | → systems-biology |
263
+ | `results/hub_metabolites.csv` | ハブ代謝産物 | → multi-omics |