HF Space deployment needs every showcase path to work without external
data. Five artifacts are gitignored (model binaries, RAG index, derived
fixtures) so they have to be (re)generated at image-build time.
- scripts/seed_demo_artifacts.py: idempotent generator for
data/processed/mri_dl_2d/best_model.pt (random resnet18 4-class),
data/processed/mri_model.onnx (dynamic-D/H/W ONNX biased to 'abnormal'),
data/processed/eeg_clf.joblib (synthetic-separable RandomForest),
data/external_rag/index/rag_index.pkl (4-chunk synthetic clinical
TF-IDF index), and tests/fixtures/mri_sample/subject_0_axial.png
(axial slice from the bundled NIfTI). Each step is a no-op when the
artifact already exists.
- Dockerfile.hf: COPY scripts/, RUN seed_demo_artifacts.py after the
pipeline + FAISS-index build steps. Keeps Dockerfile (HF
auto-discovered name) byte-identical to Dockerfile.hf.
- docker-entrypoint.sh: re-run the seed script on container start so a
mounted-volume deploy can re-s…
fix(demo): wire every showcase path end-to-end with auto-resolved artifacts
Goal: every endpoint and every Streamlit tab must work in a 2-minute
hackathon demo without requiring the demoer to fetch external data.
Code fixes:
- src/api/routes.py + src/agents/tools.py: fix two stale references —
bbb_model.load (not load_model) and bbb_model.predict_with_proba (not
predict_one); also corrects the agent-side default BBB artifact path
from .pkl to .joblib so the auto-resolve in adjust_drug_dose works.
- src/api/routes.py: replace hard-coded _AGENT_DEFAULT_MODEL = google/
gemini-2.0-flash-exp:free (now 404 on OpenRouter) with a fallback
chain probed at orchestrator-build time. Picks the first model that
returns a non-404/429 ping. Override via NEUROBRIDGE_AGENT_MODEL
(single id) or NEUROBRIDGE_AGENT_MODEL_CHAIN (csv).
- src/llm/explainer.py: prepend openai/gpt-oss-20b:free (verified 2026-
05-02) to the explainer fallback chain so /explain/* doesn't fall
back to the deterministic template when free-tier ids churn.
- src/frontend/app.py: drop the en…
Adds the only legitimate BBB <-> MRI bridge in the platform — researcher
lane only. Fusion engine still excludes BBB as a diagnostic modality.
Modules:
- src/models/bbb_permeability_map.py: scalar BBB leakage score in [0,1]
with two modes. heuristic_proxy (default) reuses the 2D resnet18
Alzheimer classifier and computes 1 - P(NonDemented). dce_onnx loads
a real 4D DCE-MRI ONNX model from BBB_PERMEABILITY_DCE_PATH and
averages a normalised Ktrans map; raises a clear FileNotFoundError
with contract documentation when no artifact exists.
- src/research/drug_dose_adjuster.py: pure-function dose revision keyed
on (BBB intact?, drug BBB-permeable?). Permeable drugs see an
aggressive linear cut floored at 30%; non-permeable drugs see a mild
cut floored at 60%; intact BBB returns baseline unchanged. Unknown
drug permeability is treated as permeable (safer). Rationale always
includes the 'research suggestion, not medical advice' disclaimer.
API:
- POST /predict/bbb_permea…
fix(mri): try weights_only=True first; fall back only for trusted module pickles
- tests/fusion/test_eeg_modality_flow.py: end-to-end EEG -> fusion `eeg`
modality flow (alzheimers feature spike lifts AD score; control profile
doesn't inflate it).
- tests/models/test_eeg_model_real.py: skip-if-absent sanity for the real
EEG joblib; runs automatically once the file lands at the canonical path.
- src/frontend/app.py: doctor MRI tab grows an EEG predict form
(comma-separated features) calling POST /predict/eeg. 503 surfaces as a
human-readable warning instead of a stack trace.
feat(api): add POST /predict/eeg route (stub-able for demo)
Adds network-gated live test that skips without OPENROUTER_API_KEY (and
without the BBB model artifact, since explainer.py auto-loads .env at
import time). Adds /diag/agent diagnostic endpoint reporting key presence,
agent model, RAG index status, and registered tool names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(frontend): Agent tab with decision-trace expander
Add 6th "🤖 Agent" tab to the Streamlit UI that calls POST /agent/run,
renders the response text + model/finish_reason caption, and shows an
expandable decision-trace panel listing each tool call's name, args,
and result (or error). Also makes _post() timeout configurable via a
keyword arg (default 120 s) for backwards compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(api): POST /agent/run endpoint (orchestrator + RAG, stub-injectable)
Replace top-level _execute_bbb/eeg/mri functions with closure factories
(_make_bbb/eeg/mri_executor) that (a) accept processed_dir so output paths
are no longer hard-coded relative strings, and (b) catch HTTPException from
route calls and re-raise as ValueError with a clean message.
Add build_default_tools(processed_dir=Path("data/processed")) keyword arg
for backwards-compatible parameterization.
Add 4 new tests: processed_dir threading, retrieve short-circuit with no
index, backwards-compat default, and HTTPException → ValueError translation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TDD implementation: tests/rag/test_embed.py exercises Embedder.encode() with
batch processing, empty lists, dimension validation, and semantic similarity
guarantees. Model lazy-loads on first call (no torch dependency, ~33MB ONNX).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous prompt forced every LLM call into "paper-style rationale"
mode, ignoring the user's actual question. Result: Turkish questions
got English paper-prose answers, casual greetings got SHAP rationales,
"what does 93% mean" got the same canned SHAP citation.
New prompt:
- Puts the user's question above the data, not below it.
- When a non-default question is supplied: instructs the model to
match the question's language, answer it directly, and reply
conversationally if the question is casual.
- When no question is supplied (default /explain caller): preserves
the original 2-4 sentence paper-style rationale behavior.
11/11 LLM unit tests pass; template path untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-shot in-container probe to diagnose why /explain/* falls back
to template on HF Space. Shows key presence (length + 12-char prefix
only — never full secret), kill-switch state, and an 8-token probe
against the chain's first model. Surfaces 401/429/CONN errors as
JSON so deployment issues can be diagnosed without container shell
or HF logs API access.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs(llm): add probe-date + 429-policy notes; fix chain capability order
Replaces single-model call with a configurable fallback chain. On 429
(rate-limit / quota), 402 (credits), 403/404 (retired id) or 5xx
(upstream) the explainer advances to the next free model. Network and
timeout errors still drop straight to the deterministic template,
since switching models can't fix an unreachable host.
Loads .env via python-dotenv at import time so OPENROUTER_API_KEY
flows through without manual exports. .env added to .gitignore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(frontend): toast icon must be valid emoji, not ✓
Streamlit's st.toast(icon=...) accepts only single emoji characters,
not arbitrary unicode shortcodes. The check-mark "✓" tripped a
StreamlitAPIException after every successful prediction. Switched all
three success toasts (BBB / EEG / MRI) to "✅".
fix(frontend): EEG default path points at fixture (HF container has no data/raw/eeg.fif)