From 9630f88fa0270d417e516dd81e0388cc8040561a Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 15:38:41 +0900 Subject: [PATCH 1/7] feat: start BioDockLab full-stack research platform modules --- ai/experiment_analyzer.py | 42 ++++ backend/main.py | 297 ++---------------------- backend/requirements.txt | 5 + bio/sequence_parser.py | 35 +++ data/sample/bio_experiments.json | 35 +++ docs/architecture/TECH_STACK_ROADMAP.md | 86 +++++++ imaging/medical_image_note.md | 20 ++ quantum/quantum_bio_note.md | 17 ++ reports/generate_report.py | 40 ++++ simulation/digital_twin_sim.py | 23 ++ 10 files changed, 320 insertions(+), 280 deletions(-) create mode 100644 ai/experiment_analyzer.py create mode 100644 backend/requirements.txt create mode 100644 bio/sequence_parser.py create mode 100644 data/sample/bio_experiments.json create mode 100644 docs/architecture/TECH_STACK_ROADMAP.md create mode 100644 imaging/medical_image_note.md create mode 100644 quantum/quantum_bio_note.md create mode 100644 reports/generate_report.py create mode 100644 simulation/digital_twin_sim.py diff --git a/ai/experiment_analyzer.py b/ai/experiment_analyzer.py new file mode 100644 index 0000000..844fec0 --- /dev/null +++ b/ai/experiment_analyzer.py @@ -0,0 +1,42 @@ +import json +from pathlib import Path + + +DATA_PATH = Path("../data/sample/bio_experiments.json") + + +def load_experiments(): + with DATA_PATH.open("r", encoding="utf-8") as f: + return json.load(f) + + +def classify_priority(exp): + success = exp.get("success_rate", 0) + risk = exp.get("risk_level", "Medium") + + if success >= 80 and risk == "Low": + return "High Priority" + if success >= 70: + return "Review" + return "Needs Improvement" + + +def analyze(): + experiments = load_experiments() + + results = [] + for exp in experiments: + results.append({ + "id": exp["id"], + "domain": exp["domain"], + "success_rate": exp["success_rate"], + "risk_level": exp["risk_level"], + "priority": classify_priority(exp) + }) + + return results + + +if __name__ == "__main__": + for item in analyze(): + print(item) \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 7ccf8ed..6af4ee2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,298 +1,35 @@ -from fastapi import FastAPI, Query -from fastapi.middleware.cors import CORSMiddleware +from fastapi import FastAPI from pathlib import Path -from datetime import datetime import json -import random -app = FastAPI(title="BioDockLab API") +app = FastAPI(title="BioDockLab API", version="2.1.0") -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) +DATA_PATH = Path(__file__).resolve().parent.parent / "data" / "sample" / "bio_experiments.json" -BASE_DIR = Path(__file__).resolve().parent.parent -SAMPLE_DATA_DIR = BASE_DIR / "sample_data" -EXPERIMENTS_FILE = BASE_DIR / "experiments" / "experiment_runs.json" -OUTPUT_DIR = BASE_DIR / "docking" / "outputs" -LOG_DIR = BASE_DIR / "docking" / "logs" -SAMPLE_DATA_DIR.mkdir(exist_ok=True) -OUTPUT_DIR.mkdir(parents=True, exist_ok=True) -LOG_DIR.mkdir(parents=True, exist_ok=True) -EXPERIMENTS_FILE.parent.mkdir(parents=True, exist_ok=True) - -ROLE_POLICIES = { - "patient": {"dashboard", "patient", "reports_limited", "consent_status"}, - "doctor": {"dashboard", "patient", "doctor", "reports", "prescription"}, - "pharmacist": {"dashboard", "prescription", "patient_limited", "reports_limited"}, - "admin_staff": {"dashboard", "admin", "appointments", "consent_status", "patient_limited"}, - "researcher": {"dashboard", "research_lab", "docking", "data_hub"}, - "security": {"dashboard", "security", "audit_logs", "risk_events"}, - "super_admin": { - "dashboard", "patient", "doctor", "research_lab", "docking", "prescription", - "admin", "appointments", "consent_status", "data_hub", "security", - "audit_logs", "risk_events", "reports", "reports_limited", "patient_limited" - }, - "bio_data_curator": {"dashboard", "data_hub", "reports_limited", "patient_limited"}, - "ai_model_operator": {"dashboard", "research_lab", "docking", "data_hub", "reports_limited"}, - "patient_explanation_designer": {"dashboard", "patient", "reports_limited", "consent_status"}, - "research_workflow_engineer": {"dashboard", "research_lab", "docking", "data_hub"}, - "clinical_workflow_coordinator": { - "dashboard", "admin", "appointments", "consent_status", - "patient_limited", "prescription" - }, - "bio_security_architect": {"dashboard", "security", "audit_logs", "risk_events"}, - "virtual_lab_developer": {"dashboard", "research_lab", "docking", "data_hub"}, -} - -DEFAULT_USERS = [ - {"id": "USER-PATIENT-001", "name": "환자 사용자", "role": "patient", "role_label": "환자", "department": "Patient Portal"}, - {"id": "USER-DOCTOR-001", "name": "이준호 의사", "role": "doctor", "role_label": "의사", "department": "Doctor Workspace"}, - {"id": "USER-PHARM-001", "name": "박민지 약사", "role": "pharmacist", "role_label": "약사", "department": "Pharmacy"}, - {"id": "USER-ADMIN-001", "name": "최하늘 원무", "role": "admin_staff", "role_label": "원무", "department": "Administration"}, - {"id": "USER-RESEARCHER-001", "name": "김서연 박사", "role": "researcher", "role_label": "연구자", "department": "Research Lab"}, - {"id": "USER-SECURITY-001", "name": "보안관리자", "role": "security", "role_label": "보안", "department": "Security Center"}, - {"id": "USER-OWNER-001", "name": "이영준 관리자", "role": "super_admin", "role_label": "플랫폼 관리자", "department": "Platform Admin"}, - {"id": "USER-BIOSEC-001", "name": "바이오 보안 아키텍트", "role": "bio_security_architect", "role_label": "Bio Security Architect", "department": "Bio Security"}, - {"id": "USER-VLAB-001", "name": "가상실험실 개발자", "role": "virtual_lab_developer", "role_label": "Virtual Lab Developer", "department": "Virtual Lab"}, -] - -DEFAULT_RISK_EVENTS = [ - { - "id": "RISK-001", - "severity": "high", - "title": "권한 밖 민감 데이터 접근 시도", - "description": "원무 역할 사용자가 처방 상세 데이터 화면에 접근하려 했습니다.", - "status": "needs_review", - "recommended_action": "접근 사유 확인 및 권한 정책 점검" - }, - { - "id": "RISK-002", - "severity": "medium", - "title": "동일 환자 기록 반복 열람", - "description": "짧은 시간 내 동일 환자 기록이 반복 조회되었습니다.", - "status": "monitoring", - "recommended_action": "내부자 과다열람 여부 확인" - } -] - - -def load_json(path: Path, default): - try: - if not path.exists(): - save_json(path, default) - return default - raw = path.read_text(encoding="utf-8").strip() - if not raw: - save_json(path, default) - return default - data = json.loads(raw) - return data - except Exception: - save_json(path, default) - return default - - -def save_json(path: Path, data): - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - -def can_access(role: str, view: str) -> bool: - return view in ROLE_POLICIES.get(role, set()) - - -def append_audit(actor: str, role: str, action: str, resource: str, patient_id=None, allowed=True, reason=""): - path = SAMPLE_DATA_DIR / "audit_logs.json" - logs = load_json(path, []) - item = { - "timestamp": datetime.now().isoformat(timespec="seconds"), - "actor": actor, - "role": role, - "action": action, - "resource": resource, - "patient_id": patient_id, - "allowed": allowed, - "reason": reason - } - logs.append(item) - save_json(path, logs) - return item - - -def add_risk_event(title: str, description: str, severity="medium"): - path = SAMPLE_DATA_DIR / "risk_events.json" - events = load_json(path, DEFAULT_RISK_EVENTS) - item = { - "id": f"RISK-{len(events) + 1:03d}", - "severity": severity, - "title": title, - "description": description, - "status": "needs_review", - "recommended_action": "접근권한, 접근 사유, 반복 열람 여부 확인" - } - events.append(item) - save_json(path, events) - return item +def load_data(): + with open(DATA_PATH, "r", encoding="utf-8") as f: + return json.load(f) @app.get("/") def root(): return { - "message": "BioDockLab Backend Running", - "mode": "v0.7.2 clean backend", - "claim_boundary": "research, education, explanation support, and security audit only" - } - - -@app.get("/health") -def health(): - return {"status": "ok"} - - -@app.get("/clinical/users") -def clinical_users(): - return load_json(SAMPLE_DATA_DIR / "clinical_users.json", DEFAULT_USERS) - - -@app.post("/security/access-check") -def access_check(payload: dict): - role = payload.get("role", "") - actor = payload.get("actor", "unknown") - view = payload.get("view", "") - patient_id = payload.get("patient_id") - reason = payload.get("reason", "screen access") - - allowed = can_access(role, view) - - append_audit( - actor=actor, - role=role, - action="ACCESS_CHECK", - resource=view, - patient_id=patient_id, - allowed=allowed, - reason=reason - ) - - if not allowed: - add_risk_event( - title="권한 없는 화면 접근 시도", - description=f"{actor}({role}) tried to access {view} for patient {patient_id}", - severity="high" - ) - return { - "allowed": False, - "message": "권한이 없는 화면입니다. 보안 로그에 기록되었습니다." - } - - return { - "allowed": True, - "message": "접근 허용" - } - - -@app.get("/security/audit-logs") -def audit_logs(role: str = Query("security"), actor: str = Query("Security")): - allowed = can_access(role, "audit_logs") - - append_audit( - actor=actor, - role=role, - action="VIEW_AUDIT_LOGS", - resource="audit_logs", - patient_id=None, - allowed=allowed, - reason="security center" - ) - - if not allowed: - add_risk_event( - title="감사 로그 권한 없는 접근", - description=f"{actor}({role}) attempted audit logs", - severity="high" - ) - return { - "error": "ACCESS_DENIED", - "message": "감사 로그 접근 권한이 없습니다." - } - - return load_json(SAMPLE_DATA_DIR / "audit_logs.json", []) - - -@app.get("/security/risk-events") -def risk_events(role: str = Query("security"), actor: str = Query("Security")): - allowed = can_access(role, "risk_events") - - append_audit( - actor=actor, - role=role, - action="VIEW_RISK_EVENTS", - resource="risk_events", - patient_id=None, - allowed=allowed, - reason="security center" - ) - - if not allowed: - return { - "error": "ACCESS_DENIED", - "message": "위험 이벤트 접근 권한이 없습니다." - } - - return load_json(SAMPLE_DATA_DIR / "risk_events.json", DEFAULT_RISK_EVENTS) - - -@app.get("/proteins") -def get_proteins(): - return load_json(SAMPLE_DATA_DIR / "proteins.json", []) - - -@app.get("/docking/{protein_id}") -def get_docking_result(protein_id: str): - protein_id = protein_id.upper() - data = load_json(SAMPLE_DATA_DIR / "docking_results.json", {}) - if isinstance(data, dict) and protein_id in data: - return data[protein_id] - return { - "protein": protein_id, - "note": "Sample docking result fallback", - "ligands": [ - {"rank": 1, "name": "BDL-10234", "binding_score": -10.28}, - {"rank": 2, "name": "BDL-08765", "binding_score": -9.46}, - {"rank": 3, "name": "BDL-09123", "binding_score": -8.74} - ] + "project": "BioDockLab", + "version": "2.1", + "message": "Bio AI research software platform API" } @app.get("/experiments") def get_experiments(): - return load_json(EXPERIMENTS_FILE, []) - - -@app.post("/experiments/sample/{pdb_id}") -def create_sample_experiment(pdb_id: str): - pdb_id = pdb_id.upper() - experiment_id = f"EXP-{datetime.now().strftime('%Y-%m-%d-%H%M%S')}-{pdb_id}" - result = { - "experiment_id": experiment_id, - "pdb_id": pdb_id, - "status": "completed_sample_run", - "engine": "AutoDock Vina sample mode", - "best_score": round(random.uniform(-10.5, -8.0), 2), - "created_at": datetime.now().isoformat() - } - - output_file = OUTPUT_DIR / f"{experiment_id}_result.json" - save_json(output_file, result) + return load_data() - experiments = load_json(EXPERIMENTS_FILE, []) - experiments.append(result) - save_json(EXPERIMENTS_FILE, experiments) - return result +@app.get("/experiments/{experiment_id}") +def get_experiment(experiment_id: str): + experiments = load_data() + for exp in experiments: + if exp["id"] == experiment_id: + return exp + return {"error": "Experiment not found"} \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..75c3cbe --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi +uvicorn +pandas +numpy +scikit-learn \ No newline at end of file diff --git a/bio/sequence_parser.py b/bio/sequence_parser.py new file mode 100644 index 0000000..baf0994 --- /dev/null +++ b/bio/sequence_parser.py @@ -0,0 +1,35 @@ +def parse_fasta(text: str): + records = [] + current_id = None + sequence = [] + + for line in text.splitlines(): + line = line.strip() + if not line: + continue + + if line.startswith(">"): + if current_id: + records.append({ + "id": current_id, + "sequence": "".join(sequence) + }) + current_id = line[1:] + sequence = [] + else: + sequence.append(line) + + if current_id: + records.append({ + "id": current_id, + "sequence": "".join(sequence) + }) + + return records + + +if __name__ == "__main__": + sample = """>protein_sample_001 +MKTAYIAKQRQISFVKSHFSRQDILDLWQ +""" + print(parse_fasta(sample)) \ No newline at end of file diff --git a/data/sample/bio_experiments.json b/data/sample/bio_experiments.json new file mode 100644 index 0000000..879bd4c --- /dev/null +++ b/data/sample/bio_experiments.json @@ -0,0 +1,35 @@ +[ + { + "id": "ORG-001", + "domain": "Organoid", + "sample": "intestinal organoid", + "condition": "growth factor A + drug candidate X", + "temperature": 37, + "duration_hours": 72, + "success_rate": 78, + "risk_level": "Medium", + "note": "Potential drug response model" + }, + { + "id": "CFPS-001", + "domain": "CFPS", + "sample": "cell-free protein synthesis", + "condition": "enzyme mix B + amino acid pool", + "temperature": 30, + "duration_hours": 6, + "success_rate": 84, + "risk_level": "Low", + "note": "Protein production simulation sample" + }, + { + "id": "DT-001", + "domain": "Digital Twin", + "sample": "virtual patient model", + "condition": "treatment response prediction", + "temperature": null, + "duration_hours": 24, + "success_rate": 69, + "risk_level": "High", + "note": "Digital twin prediction sample" + } +] \ No newline at end of file diff --git a/docs/architecture/TECH_STACK_ROADMAP.md b/docs/architecture/TECH_STACK_ROADMAP.md new file mode 100644 index 0000000..d438a37 --- /dev/null +++ b/docs/architecture/TECH_STACK_ROADMAP.md @@ -0,0 +1,86 @@ +# BioDockLab Technical Stack Roadmap + +## 1. Frontend + +- HTML +- CSS +- JavaScript +- React +- TypeScript +- Vite +- Recharts +- D3.js +- Three.js + +## 2. Backend + +- Python +- FastAPI +- REST API +- WebSocket + +## 3. Database + +- SQLite +- PostgreSQL +- Supabase + +## 4. AI / Machine Learning + +- Pandas +- NumPy +- Scikit-learn +- PyTorch +- LLM API + +## 5. Bioinformatics + +- Biopython +- RDKit +- FASTA parser +- Biomedical data parser + +## 6. Digital Twin / Simulation + +- NumPy +- SciPy +- SimPy +- Recharts +- D3.js +- Three.js + +## 7. Medical Imaging / Surgery AI + +- OpenCV +- DICOM +- MONAI +- PyTorch +- SimpleITK + +## 8. Quantum Biocomputing + +- Qiskit +- PennyLane +- Cirq + +## 9. Report Automation + +- Markdown +- Jinja2 +- ReportLab +- WeasyPrint + +## 10. Deployment / DevOps + +- GitHub +- GitHub Actions +- Docker +- Docker Compose +- Vercel +- Render + +## Development Position + +BioDockLab is not simply a bio research project. + +It is a research software platform that structures biomedical experiment data, analyzes it, simulates possible outcomes, and generates decision-support reports. \ No newline at end of file diff --git a/imaging/medical_image_note.md b/imaging/medical_image_note.md new file mode 100644 index 0000000..ca520a9 --- /dev/null +++ b/imaging/medical_image_note.md @@ -0,0 +1,20 @@ +# Medical Imaging / Surgery AI Direction + +This module is reserved for future Surgery AI research. + +## Planned Stack + +- OpenCV +- DICOM +- PyTorch +- MONAI +- SimpleITK +- NiBabel + +## Initial Goal + +BioDockLab will not directly implement surgical AI at the first stage. + +The first goal is to describe the data flow: + +Medical Image → Feature Extraction → Risk Estimation → Decision Support → Report \ No newline at end of file diff --git a/quantum/quantum_bio_note.md b/quantum/quantum_bio_note.md new file mode 100644 index 0000000..b59d138 --- /dev/null +++ b/quantum/quantum_bio_note.md @@ -0,0 +1,17 @@ +# Quantum Biocomputing Direction + +Quantum biocomputing is managed as a long-term research direction in BioDockLab. + +## Possible Stack + +- Qiskit +- PennyLane +- Cirq +- Quantum chemistry simulation +- Protein structure simulation + +## Current Position + +This is not an immediate implementation target. + +BioDockLab will first treat quantum biocomputing as a research keyword for future molecular simulation and protein analysis. \ No newline at end of file diff --git a/reports/generate_report.py b/reports/generate_report.py new file mode 100644 index 0000000..d0f7ef0 --- /dev/null +++ b/reports/generate_report.py @@ -0,0 +1,40 @@ +from datetime import datetime +from pathlib import Path +import json + +DATA_PATH = Path("../data/sample/bio_experiments.json") +OUTPUT_PATH = Path("BioDockLab_Report.md") + + +def load_data(): + with DATA_PATH.open("r", encoding="utf-8") as f: + return json.load(f) + + +def generate_report(): + experiments = load_data() + + lines = [] + lines.append("# BioDockLab Research Report") + lines.append("") + lines.append(f"Generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + lines.append("") + lines.append("## Experiment Summary") + lines.append("") + + for exp in experiments: + lines.append(f"### {exp['id']} - {exp['domain']}") + lines.append("") + lines.append(f"- Sample: {exp['sample']}") + lines.append(f"- Condition: {exp['condition']}") + lines.append(f"- Success Rate: {exp['success_rate']}%") + lines.append(f"- Risk Level: {exp['risk_level']}") + lines.append(f"- Note: {exp['note']}") + lines.append("") + + OUTPUT_PATH.write_text("\n".join(lines), encoding="utf-8") + + +if __name__ == "__main__": + generate_report() + print(f"Report generated: {OUTPUT_PATH}") \ No newline at end of file diff --git a/simulation/digital_twin_sim.py b/simulation/digital_twin_sim.py new file mode 100644 index 0000000..8c07d07 --- /dev/null +++ b/simulation/digital_twin_sim.py @@ -0,0 +1,23 @@ +def simulate_response(success_rate: float, risk_score: float, treatment_strength: float): + adjusted = success_rate + (treatment_strength * 8) - (risk_score * 5) + + if adjusted >= 80: + level = "Positive" + elif adjusted >= 60: + level = "Uncertain" + else: + level = "Negative" + + return { + "predicted_response": round(adjusted, 2), + "response_level": level + } + + +if __name__ == "__main__": + result = simulate_response( + success_rate=72, + risk_score=2.4, + treatment_strength=1.1 + ) + print(result) \ No newline at end of file From caccbd7ce1ee68193c49e407dd90fb66e6f716df Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 15:53:46 +0900 Subject: [PATCH 2/7] test: add Node-based BioDockLab data validation and report generation --- reports/BioDockLab_Report_Node.md | 29 +++++++++++++++++++++++++ scripts/generate_report_node.js | 31 ++++++++++++++++++++++++++ scripts/validate_bio_data.js | 36 +++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 reports/BioDockLab_Report_Node.md create mode 100644 scripts/generate_report_node.js create mode 100644 scripts/validate_bio_data.js diff --git a/reports/BioDockLab_Report_Node.md b/reports/BioDockLab_Report_Node.md new file mode 100644 index 0000000..e822510 --- /dev/null +++ b/reports/BioDockLab_Report_Node.md @@ -0,0 +1,29 @@ +# BioDockLab Research Report + +Generated by Node.js at: 2026-06-19T06:53:39.571Z + +## Experiment Summary + +### ORG-001 - Organoid + +- Sample: intestinal organoid +- Condition: growth factor A + drug candidate X +- Success Rate: 78% +- Risk Level: Medium +- Note: Potential drug response model + +### CFPS-001 - CFPS + +- Sample: cell-free protein synthesis +- Condition: enzyme mix B + amino acid pool +- Success Rate: 84% +- Risk Level: Low +- Note: Protein production simulation sample + +### DT-001 - Digital Twin + +- Sample: virtual patient model +- Condition: treatment response prediction +- Success Rate: 69% +- Risk Level: High +- Note: Digital twin prediction sample diff --git a/scripts/generate_report_node.js b/scripts/generate_report_node.js new file mode 100644 index 0000000..7f291be --- /dev/null +++ b/scripts/generate_report_node.js @@ -0,0 +1,31 @@ +const fs = require("fs"); +const path = require("path"); + +const dataPath = path.join(__dirname, "..", "data", "sample", "bio_experiments.json"); +const outputPath = path.join(__dirname, "..", "reports", "BioDockLab_Report_Node.md"); + +const experiments = JSON.parse(fs.readFileSync(dataPath, "utf-8")); + +const lines = []; + +lines.push("# BioDockLab Research Report"); +lines.push(""); +lines.push(`Generated by Node.js at: ${new Date().toISOString()}`); +lines.push(""); +lines.push("## Experiment Summary"); +lines.push(""); + +experiments.forEach((exp) => { + lines.push(`### ${exp.id} - ${exp.domain}`); + lines.push(""); + lines.push(`- Sample: ${exp.sample}`); + lines.push(`- Condition: ${exp.condition}`); + lines.push(`- Success Rate: ${exp.success_rate}%`); + lines.push(`- Risk Level: ${exp.risk_level}`); + lines.push(`- Note: ${exp.note}`); + lines.push(""); +}); + +fs.writeFileSync(outputPath, lines.join("\n"), "utf-8"); + +console.log(`Report generated: ${outputPath}`); \ No newline at end of file diff --git a/scripts/validate_bio_data.js b/scripts/validate_bio_data.js new file mode 100644 index 0000000..afe6f77 --- /dev/null +++ b/scripts/validate_bio_data.js @@ -0,0 +1,36 @@ +const fs = require("fs"); +const path = require("path"); + +const dataPath = path.join(__dirname, "..", "data", "sample", "bio_experiments.json"); + +function loadExperiments() { + const raw = fs.readFileSync(dataPath, "utf-8"); + return JSON.parse(raw); +} + +function classifyPriority(exp) { + const success = exp.success_rate || 0; + const risk = exp.risk_level || "Medium"; + + if (success >= 80 && risk === "Low") return "High Priority"; + if (success >= 70) return "Review"; + return "Needs Improvement"; +} + +const experiments = loadExperiments(); + +console.log("BioDockLab Data Validation"); +console.log("=========================="); + +experiments.forEach((exp) => { + console.log({ + id: exp.id, + domain: exp.domain, + success_rate: exp.success_rate, + risk_level: exp.risk_level, + priority: classifyPriority(exp), + }); +}); + +console.log("=========================="); +console.log(`Total experiments: ${experiments.length}`); \ No newline at end of file From 8aae0634ecd18e6cb89bbf61cda09347b428fc94 Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 15:55:52 +0900 Subject: [PATCH 3/7] feat: add Node API server and live bio data dashboard --- frontend/bio_ai_live_dashboard.html | 168 ++++++++++++++++++++++++++++ scripts/biodocklab_node_server.js | 56 ++++++++++ 2 files changed, 224 insertions(+) create mode 100644 frontend/bio_ai_live_dashboard.html create mode 100644 scripts/biodocklab_node_server.js diff --git a/frontend/bio_ai_live_dashboard.html b/frontend/bio_ai_live_dashboard.html new file mode 100644 index 0000000..9a0d47a --- /dev/null +++ b/frontend/bio_ai_live_dashboard.html @@ -0,0 +1,168 @@ + + + + + BioDockLab Live Dashboard + + + +
+
+
BioDockLab v2.1 · Live Data Dashboard
+

실험 데이터 기반
바이오 AI 대시보드

+

+ 이 화면은 정적 HTML이 아니라 Node 서버의 API에서 + bio_experiments.json 데이터를 읽어와 카드 UI로 렌더링합니다. +

+
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/scripts/biodocklab_node_server.js b/scripts/biodocklab_node_server.js new file mode 100644 index 0000000..f3a89b5 --- /dev/null +++ b/scripts/biodocklab_node_server.js @@ -0,0 +1,56 @@ +const http = require("http"); +const fs = require("fs"); +const path = require("path"); + +const PORT = 5173; +const ROOT = path.join(__dirname, ".."); +const FRONTEND_DIR = path.join(ROOT, "frontend"); +const DATA_PATH = path.join(ROOT, "data", "sample", "bio_experiments.json"); + +function send(res, status, contentType, body) { + res.writeHead(status, { "Content-Type": contentType + "; charset=utf-8" }); + res.end(body); +} + +function serveFile(res, filePath, contentType) { + fs.readFile(filePath, (err, data) => { + if (err) { + send(res, 404, "text/plain", "Not Found"); + return; + } + send(res, 200, contentType, data); + }); +} + +const server = http.createServer((req, res) => { + const url = req.url.split("?")[0]; + + if (url === "/") { + return serveFile(res, path.join(FRONTEND_DIR, "bio_ai_live_dashboard.html"), "text/html"); + } + + if (url === "/api/experiments") { + return serveFile(res, DATA_PATH, "application/json"); + } + + if (url.startsWith("/frontend/")) { + const filePath = path.join(ROOT, url); + const ext = path.extname(filePath).toLowerCase(); + + const typeMap = { + ".html": "text/html", + ".css": "text/css", + ".js": "application/javascript", + ".json": "application/json" + }; + + return serveFile(res, filePath, typeMap[ext] || "text/plain"); + } + + send(res, 404, "text/plain", "BioDockLab route not found"); +}); + +server.listen(PORT, () => { + console.log(`BioDockLab Node server running at http://127.0.0.1:${PORT}`); + console.log(`API: http://127.0.0.1:${PORT}/api/experiments`); +}); \ No newline at end of file From 5b4aef050f6a140554c92cc2e4a682ba407eff0a Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 15:58:40 +0900 Subject: [PATCH 4/7] feat: add experiment analysis API and priority results --- frontend/bio_ai_live_dashboard.html | 51 ++++++++++++++++++++++------- scripts/biodocklab_node_server.js | 33 +++++++++++++++++++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/frontend/bio_ai_live_dashboard.html b/frontend/bio_ai_live_dashboard.html index 9a0d47a..a6f0229 100644 --- a/frontend/bio_ai_live_dashboard.html +++ b/frontend/bio_ai_live_dashboard.html @@ -139,10 +139,35 @@

