Without this, the regression-detection branch in the live integration
test was silently dead — caplog never received explainer log records
(propagate=False on src.core.logger.get_logger), so every template
fallback hit pytest.fail instead of pytest.skip.
test(llm): tighten live integration test (slow marker, log-gated skip, on-topic assertion)
- explain() gains modality kwarg ('bbb' | 'eeg' | 'mri'), default 'bbb'
for backward compat with Day-7 callers.
- _template_explain renamed to _template_explain_bbb; added
_template_explain_eeg (epochs, features, ICA story) and
_template_explain_mri (site-gap pre/post, reduction factor).
- _build_llm_prompt branches on modality with a domain-specific header
+ body. Unknown modality logs warning and falls back to BBB template.
- ExplainPayload loosened from strict TypedDict to dict[str, Any] since
shapes differ across modalities.
- 3 new tests (TestEEGTemplate, TestMRITemplate, TestModalityDispatch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(llm): explainer with deterministic template + OpenRouter fallback
- New module src/llm/explainer.py — single public entry point
explain(payload). Returns {rationale, source, model}. Never raises.
- Deterministic template (4 sentences: verdict, calibration if any,
top-3 SHAP, drift) is the source of truth for tests.
- LLM path: OpenRouter chat completions via openai==1.51.0 SDK,
model meta-llama/llama-3.2-3b-instruct:free, 8s timeout, 256 max
tokens, temperature 0.3. Gated by OPENROUTER_API_KEY presence and
NEUROBRIDGE_DISABLE_LLM=1 kill-switch.
- Fallback chain: env-disabled → no key → SDK ImportError → API error
→ empty/malformed response → all degrade to template, log WARNING,
source="template".
- 4 new tests: deterministic, top features included, label text
included, kill-switch overrides key.
- New pip dep: openai==1.51.0 (~600KB, transitive deps already present).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>