What determines the optimal timing and dosing of ketogenic interventions for neuroprotection?¶
Notebook ID: nb-SDA-2026-04-03-gap-debate-20260403-222618-2709aad9 · Analysis: SDA-2026-04-03-gap-debate-20260403-222618-2709aad9 · Generated: 2026-04-17
Research question¶
While ketone metabolism was discussed as therapeutic, the debate revealed no clear framework for when and how much ketosis provides benefit vs harm. The 'metabolic steal syndrome' hypothesis suggests timing could be critical but remains untested.
Source: Debate session sess_SDA-2026-04-02-gap-v2-5d0e3052 (Analysis: SDA-2026-04-02-gap-v2-5d0e3052)
Approach¶
This notebook is generated programmatically from real Forge tool calls and SciDEX debate data. Code cells load cached evidence bundles from data/forge_cache/seaad/*.json and query live data from scidex.db. Re-run python3 scripts/regenerate_notebooks.py --analysis SDA-2026-04-03-gap-debate-20260403-222618-2709aad9 --force to refresh.
7 hypotheses were generated and debated. The knowledge graph has 13 edges.
Debate Summary¶
Quality score: 0.8 · Rounds: 4 · Personas: Theorist, Skeptic, Domain_Expert, Synthesizer
1. Forge tool provenance¶
import json, sys, sqlite3
from pathlib import Path
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
matplotlib.rcParams['figure.dpi'] = 110
matplotlib.rcParams['figure.facecolor'] = 'white'
REPO = Path('.').resolve()
sys.path.insert(0, str(REPO))
CACHE_SUB = 'seaad'
CACHE = REPO / 'data' / 'forge_cache' / CACHE_SUB
def load(name):
p = CACHE / f'{name}.json'
if p.exists():
return json.loads(p.read_text())
return {}
db_path = Path('/home/ubuntu/scidex/scidex.db')
try:
db = sqlite3.connect(str(db_path))
prov = pd.read_sql_query('''
SELECT skill_id, status, COUNT(*) AS n_calls,
ROUND(AVG(duration_ms),0) AS mean_ms
FROM tool_calls
WHERE created_at >= date('now','-30 days')
GROUP BY skill_id, status
ORDER BY n_calls DESC
''', db)
db.close()
prov['tool'] = prov['skill_id'].str.replace('tool_', '', regex=False)
print(f'{len(prov)} tool-call aggregates (last 30 days):')
prov[['tool','status','n_calls','mean_ms']].head(20)
except Exception as e:
print(f'Provenance unavailable: {e}')
181 tool-call aggregates (last 30 days):
2. Target gene annotations¶
ann_rows = []
for g in ['IRAKM']:
mg = load(f'mygene_{g}')
hpa = load(f'hpa_{g}')
if not mg and not hpa:
ann_rows.append({'gene': g, 'name': '—', 'protein_class': '—',
'disease_involvement': '—'})
continue
ann_rows.append({
'gene': g,
'name': (mg.get('name') or '')[:55],
'protein_class': ', '.join((hpa.get('protein_class') or [])[:2])[:55]
if isinstance(hpa.get('protein_class'), list)
else str(hpa.get('protein_class') or '—')[:55],
'disease_involvement': ', '.join((hpa.get('disease_involvement') or [])[:2])[:55]
if isinstance(hpa.get('disease_involvement'), list)
else str(hpa.get('disease_involvement') or '')[:55],
})
pd.DataFrame(ann_rows)
| gene | name | protein_class | disease_involvement | |
|---|---|---|---|---|
| 0 | IRAKM | — | — | — |
3. GO Biological Process enrichment (Enrichr)¶
go_bp = load('enrichr_GO_Biological_Process')
if isinstance(go_bp, list) and go_bp:
go_df = pd.DataFrame(go_bp[:10])[['term','p_value','odds_ratio','genes']]
go_df['p_value'] = go_df['p_value'].apply(lambda p: f'{p:.2e}')
go_df['odds_ratio'] = go_df['odds_ratio'].round(1)
go_df['term'] = go_df['term'].str[:60]
go_df['n_hits'] = go_df['genes'].apply(len)
go_df['genes'] = go_df['genes'].apply(lambda g: ', '.join(g))
go_df[['term','n_hits','p_value','odds_ratio','genes']]
else:
print('No GO:BP enrichment data')
# Visualize top GO BP enrichment
go_bp = load('enrichr_GO_Biological_Process')
if isinstance(go_bp, list) and go_bp:
top = go_bp[:8]
terms = [t['term'][:45] for t in top][::-1]
neglogp = [-np.log10(max(t['p_value'], 1e-300)) for t in top][::-1]
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.barh(terms, neglogp, color='#4fc3f7')
ax.set_xlabel('-log10(p-value)')
ax.set_title('Top GO:BP enrichment (Enrichr)')
ax.grid(axis='x', alpha=0.3)
plt.tight_layout(); plt.show()
else:
print('No GO:BP data to plot')
4. KEGG pathway enrichment¶
kegg = load('enrichr_KEGG_Pathways')
if isinstance(kegg, list) and kegg:
kegg_df = pd.DataFrame(kegg[:10])[['term','p_value','odds_ratio','genes']]
kegg_df['genes'] = kegg_df['genes'].apply(lambda g: ', '.join(g))
kegg_df['p_value'] = kegg_df['p_value'].apply(lambda p: f'{p:.2e}')
kegg_df['odds_ratio'] = kegg_df['odds_ratio'].round(1)
kegg_df
else:
print('No KEGG enrichment data')
No KEGG enrichment data
5. STRING protein interaction network¶
ppi = load('string_network')
if isinstance(ppi, list) and ppi:
ppi_df = pd.DataFrame(ppi).sort_values('score', ascending=False)
display_cols = [c for c in ['protein1','protein2','score','escore','tscore'] if c in ppi_df.columns]
print(f'{len(ppi_df)} STRING edges')
ppi_df[display_cols].head(20)
else:
print('No STRING edges returned')
11 STRING edges
# Network figure
ppi = load('string_network')
if isinstance(ppi, list) and ppi:
import math
nodes = sorted({p for e in ppi for p in (e['protein1'], e['protein2'])})
n = len(nodes)
pos = {n_: (math.cos(2*math.pi*i/n), math.sin(2*math.pi*i/n)) for i, n_ in enumerate(nodes)}
fig, ax = plt.subplots(figsize=(7, 7))
for e in ppi:
x1,y1 = pos[e['protein1']]; x2,y2 = pos[e['protein2']]
ax.plot([x1,x2],[y1,y2], color='#888', alpha=0.3+0.5*e['score'],
linewidth=0.5+2*e['score'])
for name,(x,y) in pos.items():
ax.scatter([x],[y], s=450, color='#ffd54f', edgecolors='#333', zorder=3)
ax.annotate(name, (x,y), ha='center', va='center', fontsize=8, fontweight='bold', zorder=4)
ax.set_aspect('equal'); ax.axis('off')
ax.set_title(f'STRING PPI network ({len(ppi)} edges)')
plt.tight_layout(); plt.show()
else:
print('No STRING data to visualize')
6. Reactome pathway footprint¶
pw_rows = []
for g in ['IRAKM']:
pws = load(f'reactome_{g}')
if isinstance(pws, list):
pw_rows.append({'gene': g, 'n_pathways': len(pws),
'top_pathway': (pws[0]['name'] if pws else '—')[:70]})
else:
pw_rows.append({'gene': g, 'n_pathways': 0, 'top_pathway': '—'})
pd.DataFrame(pw_rows).sort_values('n_pathways', ascending=False)
| gene | n_pathways | top_pathway | |
|---|---|---|---|
| 0 | IRAKM | 0 | — |
7. Allen Brain Atlas ISH regional expression¶
ish_rows = []
for g in ['IRAKM']:
ish = load(f'allen_ish_{g}')
regions = ish.get('regions') or [] if isinstance(ish, dict) else []
ish_rows.append({
'gene': g,
'n_ish_regions': len(regions),
'top_region': (regions[0].get('structure','') if regions else '—')[:45],
'top_energy': round(regions[0].get('expression_energy',0), 2) if regions else None,
})
pd.DataFrame(ish_rows)
| gene | n_ish_regions | top_region | top_energy | |
|---|---|---|---|---|
| 0 | IRAKM | 0 | — | — |
8. Hypothesis ranking (7 hypotheses)¶
hyp_data = [('Epigenetic Priming Ketone Protocol', 0.852), ('Biphasic Ketogenic Intervention Protocol', 0.77), ('Astrocyte-Neuron Metabolic Coupling Titration', 0.71), ('Inflammatory State-Dependent Ketone Timing', 0.665), ('Circadian-Gated Ketone Window Hypothesis', 0.606), ('Glucose-Ketone Metabolic Switch Timing', 0.531), ('Age-Stratified Ketone Dosing Matrix', 0.452)]
titles = [h[0] for h in hyp_data][::-1]
scores = [h[1] for h in hyp_data][::-1]
fig, ax = plt.subplots(figsize=(10, max(8, len(titles)*0.4)))
colors = ['#ef5350' if s >= 0.6 else '#ffa726' if s >= 0.5 else '#66bb6a' for s in scores]
ax.barh(range(len(titles)), scores, color=colors)
ax.set_yticks(range(len(titles))); ax.set_yticklabels(titles, fontsize=7)
ax.set_xlabel('Composite Score'); ax.set_title('What determines the optimal timing and dosing of ketogenic interventions for neuroprotection?')
ax.grid(axis='x', alpha=0.3)
plt.tight_layout(); plt.show()
9. Score dimension heatmap (top 10)¶
labels = ['Epigenetic Priming Ketone Protocol', 'Biphasic Ketogenic Intervention Protocol', 'Astrocyte-Neuron Metabolic Coupling Titr', 'Inflammatory State-Dependent Ketone Timi', 'Circadian-Gated Ketone Window Hypothesis', 'Glucose-Ketone Metabolic Switch Timing', 'Age-Stratified Ketone Dosing Matrix']
matrix = np.array([[0.9, 0.8, 0.7, 0.8, 0, 0.7, 0.6, 0.9, 0.6], [0.8, 0.6, 0.8, 0.7, 0, 0.6, 0.7, 0.3, 0.4], [0.7, 0.5, 0.7, 0.6, 0, 0.6, 0.5, 0.5, 0.5], [0.8, 0.4, 0.6, 0.5, 0, 0.4, 0.4, 0.3, 0.6], [0.9, 0.3, 0.5, 0.4, 0, 0.3, 0.3, 0.2, 0.5], [0.7, 0.2, 0.4, 0.4, 0, 0.4, 0.3, 0.3, 0.3], [0.6, 0.2, 0.3, 0.3, 0, 0.3, 0.2, 0.2, 0.2]])
dims = ['novelty_score', 'feasibility_score', 'impact_score', 'mechanistic_plausibility_score', 'clinical_relevance_score', 'data_availability_score', 'reproducibility_score', 'druggability_score', 'safety_profile_score']
if matrix.size:
fig, ax = plt.subplots(figsize=(10, 5))
im = ax.imshow(matrix, cmap='RdYlGn', aspect='auto', vmin=0, vmax=1)
ax.set_xticks(range(len(dims)))
ax.set_xticklabels([d.replace('_score','').replace('_',' ').title() for d in dims],
rotation=45, ha='right', fontsize=8)
ax.set_yticks(range(len(labels))); ax.set_yticklabels(labels, fontsize=7)
ax.set_title('Score dimensions — top hypotheses')
plt.colorbar(im, ax=ax, shrink=0.8)
plt.tight_layout(); plt.show()
else:
print('No score data available')
10. PubMed evidence per hypothesis¶
Hypothesis 1: Epigenetic Priming Ketone Protocol¶
Target genes: HDAC2/HDAC3 · Composite score: 0.852
Brief intermittent ketogenic exposures (2-4 hour pulses of 2-3 mM β-hydroxybutyrate, 2-3 times weekly) prime neuroprotective gene expression through histone deacetylase inhibition and epigenetic modifications, creating a 'metabolic memory' that enhances resilience without chronic metabolic disruption.
hid = 'h-d7212534'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
Hypothesis 2: Biphasic Ketogenic Intervention Protocol¶
Target genes: HMGCS2 · Composite score: 0.77
Initial high-dose ketone administration (3-5 mM β-hydroxybutyrate) during acute neurological insult provides immediate mitochondrial support and oxidative stress reduction, followed by sustained low-dose maintenance (0.5-1.5 mM) to prevent chronic metabolic steal while maintaining neuroprotective signaling pathways.
hid = 'h-6df1bc66'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
Hypothesis 3: Astrocyte-Neuron Metabolic Coupling Titration¶
Target genes: BDH1 · Composite score: 0.71
Low-dose β-hydroxybutyrate (0.5-1.0 mM) preserves astrocytic lactate production for neuronal support, while higher doses (>2.0 mM) create metabolic steal syndrome by completely shutting down astrocytic glycolysis. Optimal neuroprotection requires maintaining astrocyte-neuron lactate shuttle integrity.
hid = 'h-17a2da3f'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
Hypothesis 4: Inflammatory State-Dependent Ketone Timing¶
Target genes: IRAKM · Composite score: 0.665
Ketogenic intervention timing should be guided by neuroinflammatory biomarkers rather than injury timeline. High ketone levels during peak microglial activation (24-72 hours post-injury) may interfere with necessary inflammatory clearance processes, while delayed intervention (>72 hours) supports resolution and tissue repair phases.
hid = 'h-a1d97415'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
Hypothesis 5: Circadian-Gated Ketone Window Hypothesis¶
Target genes: OXCT1 · Composite score: 0.606
Ketone body utilization efficiency follows circadian rhythms, with optimal neuroprotective windows occurring during natural fasting periods (18:00-06:00). β-hydroxybutyrate administered during these periods maximizes mitochondrial efficiency while minimizing glucose-ketone substrate competition that could impair astrocytic function.
hid = 'h-9d4571a7'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
Hypothesis 6: Glucose-Ketone Metabolic Switch Timing¶
Target genes: GLUT1/GLUT3/MCT1/MCT2 · Composite score: 0.531
Ketogenic intervention should be initiated during periods of metabolic stress when glucose utilization is already compromised (hypoxia, inflammation, metabolic dysfunction), as ketones provide alternative energy without competing with functional glucose pathways. Pre-emptive ketosis in healthy tissue may paradoxically reduce glucose availability.
hid = 'h-a947032c'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
Hypothesis 7: Age-Stratified Ketone Dosing Matrix¶
Target genes: OXCT1 · Composite score: 0.452
Neuroprotective ketone dosing should be inversely related to age due to declining endogenous ketone utilization capacity. Pediatric patients require lower doses (0.5-1.0 mM) due to higher baseline ketone utilization efficiency, while elderly patients need higher doses (2.0-4.0 mM) to overcome metabolic inflexibility and mitochondrial dysfunction.
hid = 'h-404bab00'
papers = load(f'pubmed_{hid}')
if isinstance(papers, list) and papers:
lit = pd.DataFrame(papers)
cols = [c for c in ['year','journal','title','pmid'] if c in lit.columns]
if cols:
lit = lit[cols]
lit['title'] = lit['title'].str[:80]
if 'journal' in lit.columns:
lit['journal'] = lit['journal'].str[:30]
lit.sort_values('year', ascending=False, inplace=True)
display_df = lit
else:
display_df = pd.DataFrame(papers[:5])
else:
display_df = pd.DataFrame([{'note':'no PubMed results'}])
display_df
| note | |
|---|---|
| 0 | no PubMed results |
11. Knowledge graph edges (13 total)¶
edge_data = [{'source': 'h-d7212534', 'relation': 'targets', 'target': 'HDAC2/HDAC3', 'strength': 0.5}, {'source': 'h-6df1bc66', 'relation': 'targets', 'target': 'HMGCS2', 'strength': 0.5}, {'source': 'h-17a2da3f', 'relation': 'targets', 'target': 'BDH1', 'strength': 0.5}, {'source': 'h-a1d97415', 'relation': 'targets', 'target': 'IRAKM', 'strength': 0.5}, {'source': 'h-9d4571a7', 'relation': 'targets', 'target': 'OXCT1', 'strength': 0.5}, {'source': 'h-a947032c', 'relation': 'targets', 'target': 'GLUT1/GLUT3/MCT1/MCT2', 'strength': 0.5}, {'source': 'h-404bab00', 'relation': 'targets', 'target': 'OXCT1', 'strength': 0.5}, {'source': 'HDAC2/HDAC3', 'relation': 'associated_with', 'target': 'metabolic_neuroscience', 'strength': 0.4}, {'source': 'HMGCS2', 'relation': 'associated_with', 'target': 'metabolic_neuroscience', 'strength': 0.4}, {'source': 'BDH1', 'relation': 'associated_with', 'target': 'metabolic_neuroscience', 'strength': 0.4}, {'source': 'IRAKM', 'relation': 'associated_with', 'target': 'metabolic_neuroscience', 'strength': 0.4}, {'source': 'OXCT1', 'relation': 'associated_with', 'target': 'metabolic_neuroscience', 'strength': 0.4}, {'source': 'GLUT1/GLUT3/MCT1/MCT2', 'relation': 'associated_with', 'target': 'metabolic_neuroscience', 'strength': 0.4}]
if edge_data:
pd.DataFrame(edge_data).head(25)
else:
print('No KG edge data available')
12. Caveats¶
This notebook uses real Forge tool calls cached from live APIs, but:
- Enrichment is against curated gene-set libraries, not genome-wide screens
- STRING/Reactome/HPA/MyGene reflect curated knowledge
- PubMed literature is search-relevance ranked, not systematic review
The cached evidence bundle is the minimum viable real-data analysis for this topic.