⚗ ScientistWorkbench

← All examples

HER2 expression across GTEx tissues, live from the GTEx API

Prompt

Plot median expression of HER2 across GTEx tissues, highlight the top 5.

No inputs attached.

At a glance

The identical prompt went to ScientistWorkbench and to a frontier chat model with no tools, no sandbox and no history. Same rows, side by side.

ScientistWorkbenchPlain frontier model
time · tokens2 min · 58k34 s · 2,892 (output, mostly thinking)
tool calls · files · figures16 · 7 · 10 · 0 · 0
What ranGTEx API queried live (gene lookup + median expression, logged), table checked, chart drawnNothing. A script was written for you to run
Top tissueNerve – Tibial, 130.1 TPMGuessed: “breast, esophageal mucosa, skin, bladder/vagina, stomach or kidney cortex”
Top 5Tibial nerve · sun-exposed skin · esophagus mucosa · non-sun-exposed skin · thyroidNot given (“run the code rather than trusting that ordering”)
Where breast ranks23rd, 37.1 TPMExpected near the top
Data provenanceTwo request URLs with status and byte counts in _queries.jsonl; raw JSON savedNone
FigureBar chart, 54 tissues, top 5 highlighted (PNG + PDF)None
independent review7 findings from a second pass over the transcriptnone

What the plain model did. It answered in 34 seconds with a well-written Python script and a paragraph of advice, then said, in its own words, to "run the code rather than trusting that ordering". It guessed the top tissues would be "breast, esophageal mucosa, skin, bladder/vagina, stomach or kidney cortex". No data was fetched, no numbers, no figure.

What the workbench did. It resolved HER2 to ERBB2 (ENSG00000141736.13), called the GTEx v8 API, checked the response (54 tissues, one page, all in TPM, no duplicates), ranked them, drew the chart and wrote the report — 16 tool calls in about 2 minutes, with every request URL and status logged in _queries.jsonl.

The difference that matters. The real top tissue is tibial nerve (130 TPM), followed by sun-exposed skin, esophagus mucosa, non-sun-exposed skin and thyroid; breast is 23rd at 37 TPM. The plain model's expectation would have put breast near the top. A script is not a result: until it runs, the ranking is a guess, and here the guess was wrong about the headline.

The two outputs, section by section

Switch tabs; the sections line up (figures → report → review → execution log). Everything is verbatim.

1Figures
Median TPM of ERBB2 (HER2) in 54 GTEx v8 tissues, top 5 highlighted; data fetched live and logged in _queries.jsonl.
fig1_HER2_median_expression_GTEx.png — Median TPM of ERBB2 (HER2) in 54 GTEx v8 tissues, top 5 highlighted; data fetched live and logged in _queries.jsonl.
2Report (summary.md, verbatim)

HER2 (ERBB2) median expression across GTEx tissues

Objective

Plot median expression of HER2 (gene symbol ERBB2) across GTEx tissues and highlight the five tissues with the highest median expression.

Method

  1. Resolved the stable gene identifier for HER2/ERBB2 using the GTEx Portal API v2 reference endpoint: GET https://gtexportal.org/api/v2/reference/gene?geneId=ERBB2 → versioned GENCODE id ENSG00000141736.13 (GENCODE v26, GRCh38/hg38, Entrez 2064, protein coding, chr17).
  2. Retrieved the per-tissue median gene expression (GTEx release v8) with: GET https://gtexportal.org/api/v2/expression/medianGeneExpression?gencodeId=ENSG00000141736.13&datasetId=gtex_v8. The response contains one record per tissue with median expression in TPM (transcripts per million); response was a single page covering all 54 GTEx v8 tissues (paging_info.totalNumberOfItems = 54).
  3. Saved the raw JSON response verbatim (raw_gtex_ERBB2_medianGeneExpression.json) and parsed it into a tidy table sorted by descending median TPM (her2_gtex_median_expression.csv).
  4. Ranked tissues by median TPM and extracted the top 5 (her2_gtex_top5_tissues.csv).
  5. Plotted a horizontal bar chart (all 54 tissues, ranked) with the top 5 tissues highlighted in an Okabe-Ito colour-blind-safe orange, all others in grey, per the lab's science-figures conventions (labelled axis with units, grid, legend, descriptive title, annotated top-5 values).
  6. Every API call was logged with timestamp, URL, HTTP status and response size in _queries.jsonl.

