project overvew udpate
Browse files
PROJECT_OVERVIEW.md
CHANGED
|
@@ -554,7 +554,111 @@ hackathon/
|
|
| 554 |
|
| 555 |
---
|
| 556 |
|
| 557 |
-
## 16.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
|
| 559 |
NeuroBridge Enterprise hackathon'un sloganına ("**Stop Building Ideas. Start Building Systems.**") en doğrudan cevap. 8 gün boyunca disiplinli TDD + Subagent-Driven Development ile inşa ettik. Public deploy'lu, jüri tarayıcıdan tıklayıp dokunabiliyor. 184 test green, 96.8% jüri skoru projeksiyonu, 5/5 hackathon track strong, 4/4 Living Systems pillar full.
|
| 560 |
|
|
|
|
| 554 |
|
| 555 |
---
|
| 556 |
|
| 557 |
+
## 16. Terimler Sözlüğü
|
| 558 |
+
|
| 559 |
+
Yukarıdaki bölümlerde geçen teknik terimlerin sade Türkçe karşılıkları. Hiç tanımadığın bir alan için kısa "neden önemli?" notlarıyla.
|
| 560 |
+
|
| 561 |
+
### 16.1 Klinik / Biyomedikal
|
| 562 |
+
|
| 563 |
+
- **BBB (Blood-Brain Barrier / Kan-Beyin Bariyeri):** Beyne giden damarlardaki özel hücre tabakası. Vücudun beyni zararlı maddelerden koruyan filtre. Bir ilacın etki etmesi için (eğer beyinde çalışacaksa) buradan geçmesi gerekir; geçmemesi gerekiyorsa (yan etki istemiyoruz) buraya takılması gerekir. İlaç keşfinde kritik bir filtre.
|
| 564 |
+
- **MSS (Merkezi Sinir Sistemi):** Beyin + omurilik. CNS olarak da geçer.
|
| 565 |
+
- **MRI (Magnetic Resonance Imaging):** Manyetik rezonans görüntüleme; beynin/dokunun kesit görüntüsü.
|
| 566 |
+
- **NIfTI (`.nii.gz`):** Beyin görüntüleme veri formatı; MRI taramaları bu formatta saklanır.
|
| 567 |
+
- **ROI (Region of Interest / İlgi Bölgesi):** Görüntüde ölçüm aldığımız bölge (örn. hipokampus). Bizde N×N×N grid hücreleri.
|
| 568 |
+
- **EEG (Electroencephalography):** Kafa derisindeki elektrotlarla beynin elektriksel aktivitesini ölçen yöntem.
|
| 569 |
+
- **EOG (Electrooculography):** Göz hareketlerinin elektriksel kaydı. EEG'de göz kırpma artefaktını ayıklamak için referans olarak kullanılır.
|
| 570 |
+
- **MNE-Python:** EEG/MEG işlemenin de facto bilimsel kütüphanesi (klinik standart).
|
| 571 |
+
- **ICA (Independent Component Analysis):** Karışık bir sinyali bağımsız kaynaklarına ayıran algoritma. Örn. EEG kaydında göz kırpma + beyin aktivitesi karışmıştır; ICA bunları ayrı "kaynak"lara böler, sonra göz kırpma kaynağını silip temiz sinyali yeniden inşa edersin.
|
| 572 |
+
- **PSD (Power Spectral Density / Güç Spektral Yoğunluğu):** Sinyalin gücünün frekanslara dağılımı. EEG'de **delta** (0.5–4 Hz, derin uyku), **theta** (4–8 Hz, uyku-uyanıklık geçişi), **alpha** (8–13 Hz, gevşek uyanıklık), **beta** (13–30 Hz, aktif düşünme), **gamma** (30+ Hz, yüksek bilişsel aktivite) bantları.
|
| 573 |
+
- **Bandpass Filter (0.5–40 Hz):** Sadece bu aralıktaki frekansları geçiren filtre. 0.5 Hz altı = DC drift (yavaş baseline kayması), 50/60 Hz = elektrik şebekesi gürültüsü → ikisini de keser.
|
| 574 |
+
- **Epoch:** EEG kaydını sabit süreli (örn. 2 saniyelik) eşit parçalara bölme. Her parça bir "örnek".
|
| 575 |
+
- **SMILES (Simplified Molecular Input Line Entry System):** Molekül yapısının kısa metin gösterimi. Örnekler: `CCO` → ethanol, `CN1C=NC2=C1C(=O)N(C(=O)N2C)C` → kafein. ML modeli SMILES'i değil onu vektöre çeviren fingerprint'i öğrenir.
|
| 576 |
+
- **Morgan Fingerprint:** Molekülün yapısal "barkod"u. 2048 bit'lik 0/1 vektör; her bit "molekülde şu alt yapı var mı?" sorusuna cevap. RDKit `GetMorganFingerprintAsBitVect`, radius=2 (her atomun 2 komşuluk uzağına kadar bakar).
|
| 577 |
+
- **RDKit:** Kimya/cheminformatics açık kaynak Python kütüphanesi. SMILES parse, validation, fingerprint hep RDKit ile.
|
| 578 |
+
- **Cyclosporine / Macrocycle:** ~1.2 kDa, 11-residue (peptit zinciri) makrosiklik (halkasal) immünosüpresan ilaç. BBB eğitim setindeki tipik küçük moleküllere göre devasa ve sıra dışı; OOD probe için kullanıyoruz.
|
| 579 |
+
|
| 580 |
+
### 16.2 Site Bias & Harmonizasyon
|
| 581 |
+
|
| 582 |
+
- **Site / Cihaz Bias'ı (Site Effect):** Aynı hastanın farklı hastane MRI cihazlarında farklı sayısal sonuç vermesi. Cihaz markası, manyetik alan gücü, yazılım sürümü, çekim protokolü hepsi sistematik kayma yaratır. Tedavi etkisi gibi görünebilir aslında sadece "cihaz farkı"dır.
|
| 583 |
+
- **Site-Gap (Site Boşluğu):** Aynı feature'ın siteler-arası ortalama farkının büyüklüğü. `max(per_site_means) - min(per_site_means)`. Sıfıra yakın olması iyidir (cihaz farkı gözükmüyor demektir).
|
| 584 |
+
- **Site-Gap Reduction (Site Boşluğu Azalması):** Harmonizasyon öncesi vs. sonrası site-gap oranı. Bizde **3290× reduction**: ComBat öncesi siteler-arası ortalama farkı 5.0, sonrası 0.0015 — fark 3290 kat çöktü. Yani MRI'ın hangi hastanede çekildiğinin tahmine etkisi neredeyse sıfırlandı.
|
| 585 |
+
- **ComBat Harmonization:** Çok-merkezli verilerde site bias'ı düzelten istatistiksel algoritma. Her sitenin ortalama (location) + varyans (scale) farkını referans dağılıma çeker, biyolojik sinyali korur. Empirical Bayes shrinkage'ı sayesinde az veri durumunda bile robust. Aslen gen ekspresyonu için icat edildi (Johnson 2007), MRI'a uyarlandı (Fortin 2017–2018).
|
| 586 |
+
- **Empirical Bayes:** Klasik Bayes'in pratik versiyonu — prior'u veriden tahmin eder, az örnekli grupları "ortalamaya çeker" (shrinkage). Az veriyle overfit'i önler.
|
| 587 |
+
- **Z-score Normalization:** `(x - mean) / std`. Sadece **mean**'i sıfıra çeker, scale farkını çözmez. ComBat hem mean hem scale'i düzelttiği için z-score'dan üstün.
|
| 588 |
+
- **KDE (Kernel Density Estimation):** Histogram'ın yumuşak versiyonu. Veri dağılımını düzgün eğri olarak çizer. Bizde her site bir renk; Pre-ComBat panelde renkler ayrışık tepeler oluşturur (cihaz farkı), Post-ComBat panelde üst üste biner (cihaz farkı kalktı) — harmonizasyonun **görsel kanıtı**.
|
| 589 |
+
- **Faceted Plot (Yüzlü Grafik):** Aynı grafik tipinin küçük çoklu versiyonları yan yana. Bizde Pre-ComBat ve Post-ComBat iki ayrı panel, ama aynı eksen.
|
| 590 |
+
- **Long-format DataFrame:** Her satırda tek `(subject, site, feature, value, state)` tuple'ı. Faceted plot ve `groupby` için ideal; "wide" tablonun melt edilmiş hâli.
|
| 591 |
+
|
| 592 |
+
### 16.3 Makine Öğrenmesi
|
| 593 |
+
|
| 594 |
+
- **Random Forest (RF):** Onlarca-yüzlerce karar ağacının bağımsız oy vermesi üzerine kurulu klasik ML algoritması. Küçük-orta veri setlerinde (≤10K örnek) derin öğrenmeyi yener çünkü deep learning bu boyutta overfit eder.
|
| 595 |
+
- **Stratified Split (Tabakalı Bölme):** Train/test ayırırken her sınıfın oranını koruma. Veride %30 pozitif varsa hem train hem test'te %30 olur. Class imbalance'da kritik.
|
| 596 |
+
- **`predict_proba`:** sklearn modellerinin "ham olasılık" çıktısı. Örn. `[0.18, 0.82]` = %82 olasılıkla pozitif sınıf. `argmax`'ı alırsak label, `max`'ı alırsak confidence.
|
| 597 |
+
- **Confidence (Güven Skoru):** Modelin tahminine verdiği olasılık (`max(predict_proba)`). %50 = bilmiyor, %99 = çok emin.
|
| 598 |
+
- **Calibration (Kalibrasyon):** "Model %80 derse, gerçekten %80 doğruluk gösteriyor mu?" sorusu. Kalibre olmayan model "çok eminim" der ama yanılır; kalibre model güveni ile gerçek precision'ı uyuşur.
|
| 599 |
+
- **Calibration Bin:** Confidence aralıkları (0.50, 0.60, 0.70, 0.75, 0.80, 0.90). Her aralık için held-out test'te precision ölçülür → kullanıcıya "≥%75 confident olduğumda gerçek precision %92" deme imkânı.
|
| 600 |
+
- **Precision (Kesinlik):** Model "pozitif" dediklerinin kaçı gerçekten pozitif? `TP / (TP + FP)`.
|
| 601 |
+
- **Support:** O bin'de kaç örnek var. n=18 demek "bu istatistik 18 örnekten hesaplandı".
|
| 602 |
+
- **Held-out Test Set:** Train'de hiç görmediği veri. Modelin gerçek genelleme performansını ölçen tek dürüst veri.
|
| 603 |
+
- **OOD (Out-of-Distribution):** Eğitim verisinde olmayan, "tanımadığım" tipte örnek. Cyclosporine örneği gibi. Sağlam model OOD'de düşük confidence ile **hedge eder** (kararsız kalır).
|
| 604 |
+
- **Drift (Veri Sapması):** Modelin gerçek dünyada gördüğü verinin zamanla eğitim dağılımından uzaklaşması. "Hasta profili değişti, model eskidi" sinyali.
|
| 605 |
+
- **Drift z-score:** Son 100 tahminin median'ı, eğitim sırasındaki median'dan kaç standart sapma uzakta? Formül: `(rolling_median - train_median) / max(train_std, 1e-9)`. Yorum: `|z|<1` normal, `1≤|z|<2` hafif kayma, `|z|≥2` ciddi kayma — retrain önerilir.
|
| 606 |
+
- **Trailing-100 / Rolling-100 Window:** Son 100 tahmin penceresi. Python `collections.deque(maxlen=100)` ile tutulur.
|
| 607 |
+
- **deque (Double-Ended Queue):** Python'da iki uçtan ekleme/çıkarma yapılabilen sabit-boyutlu kuyruk. `maxlen=100` = en yeni 100 eleman tutulur, eski olan otomatik düşer.
|
| 608 |
+
- **SHAP (SHapley Additive exPlanations):** Bir tahmindeki katkıyı feature'lar arasında "adil" şekilde paylaştıran yöntem. Oyun teorisinden Shapley value (Lloyd Shapley, 1953 — Nobel 2012) kullanır. "Bit #532 kararı +0.18 ittirdi, bit #1024 −0.05 ittirdi" der.
|
| 609 |
+
- **TreeExplainer:** SHAP'in karar ağacı modelleri için kapalı-form **exact** çözümü. Sampling yok, deterministik, tam aynı feature için tam aynı katkıyı verir (Lundberg & Lee 2018).
|
| 610 |
+
- **LIME:** SHAP'a alternatif "local linear approximation". Tree boundary'larında SHAP kadar kesin değil; biz SHAP tercih ediyoruz.
|
| 611 |
+
- **Feature Attribution (Öznitelik Atfı):** Her feature'ın tahmindeki katkısı (yön + büyüklük).
|
| 612 |
+
- **Feature Importance:** Tüm dataset üzerinde bir feature'ın model kararındaki ortalama önem ağırlığı (global). Attribution ise bireysel tahmindekidir (local).
|
| 613 |
+
- **MLflow:** ML deneylerini (run'ları) izleyen açık kaynak araç. "Hangi veri, hangi parametre, hangi metrik?" — hepsi otomatik loglanır.
|
| 614 |
+
- **Run / Run ID:** MLflow'da bir eğitim çalışmasının kaydı. Her train invocation yeni bir `run_id` (örn. `abc123...`) alır.
|
| 615 |
+
- **Provenance (Kanıt İzi / Veri Kökenli):** "Bu tahmin tam olarak hangi modelden, hangi run'dan, hangi veriyle çıktı?" sorusunun audit-trail cevabı. Tıbbi/regülatif sistemlerde zorunlu.
|
| 616 |
+
- **Joblib:** sklearn modellerini diske yazmak/okumak için kullanılan serileştirme kütüphanesi. `pickle`'ın numpy-aware versiyonu.
|
| 617 |
+
- **TDD (Test-Driven Development):** Önce test yaz (kabul kriteri), sonra kodu yaz. Döngü: **RED** (test fail) → **GREEN** (test pass) → **REFACTOR** (temizle, test hâlâ pass).
|
| 618 |
+
|
| 619 |
+
### 16.4 Backend & Mimari
|
| 620 |
+
|
| 621 |
+
- **FastAPI:** Python'da modern HTTP API framework (Pydantic schema'lar + async + auto-generated `/docs` Swagger UI).
|
| 622 |
+
- **Pydantic:** Python data validation kütüphanesi. Request/response schema'larını type-safe yapar; yanlış formatta input gelirse otomatik HTTP 422 döner.
|
| 623 |
+
- **Uvicorn:** Python ASGI sunucusu, FastAPI'yi çalıştırır.
|
| 624 |
+
- **Streamlit:** Python-only web dashboard framework. React/HTML yazmadan multi-tab interactive UI.
|
| 625 |
+
- **httpx:** Modern Python HTTP istemcisi (`requests`'in async-aware halefi). Streamlit container içinde FastAPI'ya bununla konuşur.
|
| 626 |
+
- **Supervisord:** Tek container içinde birden fazla process'i (uvicorn + streamlit) yöneten süreç yöneticisi. Biri ölünce yeniden başlatır.
|
| 627 |
+
- **BFF (Backend For Frontend) / Proxy Pattern:** UI ile gerçek API arasındaki ara katman. Bizde Streamlit container içinden FastAPI'yi çağırır; dışarı sadece Streamlit (port 7860) açıktır, FastAPI (port 8000) doğrudan internet'e açık değildir.
|
| 628 |
+
- **Endpoint:** API'nin URL yolu. Örn. `POST /predict/bbb`.
|
| 629 |
+
- **HTTP Status Codes:** `200` OK, `400` Bad Request (input geçersiz), `404` Not Found, `422` Unprocessable Entity (Pydantic validation fail), `503` Service Unavailable (model yüklenmedi).
|
| 630 |
+
- **Env Variable (Çevre Değişkeni):** Container'a dışarıdan verilen ayar. Örn. `NEUROBRIDGE_DISABLE_LLM=1`.
|
| 631 |
+
- **Kill-Switch:** Bir özelliği tek değişkenle devre dışı bırakma anahtarı. Demo gününde LLM ölürse `NEUROBRIDGE_DISABLE_LLM=1` ile sistem template path'e düşer ve hayatta kalır.
|
| 632 |
+
- **Graceful Failure / Graceful Degradation:** Bir bileşen hata verince **çökmek yerine** sistemin daha basit modda çalışmaya devam etmesi. Bizde: API HTTP 400 dönerse UI'da kırmızı ERROR yerine sarı WARNING; LLM ölürse template path; MLflow ölürse provenance "—" gösterir.
|
| 633 |
+
- **Fallback Chain:** Bir hata zincirinde sırayla denenen yedekler. LLM Provider A → Provider B → deterministic template gibi.
|
| 634 |
+
- **Hybrid Path:** İki yollu sistem; LLM çalışırsa LLM cevap verir, çalışmazsa template path. Source label ile hangi path'in döndüğü işaretlenir.
|
| 635 |
+
- **Deterministic Template:** Aynı input'a her zaman tıpatıp aynı cevabı veren string template. (LLM ise rastgelelik içerir.)
|
| 636 |
+
- **Idempotence (Idempotency):** Aynı işlemi 1 kez de 100 kez de çalıştırsan sonuç aynı kalır. Pipeline'larımız idempotent → re-run güvenli.
|
| 637 |
+
- **HF Spaces (Hugging Face Spaces):** ML demolarını host etmek için ücretsiz public platform. ML community hub.
|
| 638 |
+
- **Docker / Container:** Uygulama + tüm dependencies'in tek izole paket olarak çalıştırılması. "Bende çalışıyor" sorununu öldürür.
|
| 639 |
+
- **Dockerfile:** Container'ın nasıl build edileceğine dair talimat dosyası.
|
| 640 |
+
- **Cold Start:** Container'ın ilk ayağa kalkış süresi. Bizde build-time train sayesinde model image'a gömülü → cold start'ta train beklemeyiz.
|
| 641 |
+
- **Worker:** Web sunucusu işlem birimi. Her worker bağımsız bellek alanına sahip → drift deque'i her worker'da ayrıdır (production'da Redis'e taşınır).
|
| 642 |
+
|
| 643 |
+
### 16.5 LLM / Açıklanabilirlik
|
| 644 |
+
|
| 645 |
+
- **LLM (Large Language Model):** GPT, Llama, Gemini, Claude gibi büyük dil modelleri.
|
| 646 |
+
- **OpenRouter:** Birçok LLM provider'ı tek API arkasında toplayan servis. Free tier'da `llama-3.2-3b`, `gemini-flash`, `qwen` gibi seçenekler.
|
| 647 |
+
- **API Key:** Bir servise erişim için kişisel jeton. Asla repo'ya commit edilmez (HF Spaces "Variables and Secrets"'a girilir).
|
| 648 |
+
- **Rationale (Gerekçe):** Modelin tahminine dair doğal-dil açıklama (örn. "Predicted permeable with 82% confidence; SHAP attributions toward this label include bits 532 and 1024…").
|
| 649 |
+
- **Source Label:** Cevabın hangi kaynaktan geldiğini gösteren etiket. Bizde `source: "llm"` veya `source: "template"` — auditability için.
|
| 650 |
+
|
| 651 |
+
### 16.6 Veri Yapıları & Format
|
| 652 |
+
|
| 653 |
+
- **Parquet:** Sıkıştırılmış kolonlu veri formatı. CSV'den çok daha verimli (boyut + okuma hızı).
|
| 654 |
+
- **DataFrame:** pandas'ın tablo veri yapısı. SQL-benzeri operasyonlar Python içinde.
|
| 655 |
+
- **JSON:** API request/response'larının metin formatı.
|
| 656 |
+
- **`.fif` / `.edf`:** EEG kayıt formatları (FIF = MNE'nin native format'ı, EDF = European Data Format, daha yaygın).
|
| 657 |
+
- **Joblib `.joblib`:** sklearn modeli + custom attribute'lar serialized hâli.
|
| 658 |
+
|
| 659 |
+
---
|
| 660 |
+
|
| 661 |
+
## 17. Kapanış
|
| 662 |
|
| 663 |
NeuroBridge Enterprise hackathon'un sloganına ("**Stop Building Ideas. Start Building Systems.**") en doğrudan cevap. 8 gün boyunca disiplinli TDD + Subagent-Driven Development ile inşa ettik. Public deploy'lu, jüri tarayıcıdan tıklayıp dokunabiliyor. 184 test green, 96.8% jüri skoru projeksiyonu, 5/5 hackathon track strong, 4/4 Living Systems pillar full.
|
| 664 |
|
docs/superpowers/plans/2026-04-30-hf-space-live-audit-fixes.md
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HF Space Live Audit Fixes Implementation Plan
|
| 2 |
+
|
| 3 |
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
| 4 |
+
|
| 5 |
+
**Goal:** Fix three production bugs found on the live HF Space (https://mekosotto-hackathon.hf.space, commit `84572d9`): EEG tab default path is unreachable, the Experiments tab is permanently empty, and the BBB decision card's MLflow provenance strip shows dashes — all because `NEUROBRIDGE_DISABLE_MLFLOW=1` is set in the Dockerfile and `data/raw/eeg.fif` is never seeded.
|
| 6 |
+
|
| 7 |
+
**Architecture:** Three sealed, sequential fixes. (1) Frontend default path swap — Signal tab points at the EEG fixture so a fresh user can click "Run" with zero ceremony. (2) Dockerfile hardening — drop the `NEUROBRIDGE_DISABLE_MLFLOW=1` env, seed `data/raw/eeg.fif` from the fixture, and run the EEG + MRI pipelines at build time so the file-store `mlruns/` is populated with one run per modality. (3) Smoke verification — a sealed `scripts/smoke_hf_space.sh` that probes the public URL plus a manual click-through checklist for the JS-only Streamlit interactions we cannot script.
|
| 8 |
+
|
| 9 |
+
**Tech Stack:** Python 3.12, pytest, FastAPI, Streamlit, MLflow (file-store), Docker (HF Spaces SDK), supervisord.
|
| 10 |
+
|
| 11 |
+
**Test growth:** 184 → 188 (one frontend default-path test, one Dockerfile env test, one Dockerfile pipeline-seeding test, plus an updated existing test).
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## File Structure
|
| 16 |
+
|
| 17 |
+
| Path | Action | Responsibility |
|
| 18 |
+
|---|---|---|
|
| 19 |
+
| `src/frontend/app.py` | Modify line 1200 | Default EEG input becomes `tests/fixtures/eeg_sample.fif` so the Signal tab works on first click |
|
| 20 |
+
| `Dockerfile` | Modify env block + RUN step | Remove MLflow kill-switch; seed `data/raw/eeg.fif`; run EEG + MRI pipelines at build |
|
| 21 |
+
| `Dockerfile.hf` | Modify env block + RUN step | Same as Dockerfile (kept in sync; HF auto-discovers `Dockerfile`) |
|
| 22 |
+
| `tests/frontend/test_app_defaults.py` | Create | Asserts the EEG default path is a real fixture file, not the missing `data/raw/eeg.fif` |
|
| 23 |
+
| `tests/deploy/test_dockerfile_hf.py` | Modify lines 30-50 | Flip MLflow assertion (must be absent), add EEG-seed + multi-pipeline assertions |
|
| 24 |
+
| `scripts/smoke_hf_space.sh` | Create | Sealed health probe of the public Space URL — runs after deploy |
|
| 25 |
+
| `docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md` | Create | Manual click-through checklist (browser-only verifications) |
|
| 26 |
+
|
| 27 |
+
**Reused utilities:** `src.pipelines.eeg_pipeline.run_pipeline`, `src.pipelines.mri_pipeline.run_pipeline` — both are kwargs-based with `Path` arguments, callable via `python -c` from the Dockerfile RUN step.
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## Task 1: Frontend EEG default path
|
| 32 |
+
|
| 33 |
+
**Files:**
|
| 34 |
+
- Modify: `src/frontend/app.py:1200`
|
| 35 |
+
- Test: `tests/frontend/test_app_defaults.py`
|
| 36 |
+
|
| 37 |
+
**Why this task is first:** It's the smallest change with its own test, lets us validate the workflow before touching Docker.
|
| 38 |
+
|
| 39 |
+
- [ ] **Step 1: Write the failing test**
|
| 40 |
+
|
| 41 |
+
Create `tests/frontend/test_app_defaults.py`:
|
| 42 |
+
|
| 43 |
+
```python
|
| 44 |
+
"""Defaults shown in the Streamlit UI must point at files that actually exist
|
| 45 |
+
in the deployed image. The HF container does not seed `data/raw/eeg.fif`
|
| 46 |
+
unless we explicitly do so in the Dockerfile, so the EEG tab default must
|
| 47 |
+
either be a fixture file (always present) or be seeded server-side.
|
| 48 |
+
"""
|
| 49 |
+
from __future__ import annotations
|
| 50 |
+
|
| 51 |
+
from pathlib import Path
|
| 52 |
+
import re
|
| 53 |
+
|
| 54 |
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 55 |
+
APP_PY = REPO_ROOT / "src" / "frontend" / "app.py"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _eeg_default_path() -> str:
|
| 59 |
+
"""Extract the default value passed to the EEG `input_path` text_input.
|
| 60 |
+
|
| 61 |
+
The line looks like:
|
| 62 |
+
eeg_in = st.text_input("Input FIF/EDF path", "tests/fixtures/eeg_sample.fif", key="eeg_in")
|
| 63 |
+
We pull the second positional arg (the default value).
|
| 64 |
+
"""
|
| 65 |
+
text = APP_PY.read_text()
|
| 66 |
+
match = re.search(
|
| 67 |
+
r'st\.text_input\(\s*"Input FIF/EDF path"\s*,\s*"([^"]+)"',
|
| 68 |
+
text,
|
| 69 |
+
)
|
| 70 |
+
assert match is not None, "could not locate EEG text_input default in app.py"
|
| 71 |
+
return match.group(1)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_eeg_default_path_points_at_existing_file() -> None:
|
| 75 |
+
default = _eeg_default_path()
|
| 76 |
+
abs_path = REPO_ROOT / default
|
| 77 |
+
assert abs_path.exists(), (
|
| 78 |
+
f"EEG default path {default!r} resolves to {abs_path} which does "
|
| 79 |
+
f"not exist on disk. The HF container does not seed this path "
|
| 80 |
+
f"either, so users get an HTTP 404 the moment they click Run."
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def test_eeg_default_path_is_eeg_sample_fixture() -> None:
|
| 85 |
+
"""Pin the exact fix: default must be the canonical fixture so both
|
| 86 |
+
local dev and the deployed image agree."""
|
| 87 |
+
assert _eeg_default_path() == "tests/fixtures/eeg_sample.fif"
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
- [ ] **Step 2: Run the test and confirm it fails**
|
| 91 |
+
|
| 92 |
+
Run: `pytest tests/frontend/test_app_defaults.py -v`
|
| 93 |
+
Expected: 2 failures — both assertions trip because the current default is `data/raw/eeg.fif` which neither exists locally (gitignored) nor in the HF image.
|
| 94 |
+
|
| 95 |
+
- [ ] **Step 3: Patch the default in `src/frontend/app.py:1200`**
|
| 96 |
+
|
| 97 |
+
Edit `src/frontend/app.py`. Find:
|
| 98 |
+
|
| 99 |
+
```python
|
| 100 |
+
eeg_in = st.text_input("Input FIF/EDF path", "data/raw/eeg.fif", key="eeg_in")
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
Replace with:
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
eeg_in = st.text_input(
|
| 107 |
+
"Input FIF/EDF path",
|
| 108 |
+
"tests/fixtures/eeg_sample.fif",
|
| 109 |
+
key="eeg_in",
|
| 110 |
+
help=(
|
| 111 |
+
"Defaults to the bundled EEG fixture so the demo runs out of "
|
| 112 |
+
"the box. Replace with your own .fif/.edf path on a real run."
|
| 113 |
+
),
|
| 114 |
+
)
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
- [ ] **Step 4: Run the test and confirm it passes**
|
| 118 |
+
|
| 119 |
+
Run: `pytest tests/frontend/test_app_defaults.py -v`
|
| 120 |
+
Expected: 2 passed.
|
| 121 |
+
|
| 122 |
+
- [ ] **Step 5: Run the full frontend test suite to confirm no regressions**
|
| 123 |
+
|
| 124 |
+
Run: `pytest tests/frontend/ -v`
|
| 125 |
+
Expected: 4 passed (2 existing + 2 new).
|
| 126 |
+
|
| 127 |
+
- [ ] **Step 6: Commit**
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
git add src/frontend/app.py tests/frontend/test_app_defaults.py
|
| 131 |
+
git commit -m "fix(frontend): EEG default path points at fixture (HF container has no data/raw/eeg.fif)"
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
## Task 2: Update existing Dockerfile MLflow assertion (RED for Task 3)
|
| 137 |
+
|
| 138 |
+
**Files:**
|
| 139 |
+
- Modify: `tests/deploy/test_dockerfile_hf.py:30-50`
|
| 140 |
+
|
| 141 |
+
**Why now:** The existing test asserts `NEUROBRIDGE_DISABLE_MLFLOW` is *present* in the Dockerfile. We need to flip it before Task 3 removes the env, otherwise Task 3's edit will fail this test (= a hostile RED that hides the real failure mode).
|
| 142 |
+
|
| 143 |
+
- [ ] **Step 1: Read the existing test to understand the structure**
|
| 144 |
+
|
| 145 |
+
Run: `cat tests/deploy/test_dockerfile_hf.py`
|
| 146 |
+
Confirm line 44-46 contains the `assert "neurobridge_disable_mlflow" in text` block.
|
| 147 |
+
|
| 148 |
+
- [ ] **Step 2: Replace the MLflow assertion + tighten the docstring**
|
| 149 |
+
|
| 150 |
+
Edit `tests/deploy/test_dockerfile_hf.py`. Replace the entire `test_dockerfile_contains_required_stages` method body with:
|
| 151 |
+
|
| 152 |
+
```python
|
| 153 |
+
def test_dockerfile_contains_required_stages(self, dockerfile_text):
|
| 154 |
+
"""The HF Dockerfile must:
|
| 155 |
+
- Start FROM a Python base
|
| 156 |
+
- Install requirements.txt
|
| 157 |
+
- Seed data/raw/bbbp.csv AND data/raw/eeg.fif from fixtures
|
| 158 |
+
- Build the BBB model artifact at build time
|
| 159 |
+
- Run all three pipelines (BBB / EEG / MRI) so mlruns/ has one
|
| 160 |
+
run per modality available to /experiments/runs at startup
|
| 161 |
+
- Expose port 7860 (HF Spaces convention)
|
| 162 |
+
- Launch via supervisord
|
| 163 |
+
"""
|
| 164 |
+
text = dockerfile_text.lower()
|
| 165 |
+
assert "from python" in text, "must FROM a Python base image"
|
| 166 |
+
assert "requirements.txt" in text, "must reference requirements.txt"
|
| 167 |
+
assert "src.models.bbb_model" in dockerfile_text, (
|
| 168 |
+
"must build the BBB model artifact at image-build time"
|
| 169 |
+
)
|
| 170 |
+
assert "src.pipelines.bbb_pipeline" in dockerfile_text, (
|
| 171 |
+
"must run BBB pipeline at build so mlruns/ has a BBB run"
|
| 172 |
+
)
|
| 173 |
+
assert "src.pipelines.eeg_pipeline" in dockerfile_text, (
|
| 174 |
+
"must run EEG pipeline at build so mlruns/ has an EEG run"
|
| 175 |
+
)
|
| 176 |
+
assert "src.pipelines.mri_pipeline" in dockerfile_text, (
|
| 177 |
+
"must run MRI pipeline at build so mlruns/ has an MRI run"
|
| 178 |
+
)
|
| 179 |
+
assert "tests/fixtures/eeg_sample.fif" in dockerfile_text, (
|
| 180 |
+
"must seed data/raw/eeg.fif from the bundled fixture so the "
|
| 181 |
+
"Signal tab works without user file upload"
|
| 182 |
+
)
|
| 183 |
+
assert "7860" in text, "must expose port 7860 (HF Spaces convention)"
|
| 184 |
+
assert "supervisord" in text, (
|
| 185 |
+
"must launch FastAPI + Streamlit via supervisord"
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
def test_dockerfile_does_not_disable_mlflow(self, dockerfile_text):
|
| 189 |
+
"""The kill-switch was removed in 2026-04-30 — file-store mlruns/
|
| 190 |
+
is built into the image and is safe to expose on the read-only
|
| 191 |
+
demo. Re-introducing the kill-switch would silently kill the
|
| 192 |
+
Experiments tab and the BBB provenance strip."""
|
| 193 |
+
text = dockerfile_text.lower()
|
| 194 |
+
assert "neurobridge_disable_mlflow=1" not in text, (
|
| 195 |
+
"Dockerfile must NOT disable MLflow — that empties the "
|
| 196 |
+
"Experiments tab and blanks the BBB provenance strip. "
|
| 197 |
+
"If you need to disable MLflow at runtime, set the env "
|
| 198 |
+
"manually on the Space, do not bake it into the image."
|
| 199 |
+
)
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
- [ ] **Step 3: Run the test and confirm RED**
|
| 203 |
+
|
| 204 |
+
Run: `pytest tests/deploy/test_dockerfile_hf.py -v`
|
| 205 |
+
Expected: `test_dockerfile_contains_required_stages` FAILS on the new pipeline assertions; `test_dockerfile_does_not_disable_mlflow` FAILS because the current Dockerfile still has `NEUROBRIDGE_DISABLE_MLFLOW=1`. (Both correctly RED — they describe the desired post-fix state.)
|
| 206 |
+
|
| 207 |
+
- [ ] **Step 4: Commit (RED)**
|
| 208 |
+
|
| 209 |
+
```bash
|
| 210 |
+
git add tests/deploy/test_dockerfile_hf.py
|
| 211 |
+
git commit -m "test(deploy): assert Dockerfile seeds EEG fixture, runs all pipelines, drops MLflow kill-switch (RED)"
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
---
|
| 215 |
+
|
| 216 |
+
## Task 3: Dockerfile fixes — drop kill-switch, seed EEG fixture, run all pipelines
|
| 217 |
+
|
| 218 |
+
**Files:**
|
| 219 |
+
- Modify: `Dockerfile` (canonical, alias of Dockerfile.hf)
|
| 220 |
+
- Modify: `Dockerfile.hf` (kept in sync)
|
| 221 |
+
|
| 222 |
+
**Why this works:** Both `eeg_pipeline.run_pipeline` and `mri_pipeline.run_pipeline` accept Path arguments via kwargs, so we can drive them from `python -c`. They each call `track_pipeline_run(...)` which writes to `mlruns/` via the local file-store, so removing the kill-switch lets `/experiments/runs` find them.
|
| 223 |
+
|
| 224 |
+
- [ ] **Step 1: Read the current Dockerfile**
|
| 225 |
+
|
| 226 |
+
Run: `cat Dockerfile`
|
| 227 |
+
Confirm the env block contains `NEUROBRIDGE_DISABLE_MLFLOW=1` and the RUN step only seeds `bbbp.csv`.
|
| 228 |
+
|
| 229 |
+
- [ ] **Step 2: Patch `Dockerfile` — env block + RUN step**
|
| 230 |
+
|
| 231 |
+
Edit `Dockerfile`. Replace the env block (lines 7-13):
|
| 232 |
+
|
| 233 |
+
```dockerfile
|
| 234 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 235 |
+
PYTHONUNBUFFERED=1 \
|
| 236 |
+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 237 |
+
PIP_NO_CACHE_DIR=1 \
|
| 238 |
+
DEPLOY_ENV=hf_spaces \
|
| 239 |
+
NEUROBRIDGE_DISABLE_MLFLOW=1 \
|
| 240 |
+
NEUROBRIDGE_DISABLE_LLM=1
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
with (drop only the MLflow kill-switch; LLM stays disabled because OpenRouter requires a key):
|
| 244 |
+
|
| 245 |
+
```dockerfile
|
| 246 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 247 |
+
PYTHONUNBUFFERED=1 \
|
| 248 |
+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 249 |
+
PIP_NO_CACHE_DIR=1 \
|
| 250 |
+
DEPLOY_ENV=hf_spaces \
|
| 251 |
+
NEUROBRIDGE_DISABLE_LLM=1
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
Then replace the build-time data RUN step (lines 39-42):
|
| 255 |
+
|
| 256 |
+
```dockerfile
|
| 257 |
+
RUN mkdir -p data/raw data/processed && \
|
| 258 |
+
cp tests/fixtures/bbbp_sample.csv data/raw/bbbp.csv && \
|
| 259 |
+
python -m src.pipelines.bbb_pipeline && \
|
| 260 |
+
python -m src.models.bbb_model
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
with:
|
| 264 |
+
|
| 265 |
+
```dockerfile
|
| 266 |
+
# Seed raw data from fixtures so the deployed Signal/Image/Molecule tabs
|
| 267 |
+
# work on first click. Then run all three pipelines so mlruns/ contains
|
| 268 |
+
# one run per modality — feeds /experiments/runs and the BBB provenance
|
| 269 |
+
# strip. data/raw/* is gitignored locally so we cannot COPY it.
|
| 270 |
+
RUN mkdir -p data/raw data/processed && \
|
| 271 |
+
cp tests/fixtures/bbbp_sample.csv data/raw/bbbp.csv && \
|
| 272 |
+
cp tests/fixtures/eeg_sample.fif data/raw/eeg.fif && \
|
| 273 |
+
python -m src.pipelines.bbb_pipeline && \
|
| 274 |
+
python -m src.models.bbb_model && \
|
| 275 |
+
python -c "from pathlib import Path; from src.pipelines.eeg_pipeline import run_pipeline; run_pipeline(input_path=Path('tests/fixtures/eeg_sample.fif'), output_path=Path('data/processed/eeg_features.parquet'))" && \
|
| 276 |
+
python -c "from pathlib import Path; from src.pipelines.mri_pipeline import run_pipeline; run_pipeline(input_dir=Path('tests/fixtures/mri_sample'), sites_csv=Path('tests/fixtures/mri_sample/sites.csv'), output_path=Path('data/processed/mri_features.parquet'))"
|
| 277 |
+
```
|
| 278 |
+
|
| 279 |
+
- [ ] **Step 3: Mirror the same edit into `Dockerfile.hf`**
|
| 280 |
+
|
| 281 |
+
Edit `Dockerfile.hf` to be byte-identical to `Dockerfile` (same env block, same RUN step). Run:
|
| 282 |
+
|
| 283 |
+
```bash
|
| 284 |
+
diff Dockerfile Dockerfile.hf
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
Expected: no output (files identical).
|
| 288 |
+
|
| 289 |
+
- [ ] **Step 4: Run the deploy tests to confirm GREEN**
|
| 290 |
+
|
| 291 |
+
Run: `pytest tests/deploy/ -v`
|
| 292 |
+
Expected: 2 passed (`test_dockerfile_contains_required_stages` + `test_dockerfile_does_not_disable_mlflow`).
|
| 293 |
+
|
| 294 |
+
- [ ] **Step 5: Run the full test suite — must stay green**
|
| 295 |
+
|
| 296 |
+
Run: `pytest -q`
|
| 297 |
+
Expected: 188 passed (184 baseline + 2 new frontend defaults + 1 new deploy test + 1 widened existing test). Adjust expectation only if your local count differs by exactly the same delta.
|
| 298 |
+
|
| 299 |
+
- [ ] **Step 6: Smoke-build locally to catch syntax errors before pushing**
|
| 300 |
+
|
| 301 |
+
Run (under 5 min):
|
| 302 |
+
|
| 303 |
+
```bash
|
| 304 |
+
docker build -t neurobridge-hf-test -f Dockerfile .
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
Expected: build completes; the final two `python -c` invocations log INFO lines like:
|
| 308 |
+
```
|
| 309 |
+
INFO | Wrote processed features to data/processed/eeg_features.parquet (rows=N, cols=M)
|
| 310 |
+
INFO | Wrote processed features to data/processed/mri_features.parquet (rows=N, cols=M)
|
| 311 |
+
```
|
| 312 |
+
|
| 313 |
+
If `docker` is not available locally, skip this step — CI on HF Spaces will catch a broken Dockerfile within ~3 minutes of push.
|
| 314 |
+
|
| 315 |
+
- [ ] **Step 7: Commit**
|
| 316 |
+
|
| 317 |
+
```bash
|
| 318 |
+
git add Dockerfile Dockerfile.hf
|
| 319 |
+
git commit -m "fix(deploy): seed EEG fixture, run all pipelines at build, drop MLflow kill-switch
|
| 320 |
+
|
| 321 |
+
The Experiments tab was permanently empty and the BBB provenance strip
|
| 322 |
+
showed dashes because NEUROBRIDGE_DISABLE_MLFLOW=1 short-circuited the
|
| 323 |
+
MLflow file-store lookups even though mlruns/ IS produced inside the
|
| 324 |
+
image at build time. Drop the env, run EEG + MRI pipelines too so all
|
| 325 |
+
three experiments have at least one run, and seed data/raw/eeg.fif
|
| 326 |
+
from the fixture so the Signal tab works on first click."
|
| 327 |
+
```
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## Task 4: Smoke probe script — what we CAN automate
|
| 332 |
+
|
| 333 |
+
**Files:**
|
| 334 |
+
- Create: `scripts/smoke_hf_space.sh`
|
| 335 |
+
|
| 336 |
+
**Why:** HF Spaces only exposes Streamlit on :7860 publicly. FastAPI on :8000 is container-internal, so we cannot probe `/predict/bbb` or `/experiments/runs` directly. We CAN probe Streamlit's own health and the Space's HTTP envelope.
|
| 337 |
+
|
| 338 |
+
- [ ] **Step 1: Create the script directory if missing**
|
| 339 |
+
|
| 340 |
+
Run:
|
| 341 |
+
|
| 342 |
+
```bash
|
| 343 |
+
mkdir -p scripts
|
| 344 |
+
```
|
| 345 |
+
|
| 346 |
+
- [ ] **Step 2: Write `scripts/smoke_hf_space.sh`**
|
| 347 |
+
|
| 348 |
+
Create `scripts/smoke_hf_space.sh`:
|
| 349 |
+
|
| 350 |
+
```bash
|
| 351 |
+
#!/usr/bin/env bash
|
| 352 |
+
# Sealed smoke probe for the live HF Space.
|
| 353 |
+
# Verifies what we can verify without a browser: HTTP envelope, Streamlit
|
| 354 |
+
# health, response headers. Returns 0 on success, 1 on any failure.
|
| 355 |
+
#
|
| 356 |
+
# Usage: scripts/smoke_hf_space.sh [base_url]
|
| 357 |
+
# default base_url: https://mekosotto-hackathon.hf.space
|
| 358 |
+
|
| 359 |
+
set -euo pipefail
|
| 360 |
+
|
| 361 |
+
BASE="${1:-https://mekosotto-hackathon.hf.space}"
|
| 362 |
+
FAIL=0
|
| 363 |
+
|
| 364 |
+
probe() {
|
| 365 |
+
local label="$1" url="$2" expect="$3"
|
| 366 |
+
local actual
|
| 367 |
+
actual="$(curl -sS -o /dev/null -w "%{http_code}" "$url")"
|
| 368 |
+
if [[ "$actual" == "$expect" ]]; then
|
| 369 |
+
printf " OK %-40s %s\n" "$label" "$actual"
|
| 370 |
+
else
|
| 371 |
+
printf " FAIL %-40s expected=%s actual=%s\n" "$label" "$expect" "$actual"
|
| 372 |
+
FAIL=1
|
| 373 |
+
fi
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
echo "Probing $BASE"
|
| 377 |
+
probe "frontend root" "$BASE/" "200"
|
| 378 |
+
probe "streamlit health" "$BASE/_stcore/health" "200"
|
| 379 |
+
probe "fastapi NOT publicly mounted (good)" "$BASE/health" "403"
|
| 380 |
+
|
| 381 |
+
echo
|
| 382 |
+
if [[ "$FAIL" == "0" ]]; then
|
| 383 |
+
echo "Smoke OK — proceed to manual click-through checklist:"
|
| 384 |
+
echo " docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md"
|
| 385 |
+
exit 0
|
| 386 |
+
else
|
| 387 |
+
echo "Smoke FAILED — see HF Space logs at:"
|
| 388 |
+
echo " https://huggingface.co/spaces/mekosotto/hackathon/discussions"
|
| 389 |
+
exit 1
|
| 390 |
+
fi
|
| 391 |
+
```
|
| 392 |
+
|
| 393 |
+
- [ ] **Step 3: Make it executable**
|
| 394 |
+
|
| 395 |
+
Run:
|
| 396 |
+
|
| 397 |
+
```bash
|
| 398 |
+
chmod +x scripts/smoke_hf_space.sh
|
| 399 |
+
```
|
| 400 |
+
|
| 401 |
+
- [ ] **Step 4: Test the script against the currently-deployed (broken) Space**
|
| 402 |
+
|
| 403 |
+
Run:
|
| 404 |
+
|
| 405 |
+
```bash
|
| 406 |
+
scripts/smoke_hf_space.sh
|
| 407 |
+
```
|
| 408 |
+
|
| 409 |
+
Expected output: all three probes return OK (frontend 200, streamlit 200, fastapi 403). The smoke envelope passes even on the broken deploy — it's checking infrastructure, not feature correctness. Feature correctness is the manual checklist (Task 5).
|
| 410 |
+
|
| 411 |
+
- [ ] **Step 5: Commit**
|
| 412 |
+
|
| 413 |
+
```bash
|
| 414 |
+
git add scripts/smoke_hf_space.sh
|
| 415 |
+
git commit -m "test(deploy): scripts/smoke_hf_space.sh — sealed HTTP envelope probe"
|
| 416 |
+
```
|
| 417 |
+
|
| 418 |
+
---
|
| 419 |
+
|
| 420 |
+
## Task 5: Manual click-through checklist (browser-only verifications)
|
| 421 |
+
|
| 422 |
+
**Files:**
|
| 423 |
+
- Create: `docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md`
|
| 424 |
+
|
| 425 |
+
**Why:** Streamlit is JS-rendered; we cannot drive it from curl. Document the exact button-by-button verification so the user (or a reviewer) walks the demo and catches anything the unit tests missed.
|
| 426 |
+
|
| 427 |
+
- [ ] **Step 1: Create the notes directory if missing**
|
| 428 |
+
|
| 429 |
+
Run:
|
| 430 |
+
|
| 431 |
+
```bash
|
| 432 |
+
mkdir -p docs/superpowers/notes
|
| 433 |
+
```
|
| 434 |
+
|
| 435 |
+
- [ ] **Step 2: Write `docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md`**
|
| 436 |
+
|
| 437 |
+
Create `docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md`:
|
| 438 |
+
|
| 439 |
+
```markdown
|
| 440 |
+
# HF Space Manual Smoke Checklist — 2026-04-30
|
| 441 |
+
|
| 442 |
+
After the deploy completes (~3-5 min after `git push hf main`), open
|
| 443 |
+
https://mekosotto-hackathon.hf.space/ and walk this list. Anything
|
| 444 |
+
that fails is a regression vs the audit fix in
|
| 445 |
+
`docs/superpowers/plans/2026-04-30-hf-space-live-audit-fixes.md`.
|
| 446 |
+
|
| 447 |
+
## Hero strip (top of page)
|
| 448 |
+
|
| 449 |
+
- [ ] Hero title `NeuroBridge Enterprise` fades up smoothly (not instant)
|
| 450 |
+
- [ ] Status row shows three dots:
|
| 451 |
+
- [ ] `api · operational` (green)
|
| 452 |
+
- [ ] `mlflow · tracking` (green) — **was muted before this fix**
|
| 453 |
+
- [ ] `explainer · template only` (muted, expected — LLM stays disabled)
|
| 454 |
+
|
| 455 |
+
## Molecule tab (BBB)
|
| 456 |
+
|
| 457 |
+
- [ ] Default edge case "Custom input (default)" is selected
|
| 458 |
+
- [ ] Input box shows `CCO`
|
| 459 |
+
- [ ] Click "Predict BBB permeability"
|
| 460 |
+
- [ ] Decision card animates in with spring scale-in on the verdict
|
| 461 |
+
- [ ] Provenance strip shows real values:
|
| 462 |
+
- [ ] `mlflow · <8-char run id>` (NOT `—`) — **was `—` before this fix**
|
| 463 |
+
- [ ] `model · v1`
|
| 464 |
+
- [ ] `trained · <ISO timestamp>` (NOT `—`)
|
| 465 |
+
- [ ] `n=<integer>` (NOT `n=—`)
|
| 466 |
+
- [ ] Verdict reads `permeable` with confidence ~80-100%
|
| 467 |
+
- [ ] SHAP bar chart renders with sand-colored bars
|
| 468 |
+
- [ ] Switch dropdown to "Invalid SMILES" → click Predict → see yellow warning, NOT red error
|
| 469 |
+
|
| 470 |
+
## Signal tab (EEG)
|
| 471 |
+
|
| 472 |
+
- [ ] Default input field shows `tests/fixtures/eeg_sample.fif` — **was `data/raw/eeg.fif` before this fix**
|
| 473 |
+
- [ ] Click "Run EEG pipeline"
|
| 474 |
+
- [ ] Result card shows rows / columns / duration_sec / mlflow_run_id
|
| 475 |
+
- [ ] Expand "Ask the AI Assistant about this EEG run"
|
| 476 |
+
- [ ] Click "Ask AI Assistant" → see deterministic-template rationale (no error)
|
| 477 |
+
|
| 478 |
+
## Image tab (MRI)
|
| 479 |
+
|
| 480 |
+
- [ ] Defaults: `tests/fixtures/mri_sample` and `tests/fixtures/mri_sample/sites.csv`
|
| 481 |
+
- [ ] Click "Run ComBat diagnostics"
|
| 482 |
+
- [ ] Three KPI cards render: Site-gap (Pre), Site-gap (Post), Reduction factor
|
| 483 |
+
- [ ] Pre/Post KDE altair chart renders
|
| 484 |
+
- [ ] Expand "Ask the AI Assistant about this ComBat run" → click → see rationale
|
| 485 |
+
|
| 486 |
+
## AI Assistant tab
|
| 487 |
+
|
| 488 |
+
- [ ] After running a BBB prediction in the Molecule tab, this tab shows
|
| 489 |
+
"Latest prediction: ..." caption
|
| 490 |
+
- [ ] Click "Ask the AI Assistant" → conversation appears with source =
|
| 491 |
+
`template` and model = `—` (LLM intentionally disabled on HF)
|
| 492 |
+
|
| 493 |
+
## Experiments tab
|
| 494 |
+
|
| 495 |
+
- [ ] Table loads with **at least 3 rows** — one each for `bbb_pipeline`,
|
| 496 |
+
`eeg_pipeline`, `mri_pipeline` — **was empty before this fix**
|
| 497 |
+
- [ ] Compare-two-runs section is visible (≥2 rows are present)
|
| 498 |
+
- [ ] Pick two run IDs → click "Show diff" → diff table renders
|
| 499 |
+
|
| 500 |
+
## Sidebar
|
| 501 |
+
|
| 502 |
+
- [ ] Toggle "Dark mode" off → page rebuilds with cream paper theme
|
| 503 |
+
- [ ] Toggle back on → page rebuilds with editorial dark theme
|
| 504 |
+
- [ ] Both themes preserve the sand accent on the hero word-mark
|
| 505 |
+
|
| 506 |
+
## Reduced-motion respect
|
| 507 |
+
|
| 508 |
+
- [ ] (Optional) Open DevTools → Rendering → enable "prefers-reduced-motion"
|
| 509 |
+
- [ ] Reload — animations are near-instant (< 1ms duration), but layout
|
| 510 |
+
and content are unchanged
|
| 511 |
+
```
|
| 512 |
+
|
| 513 |
+
- [ ] **Step 3: Commit**
|
| 514 |
+
|
| 515 |
+
```bash
|
| 516 |
+
git add docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md
|
| 517 |
+
git commit -m "docs(notes): manual click-through checklist for HF Space smoke verification"
|
| 518 |
+
```
|
| 519 |
+
|
| 520 |
+
---
|
| 521 |
+
|
| 522 |
+
## Task 6: Final verification — full suite + smoke envelope
|
| 523 |
+
|
| 524 |
+
- [ ] **Step 1: Run the full pytest suite locally**
|
| 525 |
+
|
| 526 |
+
Run: `pytest -q`
|
| 527 |
+
Expected: `188 passed`. (184 baseline + 2 new frontend tests + 1 new deploy test + 1 widened existing test = 188.)
|
| 528 |
+
|
| 529 |
+
- [ ] **Step 2: Run pytest with UserWarning escalation to confirm no warnings**
|
| 530 |
+
|
| 531 |
+
Run: `pytest -W error::UserWarning tests/`
|
| 532 |
+
Expected: `188 passed, 0 escalations`.
|
| 533 |
+
|
| 534 |
+
- [ ] **Step 3: Run the smoke envelope against the still-broken deployed Space**
|
| 535 |
+
|
| 536 |
+
Run: `scripts/smoke_hf_space.sh`
|
| 537 |
+
Expected: 3 OK lines (we have not deployed yet — this confirms the script itself works).
|
| 538 |
+
|
| 539 |
+
- [ ] **Step 4: Inspect the local commit graph**
|
| 540 |
+
|
| 541 |
+
Run: `git log --oneline -10`
|
| 542 |
+
Expected: 5 new commits on top of `84572d9`:
|
| 543 |
+
```
|
| 544 |
+
<hash> docs(notes): manual click-through checklist for HF Space smoke verification
|
| 545 |
+
<hash> test(deploy): scripts/smoke_hf_space.sh — sealed HTTP envelope probe
|
| 546 |
+
<hash> fix(deploy): seed EEG fixture, run all pipelines at build, drop MLflow kill-switch
|
| 547 |
+
<hash> test(deploy): assert Dockerfile seeds EEG fixture, runs all pipelines, drops MLflow kill-switch (RED)
|
| 548 |
+
<hash> fix(frontend): EEG default path points at fixture (HF container has no data/raw/eeg.fif)
|
| 549 |
+
84572d9 feat(frontend): premium motion layer — Apple HIG / Netflix transitions
|
| 550 |
+
```
|
| 551 |
+
|
| 552 |
+
---
|
| 553 |
+
|
| 554 |
+
## Task 7: Hand-off to user — push + verify
|
| 555 |
+
|
| 556 |
+
The HF write token from the previous session was revoked (correct security move). The push must be done by the user with a fresh token.
|
| 557 |
+
|
| 558 |
+
- [ ] **Step 1: User generates a new HF write token**
|
| 559 |
+
|
| 560 |
+
Visit https://huggingface.co/settings/tokens → "New token" → role "Write" → copy.
|
| 561 |
+
|
| 562 |
+
- [ ] **Step 2: User pushes**
|
| 563 |
+
|
| 564 |
+
```bash
|
| 565 |
+
cd /Users/mertgungor/Desktop/hackathon
|
| 566 |
+
git push hf main
|
| 567 |
+
```
|
| 568 |
+
|
| 569 |
+
If prompted for username/password: username = HF username (e.g. `mekosotto`), password = the write token (NOT the account password). HF deprecated password auth.
|
| 570 |
+
|
| 571 |
+
- [ ] **Step 3: User waits ~3-5 min for HF rebuild**
|
| 572 |
+
|
| 573 |
+
Monitor at https://huggingface.co/spaces/mekosotto/hackathon — the "Building" badge flips to "Running" when ready. The build re-runs `bbb_pipeline + bbb_model + eeg_pipeline + mri_pipeline` so this rebuild is slightly slower than the last one (~+1 min).
|
| 574 |
+
|
| 575 |
+
- [ ] **Step 4: User runs the smoke probe**
|
| 576 |
+
|
| 577 |
+
```bash
|
| 578 |
+
scripts/smoke_hf_space.sh
|
| 579 |
+
```
|
| 580 |
+
|
| 581 |
+
Expected: 3 OK lines.
|
| 582 |
+
|
| 583 |
+
- [ ] **Step 5: User walks the manual checklist**
|
| 584 |
+
|
| 585 |
+
Open `docs/superpowers/notes/2026-04-30-hf-smoke-checklist.md` and tick boxes against the live UI at https://mekosotto-hackathon.hf.space/.
|
| 586 |
+
|
| 587 |
+
The two regression boxes that MUST flip from FAIL → OK are:
|
| 588 |
+
1. BBB provenance strip shows real `mlflow · <run id>` (not `—`)
|
| 589 |
+
2. Experiments tab shows ≥3 rows (not empty)
|
| 590 |
+
3. Signal tab default input is `tests/fixtures/eeg_sample.fif` and "Run EEG pipeline" succeeds on first click.
|
| 591 |
+
|
| 592 |
+
If any box still fails, the HF build logs at https://huggingface.co/spaces/mekosotto/hackathon/logs are the next stop — paste any error line back into the conversation.
|
| 593 |
+
|
| 594 |
+
---
|
| 595 |
+
|
| 596 |
+
## Self-Review
|
| 597 |
+
|
| 598 |
+
**Spec coverage:**
|
| 599 |
+
- Issue 1 (EEG default path) → Task 1 (frontend edit) + Task 3 (Dockerfile seeds the path too)
|
| 600 |
+
- Issue 2 (Experiments empty + provenance dashes) → Task 3 drops kill-switch + bakes EEG/MRI runs
|
| 601 |
+
- Issue 3 (Hero status dot) → resolves automatically via Issue 2 fix; called out in Task 5 checklist
|
| 602 |
+
- Test growth 184 → 188 → covered by Task 1 (+2), Task 2 (+1 new method), unchanged delta on widened existing test
|
| 603 |
+
- "User runs `git push hf main`" → Task 7
|
| 604 |
+
|
| 605 |
+
**Placeholder scan:** None. Every step has exact code or commands.
|
| 606 |
+
|
| 607 |
+
**Type consistency:** `run_pipeline` signatures match the verified source (`eeg_pipeline.py:411`, `mri_pipeline.py:282`). The `_eeg_default_path` regex matches the literal `st.text_input("Input FIF/EDF path", "...")` form. Dockerfile env block diff is exact line-for-line.
|
| 608 |
+
|
| 609 |
+
**Risks called out:**
|
| 610 |
+
- Local docker build (Task 3 Step 6) is optional — HF CI catches Dockerfile syntax errors anyway.
|
| 611 |
+
- Removing the kill-switch widens the path that hits MLflow's broad-`except` in `_build_provenance`; that's intentional fallback behavior, not a regression.
|