# scRNA Desk > Paste one per-cell quality-control metrics table from a single-cell RNA-seq dataset and get either > the QC read that decides what to filter, or the scvi-tools integration setup that follows it. A > free in-browser engine does every distribution statistic, the MAD-based outlier arithmetic, the > per-sample retention and the batch-versus-biology check before any model call. - **URL**: https://scrna-desk.skillsafe.ai/ - **Platform**: SkillSafe (https://skillsafe.ai/) — a static bundle under a strict CSP with a model behind an authenticated API. No server, no third-party requests. - **Model**: `gpt-terra` (the balanced OpenAI tier alias), publisher markup 1000 bps. - **Cost**: the in-browser read is free and needs no account. A worksheet run is metered and needs a signed-in SkillSafe account; the reserved amount is shown before you press the button and you are charged only for what the run uses. - **Privacy**: your expression matrix never leaves your machine. Only the metrics table you paste is sent, and only when you run a lane. ## What it is for One specific sitting: **you have a per-cell QC metrics export and you need to decide what to filter and how to set up the integration that follows.** Two lanes over one work object. | Lane | `task` value | What it produces | Source skill | | --- | --- | --- | --- | | QC read | `qc` | Per-sample verdicts, the threshold call, what the cut costs in cells, what could be lost | `@anthropics/single-cell-rna-qc` | | Integration setup | `integrate` | The scvi-tools model, the covariates, the training budget, the post-integration checks | `@anthropics/scvi-tools` | The integration lane runs on the QC lane's output through a handoff button; both read the same pasted table. ## The input `adata.obs` as a table. In scanpy, `adata.obs.to_csv("qc.csv")` after `sc.pp.calculate_qc_metrics`; in Seurat, `write.csv(obj@meta.data, "qc.csv")`. Columns are matched by name, not position, with scanpy and Seurat aliases: | Meaning | Accepted names include | | --- | --- | | total UMI counts (**required**) | `total_counts`, `nCount_RNA`, `n_umi`, `umi`, `library_size` | | genes detected (**required**) | `n_genes_by_counts`, `nFeature_RNA`, `n_genes`, `n_features` | | mitochondrial % | `pct_counts_mt`, `percent.mt`, `percent_mito`, `pct_mt` | | ribosomal % | `pct_counts_ribo`, `percent.ribo` | | haemoglobin % | `pct_counts_hb`, `percent.hb` | | doublet score | `doublet_score`, `scrublet_score`, `scDblFinder_score` | | sample / batch | `sample`, `batch`, `donor`, `orig.ident`, `library`, `channel` | | condition / group | `condition`, `group`, `treatment`, `genotype`, `timepoint` | | cell type label | `cell_type`, `labels`, `annotation` | | barcode | `barcode`, `cell`, `cell_id`, the unnamed index column | Comma, tab, pipe and semicolon tables all work, as do markdown tables. Comment lines beginning `#` are ignored. Files can be dropped in, or picked from your SkillSafe storage. ## What the free engine computes, before any run - **Distributions** for every metric: n, min, q1, median, q3, max and the unscaled median absolute deviation. - **MAD outlier bounds** the way scverse best practice states them: on the `log1p` scale for total counts and genes detected (their raw distributions are right-skewed, which inflates the MAD and hides the low tail), on the raw scale for the mitochondrial fraction. A cell is an outlier when it sits more than *n* MADs from the median in either direction. - **Retention per sample** for the thresholds as set — how many cells each rule removes, and what fraction of each sample survives. - **The batch-versus-biology check**: a condition value that sits inside a single sample is raised as **critical**, because correcting that batch removes the condition and not correcting it leaves the condition unreadable. No integration method fixes it; it is a design fact. - **Raw-count checks**: non-integer counts (the matrix has already been normalised, so scvi-tools cannot be set up on it), negative counts, genes exceeding counts (swapped columns), mitochondrial values on a 0-1 scale rather than 0-100, duplicated barcodes from a merge that did not prefix them. - **Ambient and doublet arithmetic**: the fraction of cells under 500 UMIs per sample, and the observed doublet fraction against the ~0.8%-per-1,000-recovered-cells 10x multiplet expectation. - **scvi-tools routing** across scVI, scANVI, totalVI, PeakVI, MultiVI, sysVI and DestVI from the declared modality and label state, with the batch key, the highly-variable-gene count and scvi-tools' own `min(round(20000/n_cells*400), 400)` epoch heuristic. - **Two downloadable scripts** generated from those numbers and not from model prose: a scanpy `qc_filter.py` that reproduces exactly the retention reported, and an `scvi_setup.py` carrying the routed model and covariates. ## The output contract One JSON object per run, the same envelope in both lanes: `lane`, `dataset_name`, `posture` (`clear`/`caution`/`blocked`), `verdict`, `exec_summary`, `samples[]` (one per engine sample ref, carrying that lane's four keys), `findings[]`, `checks[]` (seven fixed named checks per lane, in a fixed order), `steps[]` (staged `inspect`/`correct`/`filter`/`model`/`verify`), `parameters[]`, `coverage_check[]` (one entry per engine flag), `assumptions[]`, `open_questions[]`, `evidence_gaps[]`, `summary`. **Reconciliation is enforced in the browser, not trusted.** Every flag the free engine raised must be addressed exactly once by the worksheet; a flag it never mentioned is shown to the user as *not mentioned*, a key the engine never raised is shown as *invented*, and a correction step listed after a filtering step is called out — because ambient correction and doublet calling estimate a background that filtering changes. ## What it deliberately does not do - It does not read `.h5ad` or `.h5` files, and it does not see your expression matrix. It has your metrics, not your genes. - It makes no claim about biology: it cannot tell you a cell type is present, rare or absent. - It is not a diagnostic tool and not a statistical conclusion. It documents the consequences of the thresholds you set and names what contradicts itself. ## Programmatic access `https://scrna-desk.skillsafe.ai/api.html` documents the app API end to end — the `{data}/{error}` envelope, the error-code table, and worked examples per lane in cURL, Python, JavaScript, Go, Java, Ruby, PHP and C#. Base URL `https://api.skillsafe.ai/v1/app-api`. The `task` field selects the lane. `https://scrna-desk.skillsafe.ai/tokens.html` manages the token this browser holds. ## Credits A derived work built on two community skills from `@anthropics/knowledge-work-plugins`, credited in full: - `@anthropics/single-cell-rna-qc` — https://skillsafe.ai/skill/@anthropics/single-cell-rna-qc — the MAD filtering method, the metric set, the permissive-by-default posture, the species-specific gene patterns and the ambient/doublet ordering. - `@anthropics/scvi-tools` — https://skillsafe.ai/skill/@anthropics/scvi-tools — the model-selection tree, the raw-integer-counts requirement, the highly-variable-gene guidance and the covariate handling.