Add design result analysis shortcut
This commit is contained in:
parent
e1b3156754
commit
11dc85d7ad
1 changed files with 144 additions and 0 deletions
|
|
@ -2213,6 +2213,10 @@ A+B</textarea>
|
|||
design_target_target: "目标浓度",
|
||||
design_tube_defect: "Tube 缺陷",
|
||||
design_complex_defect: "Complex 缺陷",
|
||||
design_analyze_direct: "直接分析设计结果",
|
||||
design_load_analysis: "载入为 Analysis 输入",
|
||||
design_analysis_loaded: "已载入设计序列,可修改链浓度后分析",
|
||||
design_analysis_unavailable: "没有可分析的设计结果",
|
||||
details_json: "展开原始 JSON",
|
||||
toggle_structure_data: "结构数据",
|
||||
toggle_pairs_panel: "配对热图",
|
||||
|
|
@ -2517,6 +2521,10 @@ A+B</textarea>
|
|||
design_target_target: "Target Concentration",
|
||||
design_tube_defect: "Tube Defect",
|
||||
design_complex_defect: "Complex Defect",
|
||||
design_analyze_direct: "Analyze Designed Result",
|
||||
design_load_analysis: "Load as Analysis Input",
|
||||
design_analysis_loaded: "Designed sequences loaded; adjust strand concentrations before analysis",
|
||||
design_analysis_unavailable: "No designed result is available for analysis",
|
||||
details_json: "Show raw JSON",
|
||||
toggle_structure_data: "Structure Data",
|
||||
toggle_pairs_panel: "Pair Heatmap",
|
||||
|
|
@ -3747,6 +3755,81 @@ A+B</textarea>
|
|||
};
|
||||
}
|
||||
|
||||
function buildAnalysisOptionsFromControls() {
|
||||
return {
|
||||
num_sample: Number(document.getElementById("numSample").value),
|
||||
energy_gap: Number(document.getElementById("energyGap").value),
|
||||
sparsity_fraction: Number(document.getElementById("sparsityFraction").value),
|
||||
sparsity_threshold: Number(document.getElementById("sparsityThreshold").value),
|
||||
single_mfe: false,
|
||||
result_limit: Number(document.getElementById("resultLimit").value),
|
||||
pairs_preview_size: Number(document.getElementById("pairsPreviewSize").value),
|
||||
};
|
||||
}
|
||||
|
||||
function buildDesignedAnalysisPayload(result) {
|
||||
if (!result || workflowKind(result) !== "design" || !Array.isArray(result.strands) || !result.strands.length) {
|
||||
throw new Error(t("design_analysis_unavailable"));
|
||||
}
|
||||
const inputStrandsByName = new Map((lastPayload?.strands || []).map((strand) => [strand.name, strand]));
|
||||
const strands = result.strands.map((strand) => {
|
||||
const input = inputStrandsByName.get(strand.name) || {};
|
||||
return {
|
||||
name: strand.name,
|
||||
sequence: strand.sequence,
|
||||
concentration: Number(input.concentration ?? 1.0),
|
||||
unit: input.unit || "uM",
|
||||
};
|
||||
});
|
||||
const targetLines = (result.design?.targets || result.complexes || [])
|
||||
.map((target) => Array.isArray(target.strand_names) ? target.strand_names.join("+") : "")
|
||||
.filter(Boolean);
|
||||
return {
|
||||
workflow: "analysis",
|
||||
mode: result.mode || "tube",
|
||||
model: { ...result.model },
|
||||
compute: ["pfunc", "mfe", "pairs"],
|
||||
options: buildAnalysisOptionsFromControls(),
|
||||
strands,
|
||||
tube: {
|
||||
name: result.tube?.name || result.design?.target_tubes?.[0]?.name || "designed_tube",
|
||||
max_size: Number(result.tube?.max_size ?? result.design?.target_tubes?.[0]?.max_size ?? 2),
|
||||
},
|
||||
complexes_text: targetLines.join("\n"),
|
||||
design_domains: [],
|
||||
design_complexes: [],
|
||||
design_tubes: [],
|
||||
design: {},
|
||||
hard_constraints: [],
|
||||
soft_constraints: [],
|
||||
defect_weights: [],
|
||||
};
|
||||
}
|
||||
|
||||
function loadDesignedAnalysisInput(result) {
|
||||
const payload = buildDesignedAnalysisPayload(result);
|
||||
workflowSelect.value = "analysis";
|
||||
modeSelect.value = payload.mode;
|
||||
document.getElementById("material").value = payload.model.material;
|
||||
document.getElementById("ensemble").value = payload.model.ensemble;
|
||||
document.getElementById("celsius").value = payload.model.celsius;
|
||||
document.getElementById("sodium").value = payload.model.sodium;
|
||||
document.getElementById("magnesium").value = payload.model.magnesium;
|
||||
document.getElementById("tubeName").value = payload.tube.name;
|
||||
document.getElementById("maxSize").value = payload.tube.max_size;
|
||||
document.querySelectorAll(".check input").forEach((input) => {
|
||||
input.checked = payload.compute.includes(input.value);
|
||||
});
|
||||
strandList.innerHTML = "";
|
||||
payload.strands.forEach((strand) => addStrandRow(strand));
|
||||
syncComplexRowsFromText(payload.complexes_text);
|
||||
syncWorkflow();
|
||||
syncMode();
|
||||
document.querySelector(".main-grid")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
setStatus(formatStatus(t("design_analysis_loaded")));
|
||||
return payload;
|
||||
}
|
||||
|
||||
function renderModelCard(result) {
|
||||
return `
|
||||
<div class="result-card">
|
||||
|
|
@ -3864,6 +3947,10 @@ A+B</textarea>
|
|||
<div><strong>${t("design_seed")}</strong><span class="metric-value">${stats.seed ?? "-"}</span></div>
|
||||
<div><strong>${t("summary_elapsed")}</strong><span class="metric-value">${stats.design_time ? `${Number(stats.design_time).toFixed(3)} s` : "-"}</span></div>
|
||||
</div>
|
||||
<div class="compact-actions" style="margin-top: 12px;">
|
||||
<button class="secondary" id="loadDesignedAnalysisBtn" type="button">${t("design_load_analysis")}</button>
|
||||
<button id="runDesignedAnalysisBtn" type="button">${t("design_analyze_direct")}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -5191,6 +5278,14 @@ A+B</textarea>
|
|||
document.getElementById("exportResultBtn")?.addEventListener("click", () => {
|
||||
if (lastResult) downloadText("nupack-design-result.json", JSON.stringify(lastResult, null, 2));
|
||||
});
|
||||
document.getElementById("loadDesignedAnalysisBtn")?.addEventListener("click", () => {
|
||||
try {
|
||||
loadDesignedAnalysisInput(result);
|
||||
} catch (error) {
|
||||
setStatus(`${t("status_error")}: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
document.getElementById("runDesignedAnalysisBtn")?.addEventListener("click", runDesignedAnalysis);
|
||||
return;
|
||||
}
|
||||
result.complexes.forEach((complex, index) => {
|
||||
|
|
@ -5302,6 +5397,55 @@ A+B</textarea>
|
|||
}
|
||||
}
|
||||
|
||||
async function runDesignedAnalysis() {
|
||||
if (activeSubmission) {
|
||||
setStatus(formatStatus(t("submission_locked")));
|
||||
return;
|
||||
}
|
||||
let payload;
|
||||
try {
|
||||
payload = buildDesignedAnalysisPayload(lastResult);
|
||||
const validationError = validatePayload(payload);
|
||||
if (validationError) throw new Error(validationError);
|
||||
} catch (error) {
|
||||
setStatus(`${t("status_error")}: ${error.message}`, true);
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmissionLocked(true);
|
||||
currentJobId = null;
|
||||
setStatus(formatStatus(t("status_running")));
|
||||
results.innerHTML = "";
|
||||
|
||||
try {
|
||||
refreshHealth();
|
||||
const { response, data } = await fetchJsonOrThrow("/api/jobs", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok || data.status !== "accepted") {
|
||||
throw new Error(data?.error || `Analysis failed with HTTP ${response.status}`);
|
||||
}
|
||||
currentJobId = data.job_id;
|
||||
setStatus(formatStatus(t("status_waiting_job")));
|
||||
const result = await waitForJob(data.job_id);
|
||||
result.job_id = data.job_id;
|
||||
lastPayload = payload;
|
||||
lastResult = result;
|
||||
renderResult(result);
|
||||
saveHistory(payload, result);
|
||||
renderHistory();
|
||||
setStatus(formatStatus(t("status_done")));
|
||||
} catch (error) {
|
||||
results.innerHTML = `<div class="result-card"><h3>${t("request_failed")}</h3><pre>${String(error)}</pre></div>`;
|
||||
setStatus(t("status_error"), true);
|
||||
refreshHealth();
|
||||
} finally {
|
||||
setSubmissionLocked(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function runAnalysis() {
|
||||
if (activeSubmission) {
|
||||
setStatus(formatStatus(t("submission_locked")));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue