Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import inspect | |
| import subprocess | |
| import tempfile | |
| from pathlib import Path | |
| from uuid import uuid4 | |
| import json | |
| from . import __version__ | |
| from .cli import _LEVEL_MAP | |
| from .render import render_markdown, write_outputs | |
| from .scanner import audit_repository | |
| try: | |
| import gradio as gr | |
| except ImportError as exc: # pragma: no cover | |
| raise RuntimeError( | |
| "Gradio failed to import. Install demo dependencies with " | |
| "`pip install -r requirements.txt` or `pip install -e .[demo]`. " | |
| f"Original error: {exc}" | |
| ) from exc | |
| _DEFAULT_REPO = "https://github.com/flamehaven01/STEM-BIO-AI" | |
| _PLACEHOLDER_REPO = "https://github.com/<owner>/<bio-medical-ai-repo>" | |
| def _blocks_kwargs() -> dict[str, str]: | |
| params = inspect.signature(gr.Blocks).parameters | |
| return {"css": _CSS} if "css" in params else {} | |
| def _launch_kwargs() -> dict[str, str]: | |
| params = inspect.signature(gr.Blocks.launch).parameters | |
| return {"css": _CSS} if "css" in params else {} | |
| def _clone_github(url: str, destination: Path) -> Path: | |
| if not url.startswith("https://github.com/"): | |
| raise ValueError("Only public https://github.com/ URLs are supported in the demo.") | |
| target = destination / "repo" | |
| result = subprocess.run( | |
| ["git", "clone", "--depth", "1", url, str(target)], | |
| capture_output=True, | |
| text=True, | |
| timeout=120, | |
| ) | |
| if result.returncode != 0: | |
| raise RuntimeError(result.stderr.strip() or "git clone failed") | |
| return target | |
| def _finding_cards(result: dict) -> str: | |
| score = result["score"] | |
| classification = result["classification"] | |
| integrity = result["code_integrity"] | |
| s3 = result.get("stage_3_rubric", {}) | |
| regulatory = result.get("regulatory_traceability", {}) | |
| basis = result.get("regulatory_basis", {}) | |
| detector_summary = result.get("detector_summary", {}).get("by_detector", {}) | |
| # Stage scores | |
| stage_lines = [ | |
| "| Stage | Score |", | |
| "|-------|------:|", | |
| f"| Stage 1 README Evidence | `{score['stage_1_readme_intent']} / 100` |", | |
| f"| Stage 2R Repo Consistency | `{score['stage_2_repo_local_consistency']} / 100` |", | |
| f"| Stage 3 Code/Bio Responsibility | `{score['stage_3_code_bio']} / 100` |", | |
| ] | |
| # Stage 3 3-tier detail | |
| t3_score = s3.get("T3_changelog_release_hygiene", {}).get("score", 0) | |
| b1_score = s3.get("B1_data_provenance_controls", {}).get("score", 0) | |
| b2_score = s3.get("B2_bias_limitations", {}).get("score", 0) | |
| # Replication lane | |
| rep_score = result.get("replication_score", "n/a") | |
| rep_tier = result.get("replication_tier", "n/a") | |
| # Code integrity | |
| warnings = [ | |
| f"**{key.replace('_', ' ')}**: `{item['status']}`" | |
| for key, item in integrity.items() | |
| if item.get("status") != "PASS" | |
| ] | |
| warning_text = "\n".join(f"- {line}" for line in warnings) or "- No C1-C4 warning/fail status detected." | |
| # Notable signals | |
| risks = result.get("notable_risks", []) | |
| positives = result.get("notable_positive_evidence", []) | |
| risk_text = "\n".join(f"- {r}" for r in risks[:4]) or "- None detected." | |
| pos_text = "\n".join(f"- {p}" for p in positives[:4]) or "- None detected." | |
| bio_lines = [] | |
| for detector, label in ( | |
| ("BIO_smiles_surface_integrity", "SMILES surface"), | |
| ("BIO_smiles_rdkit_validation", "SMILES RDKit lane"), | |
| ("BIO_smiles_parser_guard", "SMILES parser guard"), | |
| ("BIO_silent_mock_fallback", "Silent mock fallback"), | |
| ("BIO_run_trace", "Bio run trace"), | |
| ): | |
| counts = detector_summary.get(detector, {}) | |
| if not counts: | |
| continue | |
| if counts.get("detected"): | |
| bio_lines.append(f"- **{label}:** detected `{counts['detected']}`") | |
| elif counts.get("not_applicable"): | |
| bio_lines.append(f"- **{label}:** not applicable") | |
| else: | |
| bio_lines.append(f"- **{label}:** not detected") | |
| bio_text = "\n".join(bio_lines) or "- No bio deterministic diagnostics were emitted." | |
| traceability_summary = regulatory.get( | |
| "summary", | |
| "No regulatory traceability summary emitted.", | |
| ) | |
| basis_title = basis.get("note", {}).get("title", "Regulatory basis note") | |
| return "\n".join( | |
| [ | |
| "### Audit Snapshot", | |
| f"- **Final score:** `{score['final_score']} / 100`", | |
| f"- **Formal tier:** `{score['formal_tier']}`", | |
| f"- **Clinical adjacency:** `{classification['ca_severity']}`", | |
| f"- **Use scope:** {score['use_scope']}", | |
| "", | |
| "### Stage Scores", | |
| *stage_lines, | |
| "", | |
| "### Stage 3 Detail", | |
| f"- T3 Changelog hygiene: `{t3_score}` B1 Data provenance: `{b1_score}` B2 Bias measurement: `{b2_score}`", | |
| "", | |
| "### Replication Lane (Stage 4)", | |
| f"- Score: `{rep_score}` Tier: `{rep_tier}`", | |
| "", | |
| "### Code Integrity", | |
| warning_text, | |
| "", | |
| "### Notable Risks", | |
| risk_text, | |
| "", | |
| "### Positive Evidence", | |
| pos_text, | |
| "", | |
| "### Bio Deterministic Diagnostics", | |
| bio_text, | |
| "", | |
| "### Regulatory Traceability", | |
| f"- **Summary:** {traceability_summary}", | |
| f"- **{basis_title}:** current official source classes as of {basis.get('as_of', 'current registry month')}", | |
| ] | |
| ) | |
| def _json_preview(result: dict) -> str: | |
| preview = { | |
| "schema_version": result["schema_version"], | |
| "stem_ai_version": result["stem_ai_version"], | |
| "target": result["target"], | |
| "classification": result["classification"], | |
| "score": result["score"], | |
| "replication_score": result.get("replication_score"), | |
| "replication_tier": result.get("replication_tier"), | |
| "stage_1_rubric": result.get("stage_1_rubric", {}), | |
| "stage_2r_rubric": result.get("stage_2r_rubric", {}), | |
| "stage_3_rubric": result.get("stage_3_rubric", {}), | |
| "stage_4_rubric": result.get("stage_4_rubric", {}), | |
| "code_integrity": result.get("code_integrity", {}), | |
| "regulatory_basis": result.get("regulatory_basis", {}), | |
| "stage_traceability": result.get("stage_traceability", {}), | |
| "regulatory_traceability": result.get("regulatory_traceability", {}), | |
| "reasoning_model": result.get("reasoning_model", {}), | |
| "notable_positive_evidence": result.get("notable_positive_evidence", []), | |
| "notable_risks": result.get("notable_risks", []), | |
| } | |
| return json.dumps(preview, indent=2) | |
| def run_demo(repo_url: str, level: int): | |
| repo_url = (repo_url or "").strip() | |
| try: | |
| level_int = int(level) | |
| except (TypeError, ValueError): | |
| level_int = 1 | |
| if level_int not in _LEVEL_MAP: | |
| level_int = 1 | |
| if not repo_url: | |
| return ( | |
| "Waiting for repository URL", | |
| "### Input required\nPaste a public GitHub URL for a bio/medical AI repository.", | |
| "No report generated.", | |
| json.dumps({"status": "input_required", "example": _DEFAULT_REPO}, indent=2), | |
| "No JSON artifact generated.", | |
| "No PDF artifact generated.", | |
| ) | |
| tmp_path = Path(tempfile.gettempdir()) / "stem_ai_demo" / uuid4().hex | |
| tmp_path.mkdir(parents=True, exist_ok=True) | |
| try: | |
| repo = _clone_github(repo_url, tmp_path) | |
| result = audit_repository(repo) | |
| mode, pages = _LEVEL_MAP[level_int] | |
| output_dir = tmp_path / "out" | |
| files = write_outputs(result, output_dir, mode, pages, "all") | |
| report = render_markdown(result, mode, pages) | |
| json_file = next(p for p in files if p.name.endswith("_experiment_results.json")) | |
| pdf_file = next(p for p in files if p.suffix == ".pdf") | |
| return ( | |
| f"{result['score']['final_score']} / 100 ({result['score']['formal_tier']})", | |
| _finding_cards(result), | |
| report, | |
| _json_preview(result), | |
| f"Generated JSON artifact: `{json_file.name}`", | |
| f"Generated PDF artifact: `{pdf_file.name}`", | |
| ) | |
| except Exception as exc: | |
| return ( | |
| "Audit failed", | |
| f"### Error\n`{type(exc).__name__}: {exc}`", | |
| "No report generated.", | |
| json.dumps({"error": str(exc), "error_type": type(exc).__name__}, indent=2), | |
| "No JSON artifact generated.", | |
| "No PDF artifact generated.", | |
| ) | |
| _CSS = """ | |
| .hero { | |
| padding: 28px 32px; | |
| border-radius: 18px; | |
| background: linear-gradient(135deg, #10233f 0%, #17616f 55%, #d7a84a 100%); | |
| color: white; | |
| margin-bottom: 20px; | |
| } | |
| .hero h1 { font-size: 40px; line-height: 1.05; margin: 0 0 10px; font-weight: 800; } | |
| .hero p { font-size: 16px; max-width: 820px; margin: 0; opacity: 0.92; line-height: 1.6; } | |
| .overview-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 14px; | |
| margin: 4px 0 22px; | |
| } | |
| @media (max-width: 1200px) { | |
| .overview-grid { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| } | |
| } | |
| @media (max-width: 760px) { | |
| .overview-grid { | |
| grid-template-columns: minmax(0, 1fr); | |
| } | |
| } | |
| """ | |
| _CARD_STYLE = ( | |
| "border:1px solid #dde6ef;border-radius:13px;padding:18px 20px;" | |
| "background:#ffffff;box-shadow:0 4px 18px rgba(16,35,63,0.08);" | |
| "display:flex;flex-direction:column;gap:8px;" | |
| ) | |
| _CARD_TITLE = ( | |
| "font-size:12px;font-weight:800;text-transform:uppercase;" | |
| "letter-spacing:0.06em;color:#10233f;margin:0;" | |
| ) | |
| _CARD_BODY = "font-size:13.5px;color:#374151;line-height:1.55;margin:0;" | |
| _CODE_PILL = ( | |
| "display:inline-block;background:#dbeafe;border:1px solid #93c5fd;" | |
| "border-radius:6px;padding:2px 8px;font-family:ui-monospace,monospace;" | |
| "font-size:12.5px;font-weight:700;color:#1e40af;" | |
| ) | |
| _STAGE_BADGE = ( | |
| "display:inline-block;background:#e0f2fe;border:1px solid #0284c7;" | |
| "border-radius:5px;padding:1px 6px;margin-right:4px;" | |
| "font-size:12px;font-weight:900;color:#075985;" | |
| ) | |
| _TIER_BADGE = ( | |
| "display:inline-block;border-radius:5px;padding:1px 6px;margin:2px 4px 2px 0;" | |
| "font-size:12px;font-weight:900;color:#ffffff;" | |
| ) | |
| with gr.Blocks(title=f"STEM BIO-AI — Evidence Scanner v{__version__}", **_blocks_kwargs()) as demo: | |
| gr.HTML( | |
| f""" | |
| <div class="hero"> | |
| <h1>STEM BIO-AI <span style="font-size:20px;font-weight:400;opacity:0.7">v{__version__}</span></h1> | |
| <p>Deterministic evidence-surface scanner for bio/medical AI repositories. | |
| No LLM · No API key · No model runtime · No secrets sent anywhere.<br> | |
| Scans README, docs, CI, tests, changelogs, and manifests — returns a T0–T4 triage tier | |
| with JSON, Markdown, and PDF artifacts, plus an evidence-only regulatory traceability note layer.</p> | |
| </div> | |
| """ | |
| ) | |
| gr.HTML( | |
| f""" | |
| <div class="overview-grid"> | |
| <div style="{_CARD_STYLE}"> | |
| <p style="{_CARD_TITLE}">No API key required</p> | |
| <p style="{_CARD_BODY}">Clones a public GitHub repository and runs a fully | |
| deterministic local scanner. Does not call OpenAI, Anthropic, | |
| GitHub API, or any external service.</p> | |
| </div> | |
| <div style="{_CARD_STYLE}"> | |
| <p style="{_CARD_TITLE}">What STEM scans</p> | |
| <p style="{_CARD_BODY}"> | |
| <span style="{_STAGE_BADGE}">S1</span> README hype/responsibility (H1–H6 penalties, R1–R5 credits) | |
| <span style="{_STAGE_BADGE}">S2R</span> Repo-local consistency | |
| <span style="{_STAGE_BADGE}">S3</span> CI · tests · changelog hygiene · data provenance · bias measurement | |
| <span style="{_STAGE_BADGE}">S4</span> Replication evidence lane | |
| </p> | |
| </div> | |
| <div style="{_CARD_STYLE}"> | |
| <p style="{_CARD_TITLE}">Current Deterministic Layers</p> | |
| <p style="{_CARD_BODY}"> | |
| Deterministic bio diagnostics for SMILES surface checks, optional RDKit validation, parser-guard detection, | |
| silent mock fallback detection, contract-surface checks, and an evidence-only regulatory traceability assistant. | |
| </p> | |
| </div> | |
| <div style="{_CARD_STYLE}"> | |
| <p style="{_CARD_TITLE}">Triage tiers</p> | |
| <p style="{_CARD_BODY}"> | |
| <span style="{_TIER_BADGE}background:#b91c1c;">T0</span> Rejected | |
| <span style="{_TIER_BADGE}background:#c2410c;">T1</span> Quarantine | |
| <span style="{_TIER_BADGE}background:#b45309;">T2</span> Caution<br> | |
| <span style="{_TIER_BADGE}background:#0f766e;">T3</span> Supervised | |
| <span style="{_TIER_BADGE}background:#15803d;">T4</span> Candidate<br> | |
| <span style="color:#374151;font-size:12.5px;">Clinical-adjacent repos without a disclaimer | |
| are hard-capped at T2. T4 is not a clinical safety rating.</span> | |
| </p> | |
| </div> | |
| <div style="{_CARD_STYLE}"> | |
| <p style="{_CARD_TITLE}">AIRI — AI Risk Index</p> | |
| <p style="{_CARD_BODY}"> | |
| Maps triggered detectors to the curated MIT AI Risk Repository runtime bundle so | |
| covered risks, known gaps, and detector-scope coverage stay visible in every scan. | |
| </p> | |
| </div> | |
| <div style="{_CARD_STYLE}"> | |
| <p style="{_CARD_TITLE}">Local CLI</p> | |
| <p style="{_CARD_BODY}">Run locally for downloadable artifacts:</p> | |
| <code style="{_CODE_PILL}">stem <folder> --format all --explain</code> | |
| </div> | |
| </div> | |
| """ | |
| ) | |
| gr.Markdown( | |
| "Paste a public bio/medical AI repository URL below. The demo works best on repositories " | |
| "with README/docs/tests/dependency files. Private repositories should be audited locally.\n\n" | |
| "> STEM BIO-AI includes deterministic bio diagnostics, contract checks, and a registry-driven regulatory traceability note layer.\n\n" | |
| "> Note: This Space scans the current default branch of a public GitHub repository at run time. " | |
| "Results may differ from commit-pinned benchmark artifacts or older local audit snapshots." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| repo_input = gr.Textbox( | |
| label="Public GitHub repository URL", | |
| value="", | |
| placeholder=_PLACEHOLDER_REPO, | |
| info=( | |
| "Paste a public bio/medical AI repository URL. " | |
| f"Safe demo repository: {_DEFAULT_REPO}" | |
| ), | |
| ) | |
| with gr.Column(scale=1): | |
| level_input = gr.Radio( | |
| choices=[ | |
| ("Brief - 1 page (legacy)", 1), | |
| ("Standard - 5 pages", 2), | |
| ("Full - 7 pages", 3), | |
| ], | |
| value=3, | |
| label="Report Level", | |
| info="Legacy brief, standard review packet, or full evidence packet.", | |
| ) | |
| with gr.Row(): | |
| run_button = gr.Button("Run live GitHub audit", variant="primary", scale=2) | |
| clear_button = gr.Button("Clear", scale=1) | |
| score_output = gr.Textbox(label="Final Score", interactive=False) | |
| snapshot_output = gr.Markdown(label="Audit Snapshot") | |
| with gr.Tabs(): | |
| with gr.Tab("Report"): | |
| report_output = gr.Markdown(label="Markdown report") | |
| with gr.Tab("JSON Preview"): | |
| json_preview = gr.Code( | |
| label="Machine-readable evidence object", | |
| language="json", | |
| interactive=False, | |
| lines=24, | |
| ) | |
| with gr.Tab("Local CLI Artifacts"): | |
| gr.Markdown( | |
| "This public Space shows report and JSON previews in-browser. " | |
| "For actual downloadable JSON/Markdown/PDF artifacts, run the local CLI." | |
| ) | |
| with gr.Row(): | |
| json_output = gr.Textbox(label="JSON artifact", interactive=False) | |
| pdf_output = gr.Textbox(label="PDF artifact", interactive=False) | |
| _all_outputs = [score_output, snapshot_output, report_output, json_preview, json_output, pdf_output] | |
| run_button.click( | |
| fn=run_demo, | |
| inputs=[repo_input, level_input], | |
| outputs=_all_outputs, | |
| queue=True, | |
| ) | |
| clear_button.click( | |
| fn=lambda: ["", "", "", "", "", ""], | |
| inputs=[], | |
| outputs=_all_outputs, | |
| queue=False, | |
| ) | |
| def launch_demo() -> None: | |
| """Launch the Space UI.""" | |
| demo.queue().launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| **_launch_kwargs(), | |
| ) | |