`;
// 2. Setup Details
html += `
I. Model Setup & Criteria
`;
html += `
| Error Metric | ${data.errorMetric} |
| Data Source | ${data.dataSource} |
| Parameters Fitted | ${data.parameters} |
`;
// 3. Log Table
if (logData.length > 0) {
html += `
II. Fitting Trial Log (Optimization History)
`;
let tableHtml = `
| Trial ID |
Parameter Set Tested |
Error Value |
Outcome / Rationale |
`;
logData.forEach(item => {
const rowColor = item.outcome.toUpperCase().includes('ACCEPTED') ? '#dcfce7' : '#fff';
tableHtml += `
| ${item.id} |
${item.params} |
${item.error} |
${item.outcome} |
`;
});
tableHtml += `
`;
html += tableHtml;
}
container.innerHTML = html;
}
function bmpelSwitchTab(tabId) {
document.querySelectorAll('.bmpel-tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.bmpel-content').forEach(c => c.classList.remove('active'));
const idx = tabId === 'setup' ? 0 : (tabId === 'log' ? 1 : 2);
document.querySelectorAll('.bmpel-tab-btn')[idx].classList.add('active');
document.getElementById('bmpel-' + tabId).classList.add('active');
if (tabId === 'report') {
bmpelRenderReport();
}
}
function bmpelLoadExample() {
if(!confirm("Overwrite current data with example Hodgkin-Huxley fitting data?")) return;
document.getElementById('inp-model').value = "Hodgkin-Huxley Neural Model v3";
document.getElementById('inp-system').value = "Squid Giant Axon Firing Rate";
document.getElementById('inp-error-metric').value = "RMS Error (Root Mean Square)";
document.getElementById('inp-data-source').value = "Patch-clamp recording (Exp. ID 45B)";
document.getElementById('inp-parameters').value = "g_Na (Sodium conductance), C_m (Membrane capacitance), V_L (Leak reversal potential)";
// Clear and refill rows
document.getElementById('bmpel-log-rows-container').innerHTML = '';
bmpelAddTrialRow("TRIAL 1", "g_Na=1.2; C_m=1.0; V_L=-65", "0.087", "Rejected: Firing rate too high.");
bmpelAddTrialRow("TRIAL 2", "g_Na=1.1; C_m=0.8; V_L=-70", "0.015", "ACCEPTED: Best fit to spike shape.");
bmpelAddTrialRow("TRIAL 3", "g_Na=1.15; C_m=0.9; V_L=-68", "0.035", "Rejected: Error too high compared to Trial 2.");
bmpelRenderReport();
bmpelSwitchTab('report');
}
/* --- PDF Generation --- */
async function bmpelGeneratePDF() {
bmpelRenderReport(); // Final render check
const logData = bmpelGetLogData();
if (logData.length === 0) {
alert("Please add trial entries before generating the PDF.");
return;
}
const data = {
model: document.getElementById('inp-model').value || "Model Name",
system: document.getElementById('inp-system').value || "Target System",
errorMetric: document.getElementById('inp-error-metric').value || "Error Metric",
dataSource: document.getElementById('inp-data-source').value || "Data Source",
parameters: document.getElementById('inp-parameters').value || "Parameters pending."
};
const { jsPDF } = window.jspdf;
const doc = new jsPDF('l', 'mm', 'a4'); // Landscape for better table fit
const teal = [0, 121, 107];
let y = 20;
// 1. Header
doc.setFillColor(...teal);
doc.rect(0, 0, 297, 20, 'F');
doc.setTextColor(255, 255, 255);
doc.setFontSize(16);
doc.text(`Biophysical Model Parameter Estimation Log`, 14, 13);
// 2. Setup Details
y = 30;
doc.setTextColor(0, 0, 0);
doc.setFontSize(10);
const setupTable = [
['Model/System', `${data.model} / ${data.system}`],
['Parameters Fitted', data.parameters],
['Error Metric', data.errorMetric],
['Data Source', data.dataSource]
];
doc.autoTable({
startY: y,
head: [['MODEL SETUP & CONTEXT', '']],
body: setupTable,
theme: 'grid',
headStyles: { fillColor: teal, fontSize: 10 },
styles: { fontSize: 9 },
columnStyles: { 0: { fontStyle: 'bold', fillColor: [240, 240, 240] } }
});
y = doc.lastAutoTable.finalY + 10;
// 3. Log Table
doc.setFontSize(12);
doc.setFont("helvetica", "bold");
doc.setTextColor(...teal);
doc.text("Fitting Trial History", 14, y);
y += 5;
const tableBody = logData.map(item => [
item.id,
item.params,
item.error,
item.outcome
]);
doc.autoTable({
startY: y,
head: [['Trial ID', 'Parameter Set Tested', 'Error Value', 'Outcome / Rationale']],
body: tableBody,
theme: 'grid',
headStyles: { fillColor: teal, fontSize: 10 },
styles: { fontSize: 8.5 },
columnStyles: {
0: { cellWidth: 20, fontStyle: 'bold' },
1: { cellWidth: 65, font: 'courier' },
2: { cellWidth: 20, halign: 'center' },
3: { cellWidth: 'auto', overflow: 'linebreak' }
},
didParseCell: function(data) {
if (data.section === 'body' && data.column.index === 3) {
if (data.cell.raw.toUpperCase().includes('ACCEPTED')) {
data.cell.styles.fillColor = [200, 240, 200];
}
}
}
});
// 4. Final Note
let finalY = doc.lastAutoTable.finalY + 15;
if (finalY > 180) { doc.addPage(); finalY = 30; }
doc.setFontSize(10);
doc.text(`Final Accepted Parameter Set (Trial ${logData.filter(p => p.outcome.toUpperCase().includes('ACCEPTED')).pop()?.id || 'N/A'}) must be transferred to the model file.`, 14, finalY);
doc.save(`ParameterEstimation_Log.pdf`);
}