실험 데이터 기반
바이오 AI 대시보드

return "Risk: High"; } - fetch("/api/experiments") - .then((res) => res.json()) - .then((experiments) => { - cards.innerHTML = experiments.map((exp) => ` + + `; + }).join(""); + }) + .catch((err) => { + cards.innerHTML = "

Failed to load experiment data.

"; + console.error(err); + }); + \ No newline at end of file diff --git a/scripts/biodocklab_node_server.js b/scripts/biodocklab_node_server.js index f3a89b5..85f9682 100644 --- a/scripts/biodocklab_node_server.js +++ b/scripts/biodocklab_node_server.js @@ -33,6 +33,39 @@ const server = http.createServer((req, res) => { return serveFile(res, DATA_PATH, "application/json"); } + if (url === "/api/analysis") { + const raw = fs.readFileSync(DATA_PATH, "utf-8"); + const experiments = JSON.parse(raw); + + const analysis = experiments.map((exp) => { + const success = exp.success_rate || 0; + const risk = exp.risk_level || "Medium"; + + let priority = "Needs Improvement"; + if (success >= 80 && risk === "Low") { + priority = "High Priority"; + } else if (success >= 70) { + priority = "Review"; + } + + return { + id: exp.id, + domain: exp.domain, + success_rate: success, + risk_level: risk, + priority, + recommendation: + priority === "High Priority" + ? "Proceed to next validation stage" + : priority === "Review" + ? "Compare similar experiment conditions" + : "Adjust condition and repeat experiment" + }; + }); + + return send(res, 200, "application/json", JSON.stringify(analysis, null, 2)); + } + if (url.startsWith("/frontend/")) { const filePath = path.join(ROOT, url); const ext = path.extname(filePath).toLowerCase(); From 839b956a77ead506a5674a759cb2c259e4b21b7c Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 16:10:15 +0900 Subject: [PATCH 5/7] feat: add experiment analysis results to live dashboard --- frontend/bio_ai_live_dashboard.html | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/frontend/bio_ai_live_dashboard.html b/frontend/bio_ai_live_dashboard.html index a6f0229..9d6b8b9 100644 --- a/frontend/bio_ai_live_dashboard.html +++ b/frontend/bio_ai_live_dashboard.html @@ -123,23 +123,15 @@

실험 데이터 기반
바이오 AI 대시보드

-
+
+

Loading experiment data...

+
- - From db29b8083e50b7ba534097b95e432e7d7a2a3550 Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 16:37:31 +0900 Subject: [PATCH 6/7] chore: add multi-stack foundation files for BioDockLab --- .github/workflows/biodocklab-ci.yml | 27 +++++++++++++++++++++++++++ Dockerfile | 9 +++++++++ database/schema.sql | 20 ++++++++++++++++++++ frontend/BioDashboard.tsx | 29 +++++++++++++++++++++++++++++ frontend/bio_ai_types.ts | 22 ++++++++++++++++++++++ pyproject.toml | 19 +++++++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 .github/workflows/biodocklab-ci.yml create mode 100644 Dockerfile create mode 100644 database/schema.sql create mode 100644 frontend/BioDashboard.tsx create mode 100644 frontend/bio_ai_types.ts create mode 100644 pyproject.toml diff --git a/.github/workflows/biodocklab-ci.yml b/.github/workflows/biodocklab-ci.yml new file mode 100644 index 0000000..6a40a5b --- /dev/null +++ b/.github/workflows/biodocklab-ci.yml @@ -0,0 +1,27 @@ +name: BioDockLab CI + +on: + push: + branches: + - main + - feature/biodocklab-v2-fullstack-start + pull_request: + +jobs: + validate-node: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Validate bio experiment data + run: node scripts/validate_bio_data.js + + - name: Generate sample report + run: node scripts/generate_report_node.js \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a99cdf2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:22-alpine + +WORKDIR /app + +COPY . . + +EXPOSE 5173 + +CMD ["node", "scripts/biodocklab_node_server.js"] \ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..479eb21 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,20 @@ +CREATE TABLE experiments ( + id TEXT PRIMARY KEY, + domain TEXT NOT NULL, + sample TEXT NOT NULL, + condition TEXT NOT NULL, + temperature REAL, + duration_hours REAL, + success_rate INTEGER, + risk_level TEXT, + note TEXT +); + +CREATE TABLE analysis_results ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + experiment_id TEXT NOT NULL, + priority TEXT NOT NULL, + recommendation TEXT NOT NULL, + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (experiment_id) REFERENCES experiments(id) +); \ No newline at end of file diff --git a/frontend/BioDashboard.tsx b/frontend/BioDashboard.tsx new file mode 100644 index 0000000..548fbf6 --- /dev/null +++ b/frontend/BioDashboard.tsx @@ -0,0 +1,29 @@ +import type { BioExperiment, AnalysisResult } from "./bio_ai_types"; + +type Props = { + experiments: BioExperiment[]; + analysis: AnalysisResult[]; +}; + +export function BioDashboard({ experiments, analysis }: Props) { + const analysisMap = new Map(analysis.map((item) => [item.id, item])); + + return ( +
+

BioDockLab Live Dashboard

+ {experiments.map((exp) => { + const result = analysisMap.get(exp.id); + + return ( +
+

{exp.domain}

+

{exp.sample}

+

{exp.condition}

+ {exp.success_rate}% +

{result?.priority ?? "Needs Improvement"}

+
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/frontend/bio_ai_types.ts b/frontend/bio_ai_types.ts new file mode 100644 index 0000000..5c14bfe --- /dev/null +++ b/frontend/bio_ai_types.ts @@ -0,0 +1,22 @@ +export type BioDomain = + | "Organoid" + | "Surgery AI" + | "Quantum Biocomputing" + | "Digital Twin" + | "CFPS"; + +export type BioExperiment = { + id: string; + domain: BioDomain; + sample: string; + condition: string; + success_rate: number; + risk_level: "Low" | "Medium" | "High"; + note: string; +}; + +export type AnalysisResult = { + id: string; + priority: "High Priority" | "Review" | "Needs Improvement"; + recommendation: string; +}; \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..28b28cb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "biodocklab" +version = "2.2.0" +description = "Bio AI research software platform" +requires-python = ">=3.11" +dependencies = [ + "fastapi", + "uvicorn", + "pandas", + "numpy", + "scikit-learn", + "biopython", + "rdkit", + "opencv-python", + "qiskit", + "pennylane", + "jinja2", + "reportlab" +] \ No newline at end of file From d8c3d891de39adb05f562393dd107b627e63ab24 Mon Sep 17 00:00:00 2001 From: JeongLee Date: Fri, 19 Jun 2026 16:49:14 +0900 Subject: [PATCH 7/7] chore: add scaffold modules for remaining BioDockLab tech stacks --- ai/models/torch_success_predictor.py | 38 +++++++++++++++ bio/bioinformatics_toolkit.py | 47 +++++++++++++++++++ database/seed.sql | 33 +++++++++++++ database/supabase/supabase_client.ts | 25 ++++++++++ frontend/visualization/ResearchCharts.tsx | 30 ++++++++++++ frontend/visualization/d3_research_network.js | 18 +++++++ .../visualization/three_digital_twin_scene.js | 17 +++++++ imaging/medical_image_pipeline.py | 34 ++++++++++++++ quantum/quantum_bio_pipeline.py | 30 ++++++++++++ reports/pdf_exporter.py | 36 ++++++++++++++ 10 files changed, 308 insertions(+) create mode 100644 ai/models/torch_success_predictor.py create mode 100644 bio/bioinformatics_toolkit.py create mode 100644 database/seed.sql create mode 100644 database/supabase/supabase_client.ts create mode 100644 frontend/visualization/ResearchCharts.tsx create mode 100644 frontend/visualization/d3_research_network.js create mode 100644 frontend/visualization/three_digital_twin_scene.js create mode 100644 imaging/medical_image_pipeline.py create mode 100644 quantum/quantum_bio_pipeline.py create mode 100644 reports/pdf_exporter.py diff --git a/ai/models/torch_success_predictor.py b/ai/models/torch_success_predictor.py new file mode 100644 index 0000000..8eae712 --- /dev/null +++ b/ai/models/torch_success_predictor.py @@ -0,0 +1,38 @@ +""" +BioDockLab PyTorch scaffold. + +Goal: +Predict experiment success probability from structured bio experiment features. +This file is a model skeleton and will be implemented after Python environment setup. +""" + +try: + import torch + import torch.nn as nn +except ImportError: + torch = None + nn = None + + +if torch and nn: + class ExperimentSuccessModel(nn.Module): + def __init__(self, input_dim: int = 4): + super().__init__() + self.network = nn.Sequential( + nn.Linear(input_dim, 16), + nn.ReLU(), + nn.Linear(16, 8), + nn.ReLU(), + nn.Linear(8, 1), + nn.Sigmoid() + ) + + def forward(self, x): + return self.network(x) +else: + class ExperimentSuccessModel: + def __init__(self, input_dim: int = 4): + self.input_dim = input_dim + + def explain(self): + return "PyTorch is not installed. This is a scaffold model." \ No newline at end of file diff --git a/bio/bioinformatics_toolkit.py b/bio/bioinformatics_toolkit.py new file mode 100644 index 0000000..b22ca0f --- /dev/null +++ b/bio/bioinformatics_toolkit.py @@ -0,0 +1,47 @@ +""" +BioDockLab bioinformatics toolkit scaffold. + +Planned stack: +- Biopython: sequence parsing +- RDKit: molecular structure analysis +""" + +try: + from Bio.Seq import Seq +except ImportError: + Seq = None + +try: + from rdkit import Chem +except ImportError: + Chem = None + + +def summarize_sequence(sequence: str): + if Seq is None: + return { + "sequence": sequence, + "length": len(sequence), + "status": "Biopython not installed" + } + + seq = Seq(sequence) + return { + "sequence": str(seq), + "length": len(seq), + "reverse_complement": str(seq.reverse_complement()) + } + + +def parse_smiles(smiles: str): + if Chem is None: + return { + "smiles": smiles, + "status": "RDKit not installed" + } + + molecule = Chem.MolFromSmiles(smiles) + return { + "smiles": smiles, + "valid": molecule is not None + } \ No newline at end of file diff --git a/database/seed.sql b/database/seed.sql new file mode 100644 index 0000000..99256c4 --- /dev/null +++ b/database/seed.sql @@ -0,0 +1,33 @@ +INSERT INTO experiments ( + id, + domain, + sample, + condition, + temperature, + duration_hours, + success_rate, + risk_level, + note +) VALUES +( + 'ORG-001', + 'Organoid', + 'intestinal organoid', + 'growth factor A + drug candidate X', + 37, + 72, + 78, + 'Medium', + 'Potential drug response model' +), +( + 'CFPS-001', + 'CFPS', + 'cell-free protein synthesis', + 'enzyme mix B + amino acid pool', + 30, + 6, + 84, + 'Low', + 'Protein production simulation sample' +); \ No newline at end of file diff --git a/database/supabase/supabase_client.ts b/database/supabase/supabase_client.ts new file mode 100644 index 0000000..f7a4d83 --- /dev/null +++ b/database/supabase/supabase_client.ts @@ -0,0 +1,25 @@ +export type SupabaseExperimentRow = { + id: string; + domain: string; + sample: string; + condition: string; + temperature: number | null; + duration_hours: number | null; + success_rate: number; + risk_level: "Low" | "Medium" | "High"; + note: string; +}; + +// Future implementation: +// import { createClient } from "@supabase/supabase-js"; +// +// export const supabase = createClient( +// process.env.SUPABASE_URL!, +// process.env.SUPABASE_ANON_KEY! +// ); + +export const supabaseTablePlan = { + experiments: "stores bio experiment records", + analysis_results: "stores AI priority and recommendation results", + reports: "stores generated research report metadata" +}; \ No newline at end of file diff --git a/frontend/visualization/ResearchCharts.tsx b/frontend/visualization/ResearchCharts.tsx new file mode 100644 index 0000000..841d7df --- /dev/null +++ b/frontend/visualization/ResearchCharts.tsx @@ -0,0 +1,30 @@ +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer +} from "recharts"; + +type ExperimentChartItem = { + id: string; + domain: string; + success_rate: number; +}; + +export function ResearchCharts({ data }: { data: ExperimentChartItem[] }) { + return ( +
+

Experiment Success Rate

+ + + + + + + + +
+ ); +} \ No newline at end of file diff --git a/frontend/visualization/d3_research_network.js b/frontend/visualization/d3_research_network.js new file mode 100644 index 0000000..afe2ae8 --- /dev/null +++ b/frontend/visualization/d3_research_network.js @@ -0,0 +1,18 @@ +// D3.js scaffold for future BioDockLab research relationship network. +// Planned graph: +// Bio Domain → Experiment → Analysis Result → Recommendation + +export const researchNodes = [ + { id: "Organoid", group: "bio" }, + { id: "CFPS", group: "bio" }, + { id: "Digital Twin", group: "simulation" }, + { id: "AI Analysis", group: "ai" }, + { id: "Research Report", group: "report" } +]; + +export const researchLinks = [ + { source: "Organoid", target: "AI Analysis" }, + { source: "CFPS", target: "AI Analysis" }, + { source: "Digital Twin", target: "AI Analysis" }, + { source: "AI Analysis", target: "Research Report" } +]; \ No newline at end of file diff --git a/frontend/visualization/three_digital_twin_scene.js b/frontend/visualization/three_digital_twin_scene.js new file mode 100644 index 0000000..3cde121 --- /dev/null +++ b/frontend/visualization/three_digital_twin_scene.js @@ -0,0 +1,17 @@ +// Three.js scaffold for future BioDockLab digital twin scene. +// This file defines the intended 3D model structure before full rendering. + +export const digitalTwinScenePlan = { + scene: "BioDockLab Digital Twin", + objects: [ + "virtual organoid model", + "experiment condition panel", + "risk signal layer", + "treatment response indicator" + ], + camera: { + mode: "orbit", + target: "virtual organoid model" + }, + nextStep: "Implement Three.js canvas rendering" +}; \ No newline at end of file diff --git a/imaging/medical_image_pipeline.py b/imaging/medical_image_pipeline.py new file mode 100644 index 0000000..d432e00 --- /dev/null +++ b/imaging/medical_image_pipeline.py @@ -0,0 +1,34 @@ +""" +BioDockLab medical imaging scaffold. + +Planned stack: +- OpenCV +- DICOM / pydicom +- SimpleITK +- MONAI +""" + +try: + import cv2 +except ImportError: + cv2 = None + +try: + import pydicom +except ImportError: + pydicom = None + + +def describe_image_pipeline(): + return { + "input": "DICOM or medical image", + "steps": [ + "load image", + "normalize intensity", + "extract region of interest", + "estimate risk", + "generate decision-support report" + ], + "opencv_available": cv2 is not None, + "dicom_available": pydicom is not None + } \ No newline at end of file diff --git a/quantum/quantum_bio_pipeline.py b/quantum/quantum_bio_pipeline.py new file mode 100644 index 0000000..13b795f --- /dev/null +++ b/quantum/quantum_bio_pipeline.py @@ -0,0 +1,30 @@ +""" +BioDockLab quantum biocomputing scaffold. + +Planned stack: +- Qiskit +- PennyLane +- Cirq + +Current role: +Long-term research direction for molecular simulation and protein structure problems. +""" + +try: + import qiskit +except ImportError: + qiskit = None + +try: + import pennylane as qml +except ImportError: + qml = None + + +def quantum_bio_research_plan(): + return { + "target": "molecular simulation / protein structure analysis", + "qiskit_available": qiskit is not None, + "pennylane_available": qml is not None, + "current_status": "research scaffold" + } \ No newline at end of file diff --git a/reports/pdf_exporter.py b/reports/pdf_exporter.py new file mode 100644 index 0000000..4094716 --- /dev/null +++ b/reports/pdf_exporter.py @@ -0,0 +1,36 @@ +""" +BioDockLab PDF export scaffold. + +Planned stack: +- Jinja2 +- ReportLab +- WeasyPrint + +Goal: +Convert experiment analysis results into research reports. +""" + +try: + from reportlab.lib.pagesizes import A4 + from reportlab.pdfgen import canvas +except ImportError: + A4 = None + canvas = None + + +def export_pdf_stub(output_path: str = "BioDockLab_Report.pdf"): + if canvas is None: + return { + "status": "ReportLab not installed", + "planned_output": output_path + } + + c = canvas.Canvas(output_path, pagesize=A4) + c.drawString(72, 800, "BioDockLab Research Report") + c.drawString(72, 780, "PDF export scaffold") + c.save() + + return { + "status": "PDF generated", + "output": output_path + } \ No newline at end of file