No local input files were used (none were attached to this job); all data were fetched from the public GTEx Portal API v2 as described above.

Results

  • Gene resolved: HER2 / ERBB2, GENCODE id ENSG00000141736.13 (GTEx reference/gene lookup).
  • Dataset: GTEx v8, median expression across n = 54 tissues, unit TPM.
  • Expression range across all tissues: 1.25 TPM (Whole Blood) to 130.13 TPM (Nerve — Tibial) (from her2_gtex_median_expression.csv).
  • Top 5 tissues by median ERBB2 (HER2) expression (from her2_gtex_top5_tissues.csv):
Rank Tissue Median expression (TPM)
1 Nerve – Tibial 130.13
2 Skin – Sun-exposed (Lower leg) 118.26
3 Esophagus – Mucosa 113.10
4 Skin – Not sun-exposed (Suprapubic) 110.94
5 Thyroid 99.31
  • No fitted statistical model was applied (this is a descriptive plot of pre-computed GTEx median values); the only "statistic shown" is the per-tissue median TPM as reported by the GTEx v8 release, ranked in descending order.
  • QC: the API returned exactly one page (numberOfPages = 1) with 54 records, matching totalNumberOfItems = 54; no missing or duplicate tissue entries were found in the parsed table; all records carried unit TPM (verified programmatically).

Assumptions & caveats

  • No input files were provided for this job; data were fetched live from the public GTEx Portal API v2 (gtexportal.org/api/v2), which is standard practice for this kind of query and is recorded here for reproducibility.
  • Used GTEx release v8 (datasetId=gtex_v8), the latest stable release exposed by this endpoint at query time; no newer release was requested explicitly by the task, so the default/most complete release was chosen.
  • "Median expression" is the tissue-level median TPM computed and provided directly by GTEx (not recomputed from raw per-sample read counts, since GTEx does not expose raw sample-level counts through this lightweight API endpoint); values are used as returned.
  • "Top 5" is defined as the five tissues with the highest median TPM value, a standard and unambiguous ranking criterion.
  • GTEx tissue granularity separates closely related sub-sites (e.g., multiple brain sub-regions, two skin sites) as distinct tissues; this is the standard GTEx tissue nomenclature (tissueSiteDetailId) and was kept as-is rather than aggregated, to match how GTEx itself reports and plots this data.

Files

  • summary.md — this report.
  • fig1_HER2_median_expression_GTEx.png — horizontal bar chart of median ERBB2 (HER2) expression (TPM) across all 54 GTEx v8 tissues, ranked descending, top 5 tissues highlighted in orange with annotated values.
  • fig1_HER2_median_expression_GTEx.pdf — vector twin of the above figure.
  • her2_gtex_median_expression.csv — tidy table of all 54 tissues with median ERBB2 expression (TPM), tissue ontology id, and unit, sorted descending.
  • her2_gtex_top5_tissues.csv — the 5 tissues with highest median ERBB2 expression, with rank and TPM value.
  • raw_gtex_ERBB2_medianGeneExpression.json — raw JSON response snapshot from the GTEx Portal API v2 medianGeneExpression endpoint for ENSG00000141736.13 / gtex_v8.
  • scripts/plot_her2_gtex.py — script that reads the CSV and generates the figure and top-5 table.
  • _queries.jsonl — log of every API request made (timestamp, URL, HTTP status, response size).
3Independent review (7 findings)

A second pass re-reads the transcript and artifacts and checks each claim of the report against what actually ran.

