File size: 8,501 Bytes
0fff343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
"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&apos;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 &amp; 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(&lt;matrix&gt;, &lt;agg&gt;)</code>{" "}
          β€” collapse a matrix into one score per patient. {" "}
          <code className="font-mono text-ink">&lt;agg&gt;</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(&lt;scoreA&gt;, &lt;scoreB&gt;, &lt;op&gt;)</code>{" "}
          β€” merge two scores into one.{" "}
          <code className="font-mono text-ink">&lt;op&gt;</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(&lt;score&gt;, &lt;predicate&gt;)</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(&lt;score&gt;, &lt;target&gt;)</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(&lt;score&gt;, &lt;target&gt;, &lt;kind&gt;)</code>{" "}
          β€” Scalar output: plain observational correlation against the
          target. <code className="font-mono text-ink">&lt;kind&gt;</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(&lt;score&gt;, &lt;target&gt;, &lt;kind&gt;)</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>&apos;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 />
        &nbsp;&nbsp;Reduce(Select(M,[g05347,g00048]), mean),
        <br />
        &nbsp;&nbsp;Reduce(Select(M,[g06271]), max),
        <br />
        &nbsp;&nbsp;sub
        <br />)
      </p>
      <p className="text-muted">
        β†’ average g05347 &amp; g00048 β†’ scoreA; take the max of g06271 β†’
        scoreB; final score = scoreA βˆ’ scoreB.
      </p>
    </div>
  );
}