File size: 10,002 Bytes
9ae68e3 | 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 214 215 | <!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gene-expression semantic report</title>
<style>
:root{font:16px system-ui;color:#182536;background:#eef2f6}
body{max-width:1200px;margin:auto;padding:24px}
h1{margin-bottom:8px}h2{font-size:1.15rem}
header,section{background:white;border:1px solid #ced7e0;border-radius:12px;padding:20px;margin-bottom:16px}
label{display:inline-flex;flex-direction:column;gap:5px;margin:8px 12px 8px 0}
select,input{font:inherit;max-width:100%;padding:7px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
.grid section{min-width:0}
pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#f4f6f8;padding:12px}
.scroll{overflow:auto;max-height:420px}
table{border-collapse:collapse;width:100%;font-size:.9rem}
th,td{text-align:left;border-bottom:1px solid #ddd;padding:8px;vertical-align:top}
.tag{display:inline-block;border:1px solid;padding:3px 8px;border-radius:5px;font-size:.8rem}
.notice{color:#614700;background:#fff5d6;padding:12px}
#error{color:#9c1927}
</style>
<header>
<h1>Gene-expression semantic report</h1>
<p>Observed measurements, inferred results, and computational representations retain their declared meaning.</p>
<label>Open report JSON<input id="file" type="file" accept=".json,application/json"></label>
<p id="error" role="alert"></p>
<p id="state" aria-live="polite">No report loaded. No analysis has been executed.</p>
<p class="notice">This viewer displays supplied artifacts. Importing a report does not verify provenance, run native tools, establish convergence, or execute a quantum circuit.</p>
</header>
<section>
<h2>Observations</h2><span class="tag">OBSERVED — as declared by the report</span>
<div id="filters"></div><p id="counts"></p>
<div id="observations" class="scroll"></div>
<h3>Sequencing and quality metadata</h3><pre id="sequencing">Not supplied</pre>
</section>
<div class="grid">
<section><h2>Biological model and comparison</h2><pre id="models"></pre></section>
<section><h2>Parameters</h2><span class="tag">UNKNOWN / PRIOR</span><pre id="parameters"></pre></section>
<section><h2>Posteriors</h2><span class="tag">INFERRED</span><pre id="posteriors"></pre><h3>Predictions</h3><span class="tag">PREDICTED</span><pre id="predictions"></pre></section>
<section><h2>Constraints</h2><span class="tag">CONSTRAINED</span><pre id="constraints"></pre></section>
<section><h2>Binary semantics</h2><span class="tag">ENCODED</span><pre id="binary"></pre></section>
<section><h2>PTM</h2><pre id="ptm"></pre></section>
<section><h2>Quipper</h2><pre id="quipper"></pre></section>
<section><h2>Provenance</h2><pre id="artifacts"></pre><h3>Stage execution records</h3><pre id="stages"></pre></section>
</div>
<script>
"use strict";
const $ = id => document.getElementById(id);
const keys = ["gene_id","sample_id","condition","timepoint"];
const valueKeys = ["expression_count","normalized_expression"];
const missing = value => value === null || value === undefined;
const display = value => missing(value) ? "MISSING" :
typeof value === "object" ? JSON.stringify(value) : String(value);
const token = value => missing(value) ? "missing:" :
typeof value + ":" + JSON.stringify(value);
let report = null, observations = [], selections = {};
function show(id, value) {
$(id).textContent = missing(value) ? "Not supplied" :
JSON.stringify(value, null, 2);
}
function table(container, rows, columns) {
container.replaceChildren();
const table = document.createElement("table");
const head = document.createElement("thead");
const header = document.createElement("tr");
for (const name of columns) {
const th = document.createElement("th");
th.scope = "col"; th.textContent = name; header.append(th);
}
head.append(header); table.append(head);
const body = document.createElement("tbody");
for (const row of rows) {
const tr = document.createElement("tr");
for (const name of columns) {
const td = document.createElement("td");
td.textContent = display(row[name]); tr.append(td);
}
body.append(tr);
}
table.append(body); container.append(table);
}
function renderObservations() {
const rows = observations.filter(row =>
keys.every(key => selections[key] === "" ||
token(row[key]) === selections[key]));
const unique = key => new Set(rows.filter(row =>
!missing(row[key])).map(row => token(row[key]))).size;
const absent = rows.reduce((count,row) =>
count + valueKeys.filter(key => missing(row[key])).length, 0);
$("counts").textContent =
`${rows.length} of ${observations.length} observations; ` +
`${unique("sample_id")} samples; ${unique("gene_id")} genes; ` +
`${unique("condition")} conditions. ` +
`${absent} missing cells across expression_count and normalized_expression. ` +
`An absent measurement channel counts as missing, not zero.`;
table($("observations"), rows, [
...keys,"replicate","expression_count","normalized_expression",
"batch","cell_type","measurement_quality","synthetic","source_id"
]);
}
function renderFilters() {
$("filters").replaceChildren(); selections = {};
for (const key of keys) {
selections[key] = "";
const label = document.createElement("label");
label.textContent = key;
const select = document.createElement("select");
const all = document.createElement("option");
all.value = ""; all.textContent = "All"; select.append(all);
const values = new Map(observations.map(row =>
[token(row[key]), display(row[key])]));
for (const [id,name] of values) {
const option = document.createElement("option");
option.value = id; option.textContent = name; select.append(option);
}
select.addEventListener("change", () => {
selections[key] = select.value; renderObservations();
});
label.append(select); $("filters").append(label);
}
}
function validate(candidate) {
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
throw Error("Report must be a JSON object.");
if (!candidate.dataset ||
!Array.isArray(candidate.dataset.observations))
throw Error("dataset.observations must be an array.");
if (!candidate.dataset.observations.every(row =>
row && typeof row === "object" && !Array.isArray(row)))
throw Error("Each observation must be an object.");
if (!Array.isArray(candidate.models))
throw Error("models must be an array, including when empty.");
if (!candidate.models.every(model =>
model && typeof model === "object" && !Array.isArray(model)))
throw Error("Each model must be an object.");
for (const row of candidate.dataset.observations) {
for (const key of keys) {
if (!missing(row[key]) && !["string","number"].includes(typeof row[key]))
throw Error(`${key} must be a string, number, or null.`);
}
for (const key of valueKeys) {
if (!missing(row[key]) &&
(typeof row[key] !== "number" || !Number.isFinite(row[key])))
throw Error(`${key} must be a finite number or null.`);
}
if (!missing(row.expression_count) &&
(!Number.isSafeInteger(row.expression_count) || row.expression_count < 0))
throw Error("expression_count must be a nonnegative safe integer.");
}
}
function render() {
observations = report.dataset.observations;
renderFilters(); renderObservations();
show("sequencing", {
dataset_id: report.dataset.id ?? null,
synthetic: report.dataset.synthetic ?? "undeclared",
sequencing: report.dataset.sequencing ?? null,
quality: report.dataset.quality ?? null,
preprocessing: report.dataset.preprocessing ?? null
});
show("models", report.models.map(model => ({
id: model.id ?? null, status: model.status ?? "not supplied",
assumptions: model.assumptions ?? null,
structure: model.structure ?? null, priors: model.priors ?? null,
likelihood: model.likelihood ?? null, fit: model.fit ?? null,
limitations: model.limitations ?? null
})));
show("parameters", report.models.map(model => ({
id: model.id ?? null, parameters: model.parameters ?? null,
dimensions: model.dimensions ?? null, priors: model.priors ?? null
})));
show("posteriors", report.models.map(model => ({
id: model.id ?? null, status: model.status ?? "not supplied",
summary: model.summary ?? null,
diagnostics: model.diagnostics ?? null,
uncertainty_status: model.summary ?
"Inspect supplied intervals, standard deviations and diagnostics; viewer has not validated them." :
"No posterior summary supplied."
})));
show("predictions", report.models.map(model => ({
id: model.id ?? null, predictions: model.predictions ?? null
})));
for (const key of ["constraints","binary","ptm","quipper","artifacts","stages"])
show(key, report[key]);
$("state").textContent =
"Report loaded. Observation filters do not refit models or change posterior summaries. " +
"Model comparison is descriptive; no biological explanation is selected.";
}
let loadNumber = 0;
$("file").addEventListener("change", async event => {
const current = ++loadNumber;
const file = event.target.files[0];
if (!file) return;
$("error").textContent = "";
try {
if (file.size > 10 * 1024 * 1024)
throw Error("Report exceeds this viewer's 10 MiB import limit.");
const candidate = JSON.parse(await file.text());
if (current !== loadNumber) return;
validate(candidate); report = candidate; render();
} catch (error) {
if (current !== loadNumber) return;
$("error").textContent = error.message;
$("state").textContent =
report ? "Import rejected. Previous report remains displayed." :
"Import rejected. No report loaded.";
}
});
for (const id of ["models","parameters","posteriors","predictions",
"constraints","binary","ptm","quipper","artifacts","stages"])
show(id, null);
</script>
</html>
|