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_target_target: "目标浓度",
|
||||||
design_tube_defect: "Tube 缺陷",
|
design_tube_defect: "Tube 缺陷",
|
||||||
design_complex_defect: "Complex 缺陷",
|
design_complex_defect: "Complex 缺陷",
|
||||||
|
design_analyze_direct: "直接分析设计结果",
|
||||||
|
design_load_analysis: "载入为 Analysis 输入",
|
||||||
|
design_analysis_loaded: "已载入设计序列,可修改链浓度后分析",
|
||||||
|
design_analysis_unavailable: "没有可分析的设计结果",
|
||||||
details_json: "展开原始 JSON",
|
details_json: "展开原始 JSON",
|
||||||
toggle_structure_data: "结构数据",
|
toggle_structure_data: "结构数据",
|
||||||
toggle_pairs_panel: "配对热图",
|
toggle_pairs_panel: "配对热图",
|
||||||
|
|
@ -2517,6 +2521,10 @@ A+B</textarea>
|
||||||
design_target_target: "Target Concentration",
|
design_target_target: "Target Concentration",
|
||||||
design_tube_defect: "Tube Defect",
|
design_tube_defect: "Tube Defect",
|
||||||
design_complex_defect: "Complex 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",
|
details_json: "Show raw JSON",
|
||||||
toggle_structure_data: "Structure Data",
|
toggle_structure_data: "Structure Data",
|
||||||
toggle_pairs_panel: "Pair Heatmap",
|
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) {
|
function renderModelCard(result) {
|
||||||
return `
|
return `
|
||||||
<div class="result-card">
|
<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("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><strong>${t("summary_elapsed")}</strong><span class="metric-value">${stats.design_time ? `${Number(stats.design_time).toFixed(3)} s` : "-"}</span></div>
|
||||||
</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>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -5191,6 +5278,14 @@ A+B</textarea>
|
||||||
document.getElementById("exportResultBtn")?.addEventListener("click", () => {
|
document.getElementById("exportResultBtn")?.addEventListener("click", () => {
|
||||||
if (lastResult) downloadText("nupack-design-result.json", JSON.stringify(lastResult, null, 2));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
result.complexes.forEach((complex, index) => {
|
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() {
|
async function runAnalysis() {
|
||||||
if (activeSubmission) {
|
if (activeSubmission) {
|
||||||
setStatus(formatStatus(t("submission_locked")));
|
setStatus(formatStatus(t("submission_locked")));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue