Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useId, useState } from "react"; | |
| import { safeParseProgramRepr } from "../lib/programRepr"; | |
| interface Props { | |
| text: string; | |
| onTextChange: (next: string) => void; | |
| onDraw: (repr: string, label: string) => void; | |
| } | |
| const PLACEHOLDER = | |
| "Combine(Reduce(Select(M,[g05347,g00048]),mean),Reduce(Select(M,[g06271]),max),sub)"; | |
| export default function PasteToDraw({ text, onTextChange, onDraw }: Props) { | |
| const [error, setError] = useState<string | null>(null); | |
| const [showFormat, setShowFormat] = useState(false); | |
| const panelId = useId(); | |
| function draw() { | |
| const trimmed = text.trim(); | |
| if (!trimmed) { | |
| setError("Paste a program first."); | |
| return; | |
| } | |
| const { ok, error: pErr } = safeParseProgramRepr(trimmed); | |
| if (!ok) { | |
| setError(`Couldn't parse: ${pErr}`); | |
| return; | |
| } | |
| setError(null); | |
| onDraw(trimmed, "pasted program"); | |
| } | |
| return ( | |
| <div> | |
| <label | |
| className="mb-1 block text-[11px] font-medium tracking-wide text-muted" | |
| htmlFor={`${panelId}-textarea`} | |
| > | |
| Paste a program | |
| </label> | |
| <textarea | |
| id={`${panelId}-textarea`} | |
| value={text} | |
| onChange={(e) => onTextChange(e.target.value)} | |
| placeholder={PLACEHOLDER} | |
| spellCheck={false} | |
| rows={2} | |
| className="w-full rounded-md border border-border bg-white px-3 py-2 font-mono text-xs text-ink focus:border-accent focus:outline-none" | |
| /> | |
| <p className="mt-1 text-[11px] text-muted"> | |
| Paste a program here, or click any tile to load it. Use Copy to grab | |
| a program's text. Reveal / evaluate is only available for actual | |
| run winners β pasted programs render as opaque IDs only. | |
| </p> | |
| <div className="mt-2 flex items-center justify-end gap-3"> | |
| <button | |
| type="button" | |
| onClick={draw} | |
| className="rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-card hover:bg-ink" | |
| > | |
| Draw | |
| </button> | |
| </div> | |
| {error && ( | |
| <p className="mt-2 rounded-md border border-highlight/40 bg-highlight/10 px-3 py-2 text-xs text-highlight"> | |
| {error} | |
| </p> | |
| )} | |
| <div className="mt-3 text-xs"> | |
| <button | |
| type="button" | |
| onClick={() => setShowFormat((v) => !v)} | |
| aria-expanded={showFormat} | |
| aria-controls={`${panelId}-format`} | |
| className="inline-flex items-center gap-1.5 rounded text-ink hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent" | |
| > | |
| <span | |
| aria-hidden | |
| style={{ | |
| display: "inline-block", | |
| transition: "transform 120ms ease", | |
| transform: showFormat ? "rotate(90deg)" : "rotate(0deg)", | |
| }} | |
| > | |
| βΈ | |
| </span> | |
| <span className="font-medium">Format & examples</span> | |
| </button> | |
| {showFormat && <FormatGuide id={`${panelId}-format`} />} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function FormatGuide({ id }: { id: string }) { | |
| return ( | |
| <div | |
| id={id} | |
| className="mt-3 rounded-md border border-border bg-bg p-4 text-[12.5px] leading-relaxed text-ink" | |
| > | |
| <p className="mb-3"> | |
| <strong>What a program is:</strong> a recipe that turns gene | |
| expression into ONE score per patient. | |
| </p> | |
| <p className="mb-2 font-semibold text-ink">The pieces:</p> | |
| <ul className="mb-3 ml-5 list-disc space-y-1 marker:text-muted"> | |
| <li> | |
| <code className="font-mono text-ink">M</code> β the expression | |
| matrix (all patients Γ genes); the starting data. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Select(M, [g#####, ...])</code>{" "} | |
| β pick a group of genes β a smaller matrix. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Search(M, k)</code> β bounded | |
| nested search: keep the top-k columns by univariate target | |
| association (gated; experimental). | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Reduce(<matrix>, <agg>)</code>{" "} | |
| β collapse a matrix into one score per patient. {" "} | |
| <code className="font-mono text-ink"><agg></code> ={" "} | |
| <code className="font-mono text-ink">mean</code> |{" "} | |
| <code className="font-mono text-ink">median</code> |{" "} | |
| <code className="font-mono text-ink">max</code> |{" "} | |
| <code className="font-mono text-ink">min</code> |{" "} | |
| <code className="font-mono text-ink">var</code>. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Combine(<scoreA>, <scoreB>, <op>)</code>{" "} | |
| β merge two scores into one.{" "} | |
| <code className="font-mono text-ink"><op></code> ={" "} | |
| <code className="font-mono text-ink">add</code> |{" "} | |
| <code className="font-mono text-ink">sub</code> |{" "} | |
| <code className="font-mono text-ink">mul</code> |{" "} | |
| <code className="font-mono text-ink">protected_div</code> (safe) |{" "} | |
| <code className="font-mono text-ink">mean</code>. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Split(<score>, <predicate>)</code>{" "} | |
| β partition patients into two branches by a name-blind predicate | |
| (<code className="font-mono text-ink">score</code> = above / | |
| below median; <code className="font-mono text-ink">stage_late</code>{" "} | |
| = AJCC stage III/IV), mean-centre per side, then recombine. One | |
| level of Split only. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">FitApply(<score>, <target>)</code>{" "} | |
| β train a small model on the score against the chosen label | |
| (<code className="font-mono text-ink">msi</code> or{" "} | |
| <code className="font-mono text-ink">tmb</code>) and emit | |
| predicted probabilities / values back to the program. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Associate(<score>, <target>, <kind>)</code>{" "} | |
| β Scalar output: plain observational correlation against the | |
| target. <code className="font-mono text-ink"><kind></code>{" "} | |
| = <code className="font-mono text-ink">spearman</code> |{" "} | |
| <code className="font-mono text-ink">pearson</code>. | |
| </li> | |
| <li> | |
| <code className="font-mono text-ink">Effect(<score>, <target>, <kind>)</code>{" "} | |
| β Scalar output: same correlation AFTER residualising on the | |
| named clinical confounders (stage + age). Observational | |
| backdoor adjustment β only as good as the measured confounders. | |
| </li> | |
| <li> | |
| <strong>Output:</strong> one score per patient (or a Scalar for | |
| Associate / Effect-rooted programs), used to satisfy the | |
| objective. | |
| </li> | |
| </ul> | |
| <p className="mb-2 font-semibold text-ink">Rules:</p> | |
| <ul className="mb-3 ml-5 list-disc space-y-1 marker:text-muted"> | |
| <li> | |
| Gene IDs are the opaque{" "} | |
| <code className="font-mono text-ink">g#####</code> IDs shown on | |
| the tiles and in the result. | |
| </li> | |
| <li> | |
| List a <code className="font-mono text-ink">Select</code>'s | |
| genes in <code className="font-mono text-ink">[square brackets]</code> | |
| , comma-separated. | |
| </li> | |
| <li>Spaces are optional β paste with or without them.</li> | |
| <li> | |
| Every <code className="font-mono text-ink">Reduce</code> should | |
| wrap a <code className="font-mono text-ink">Select(...)</code> so it | |
| reduces a chosen group, not the whole matrix. | |
| </li> | |
| </ul> | |
| <p className="mb-1 font-semibold text-ink">Annotated example:</p> | |
| <p className="mb-1 font-mono text-[12px] text-ink"> | |
| Combine( | |
| <br /> | |
| Reduce(Select(M,[g05347,g00048]), mean), | |
| <br /> | |
| Reduce(Select(M,[g06271]), max), | |
| <br /> | |
| sub | |
| <br />) | |
| </p> | |
| <p className="text-muted"> | |
| β average g05347 & g00048 β scoreA; take the max of g06271 β | |
| scoreB; final score = scoreA β scoreB. | |
| </p> | |
| </div> | |
| ); | |
| } | |