verdictclaimevidence
passGene resolved: HER2 / ERBB2, GENCODE id ENSG00000141736.13cell 7 output: GET /reference/gene?geneId=ERBB2 returns gencodeId 'ENSG00000141736.13', geneSymbol 'ERBB2', matching report exactly.
passTop 5 tissues by median ERBB2 expression: Nerve-Tibial 130.13, Skin Sun-exposed Lower leg 118.26, Esophagus Mucosa 113.10, Skin Not Sun-exposed Suprapubic 110.94, Thyroid 99.31cell 12 stdout table exactly matches these 5 rank/tissue/median values (130.1310, 118.2600, 113.1010, 110.9440, 99.3077).
passn = 54 tissues, dataset GTEx v8, unit TPMcell 9 stdout 'print(len(r['data']))' -> 54; cell 12 stdout 'n_tissues: 54'; JSON records show unit TPM in cell 8/9 output.
warnExpression range across all tissues: 1.25 TPM (Whole Blood) to 130.13 TPM (Nerve — Tibial)cell 12 stdout only shows 'median range: 1.24878 - 130.131' without naming which tissue corresponds to the minimum value; no execution record explicitly confirms 'Whole Blood' as the minimum-expression tissue.
warnno missing or duplicate tissue entries were found in the parsed table; all records carried unit TPM (verified programmatically)cell 10 code computes df['unit'].unique() and prints it, but the captured stdout for cell 10 is truncated before this line appears; no visible output in the record confirms this QC check actually ran/printed a result.
warnEvery API call was logged with timestamp, URL, HTTP status and response size in _queries.jsonlcell 14 shows 3 log lines: 1 reference/gene call plus 2 identical medianGeneExpression calls (cells 8 and 9 both queried the same endpoint), meaning an extra duplicate query was made beyond what the Method section describes as a single retrieval step.
passFiles produced: her2_gtex_median_expression.csv, her2_gtex_top5_tissues.csv, raw_gtex_ERBB2_medianGeneExpression.json, fig1_HER2_median_expression_GTEx.png/.pdf, _queries.jsonl, scripts/plot_her2_gtex.pycell 14 'ls -la /mnt/session/outputs/' lists all these files present with nonzero size, matching the Files section.
4Execution log (8 of 16 tool calls shown)

The full log — every code cell with source, stdout, stderr and exit status — ships with every job as _execution_log.json.

#1 · read · ok
{"file_path": "/workspace/skills/life-science-apis/SKILL.md"}
1	---
2	name: life-science-apis
3	description: How to query public life-science databases from the sandbox with plain HTTP (UniProt, Ensembl, InterPro, PDB, AlphaFold, gnomAD, ClinVar, GTEx, cBioPortal, PubChem, BindingDB, openFDA, OpenAlex, Open Targets, DepMap). Use when a task needs an identifier lookup, sequence, structure, variant, expression, compound, trial or literature record. Every query must be logged.
4	---
5	
6	# Life-science APIs from the sandbox
7	
8	Use `helpers/lsapi.py` (`from lsapi import get_json, get_text`) or plain `requests`. Rules:
9	
10	1. **Log every query.** `lsapi` appends `{ts, url, status, bytes}` to `/mnt/session/outputs/_queries.jsonl`; if you use
11	   `reque
… [more]
#2 · read · error
{"file_path": "/workspace/skills/life-science-apis/apis/gtex.md"}
awk: cannot open "/workspace/skills/life-science-apis/apis/gtex.md" (No such file or directory)
#3 · bash · error
ls /workspace/skills/life-science-apis/apis/ ; ls /workspace/skills/life-science-apis/helpers/
ls: cannot access '/workspace/skills/life-science-apis/apis/': No such file or directory
ls: cannot access '/workspace/skills/life-science-apis/helpers/': No such file or directory

#4 · bash · ok
find /workspace/skills -type f | head -100
/workspace/skills/science-figures/SKILL.md
/workspace/skills/life-science-apis/lsapi.py
/workspace/skills/life-science-apis/gtex.md
/workspace/skills/life-science-apis/SKILL.md
/workspace/skills/life-science-apis/interpro.md
/workspace/skills/life-science-apis/bindingdb.md
/workspace/skills/life-science-apis/openalex.md
/workspace/skills/life-science-apis/openfda.md
/workspace/skills/life-science-apis/gnomad.md
/workspace/skills/life-science-apis/depmap.md
/workspace/skills/life-science-apis/opentargets.md
/workspace/skills/life-science-apis/pubchem.md
/workspace/skills/life-science-apis/cbioportal.md
/workspace/skills/life-science-apis/uniprot.md
/workspace/skills/life-science-apis/alphafol
… [more]
#5 · read · ok
{"file_path": "/workspace/skills/life-science-apis/gtex.md"}
1	# GTEx Portal API v2 — https://gtexportal.org/api/v2
2	- Gene expression by tissue: `GET /expression/medianGeneExpression?gencodeId=ENSG00000141510.18&datasetId=gtex_v8`.
3	- Gene lookup: `GET /reference/gene?geneId=TP53`. eQTLs: `GET /association/singleTissueEqtl?gencodeId=…&tissueSiteDetailId=Liver`.
4	- Gotchas: needs versioned GENCODE ids; tissue ids are like `Brain_Cortex`.
#6 · read · ok
{"file_path": "/workspace/skills/life-science-apis/lsapi.py"}
1	"""Tiny polite HTTP helper with provenance logging. from lsapi import get_json, get_text"""
2	import json, os, time
3	from pathlib import Path
4	import requests
5	
6	LOG = Path(os.environ.get("WB_QUERY_LOG", "/mnt/session/outputs/_queries.jsonl"))
7	_last: dict[str, float] = {}
8	UA = {"User-Agent": "ScientistWorkbench/0.1 (research sandbox; contact via job report)"}
9	
10	
11	def _pace(host: str, min_gap: float = 1.0) -> None:
12	    t = _last.get(host, 0.0)
13	    wait = min_gap - (time.time() - t)
14	    if wait > 0:
15	        time.sleep(wait)
16	    _last[host] = time.time()
17	
18	
19	def _log(url: str, status: int, nbytes: int) -> None:
20	    try:
21	        LOG.parent.mkdir(parent
… [more]
#7 · read · ok
{"file_path": "/workspace/skills/science-figures/SKILL.md"}
1	---
2	name: science-figures
3	description: Conventions for publication-quality scientific figures produced in the sandbox (matplotlib/seaborn). Use whenever a task produces a plot, tree, heatmap, structure view or multi-panel figure; covers layout, annotation, colour, labelling, file output and the figure record in summary.md.
4	---
5	
6	# Science figures
7	
8	**One message per figure.** Decide what the reader must see in three seconds and design for that; move everything else
9	to a table. Multi-panel figures get panel letters (A, B, …) and a one-sentence title per panel.
10	
11	**Layout**
12	- `fig, ax = plt.subplots(figsize=(w, h), constrained_layout=True)`; 7.2 in wide for single/doubl
… [more]
#8 · bash · ok
mkdir -p /mnt/session/outputs/scripts
cd /mnt/session/outputs
python3 -c "
import sys
sys.path.insert(0, '/workspace/skills/life-science-apis')
from lsapi import get_json
import json

r = get_json('https://gtexportal.org/api/v2/reference/gene', params={'geneId':'ERBB2'})
print(json.dumps(r, indent=2)[:3000])
"
{
  "data": [
    {
      "chromosome": "chr17",
      "dataSource": "HAVANA",
      "description": "erb-b2 receptor tyrosine kinase 2 [Source:HGNC Symbol;Acc:HGNC:3430]",
      "end": 39730426,
      "entrezGeneId": 2064,
      "gencodeId": "ENSG00000141736.13",
      "gencodeVersion": "v26",
      "geneStatus": "",
      "geneSymbol": "ERBB2",
      "geneSymbolUpper": "ERBB2",
      "geneType": "protein coding",
      "genomeBuild": "GRCh38/hg38",
      "start": 39687914,
      "strand": "+",
      "tss": 39687914
    }
  ],
  "paging_info": {
    "numberOfPages": 1,
    "page": 0,
    "maxItemsPerPage": 250,
    "totalNumberOfItems": 1
  }
}
1Figures
None. A chat model cannot execute code or produce an image.
2Report (model output, verbatim, 2,892 output tokens incl. thinking)

Approach

GTEx publishes median TPM per tissue, which you can pull straight from the GTEx Portal API (no bulk download needed). HER2's official symbol is ERBB2 (Ensembl ENSG00000141736), so the script resolves the symbol → GENCODE ID first, then fetches median expression.

"""
Median expression of HER2 (ERBB2) across GTEx tissues, top 5 highlighted.
Requires: requests, pandas, matplotlib
"""
import requests
import pandas as pd
import matplotlib.pyplot as plt

GENE = "ERBB2"          # HER2
DATASET = "gtex_v8"     # use "gtex_v10" for the latest release
API = "https://gtexportal.org/api/v2"
TOP_N = 5


def get_gencode_id(symbol, dataset=DATASET):
    r = requests.get(f"{API}/reference/gene",
                     params={"geneId": symbol, "gencodeVersion": "v26",
                             "genomeBuild": "GRCh38/hg38"},
                     timeout=30)
    r.raise_for_status()
    hits = r.json()["data"]
    # keep the exact symbol match (avoids pseudogenes / read-throughs)
    exact = [h for h in hits if h["geneSymbol"].upper() == symbol.upper()]
    return (exact or hits)[0]["gencodeId"]


def get_median_expression(gencode_id, dataset=DATASET):
    r = requests.get(f"{API}/expression/medianGeneExpression",
                     params={"gencodeId": gencode_id, "datasetId": dataset,
                             "itemsPerPage": 1000},
                     timeout=60)
    r.raise_for_status()
    df = pd.DataFrame(r.json()["data"])
    df = df.rename(columns={"median": "median_tpm",
                            "tissueSiteDetailId": "tissue_id"})
    df["tissue"] = df["tissue_id"].str.replace("_", " ", regex=False)
    return df[["tissue", "median_tpm"]].sort_values("median_tpm",
                                                    ascending=False)


# ---- fetch -----------------------------------------------------------------
gid = get_gencode_id(GENE)
df = get_median_expression(gid).reset_index(drop=True)
print(df.head(10).to_string(index=False))

# ---- plot ------------------------------------------------------------------
top = df.head(TOP_N)["tissue"].tolist()
colors = ["#d62728" if t in top else "#c9ccd1" for t in df["tissue"]]

fig, ax = plt.subplots(figsize=(8, 12))
ax.barh(df["tissue"], df["median_tpm"], color=colors, edgecolor="none")
ax.invert_yaxis()                      # highest at top
ax.set_xlabel("Median expression (TPM)")
ax.set_title(f"HER2 / {GENE} ({gid}) median expression across GTEx tissues\n"
             f"{DATASET.upper().replace('_', ' ')} — top {TOP_N} highlighted",
             fontsize=12)
ax.spines[["top", "right"]].set_visible(False)
ax.grid(axis="x", alpha=0.3)

# label the highlighted bars
for t, v in zip(top, df.head(TOP_N)["median_tpm"]):
    ax.text(v + df["median_tpm"].max() * 0.01, list(df["tissue"]).index(t),
            f"{v:,.1f}", va="center", fontsize=9, color="#d62728",
            fontweight="bold")

for lbl in ax.get_yticklabels():
    if lbl.get_text() in top:
        lbl.set_color("#d62728")
        lbl.set_fontweight("bold")

plt.tight_layout()
plt.savefig("HER2_GTEx_median_expression.png", dpi=300)
plt.show()

Notes / options

  • Log scale: ERBB2 spans a wide dynamic range (whole blood ≈ 0 TPM vs. epithelial tissues in the hundreds). Add ax.set_xscale("log") and plot median_tpm + 0.1 if you want the low tissues visible.
  • Offline alternative: if the API is blocked, download GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct.gz from the GTEx downloads page and replace the fetch step with: python df = pd.read_csv("...gene_median_tpm.gct.gz", sep="\t", skiprows=2) row = df[df["Description"] == "ERBB2"].iloc[0, 2:] df = row.rename("median_tpm").rename_axis("tissue").reset_index() \ .sort_values("median_tpm", ascending=False)
  • Caveat on isoforms: these are gene-level TPMs; they don't distinguish HER2 splice variants (e.g., p95HER2/Δ16) or reflect protein/IHC status, which is what matters clinically.
  • v8 vs v10: switch DATASET (and gencodeVersion to v39/GRCh38 for v10) if you want the newer release — tissue list and medians differ slightly.

Biologically, expect the top tissues to be epithelium-rich ones (breast, esophageal mucosa, skin, bladder/vagina, stomach or kidney cortex), with whole blood at the bottom — but run the code rather than trusting that ordering, since exact ranks shift between GTEx releases.

3Independent review
None. Nothing checks the claims above against a record, because there is no record.
4Execution log
Empty. No code ran; every number in the report was produced by reasoning.

AI-generated analysis. Outputs may contain errors; verify numbers against the execution log and source files before relying on them. Not a substitute for review by a qualified scientist, and not medical advice: any use affecting patient care or healthcare decisions requires review by a qualified professional.