MillPulse Professional Report Generator
⚙️ Configuration
Used for all reports and history
🏷️ Company Logo

Upload your logo to embed it on all generated reports. PNG, JPG, or SVG — max 2MB.

🖼️

Drop logo here or click to browse

PNG · JPG · SVG · Max 2MB

Logo preview
logo.png
Select Report Type
📊
Mill Operations Report
Monthly / Quarterly
Comprehensive mill performance summary: production KPIs, chemical consumption, energy performance, anomalies, shift highlights, and maintenance summary.
📋
Pre-Trial Report
Before Trial
Written before a chemical trial begins. Covers baseline conditions, trial objectives, hypothesis, chemical products, measurement plan, risk assessment, and approval signatures.
⚗️
Trial Report (In-Progress)
During Trial
Live trial status: baseline vs. trial phase comparison, preliminary observations, process deviations, interim KPI performance, adjustments made, and next steps.
Post-Trial Report
After Trial
Full statistical analysis: means, std dev, confidence intervals, t-test, economic impact, environmental assessment, and a definitive proceed / modify / abandon recommendation.
📊 Mill Operations Report
Optional. Auto-generated from period dates if left blank.
1
Querying mill data from D1…
2
Aggregating KPIs and statistics…
3
Writing analysis with GPT-4o…
4
Assembling HTML report…
5
Saving to R2 storage…
📋 Pre-Trial Report
1
Fetching trial record…
2
Querying 30-day baseline data…
3
Writing trial narrative with GPT-4o…
4
Assembling HTML report with signatures…
5
Saving to R2 storage…
⚗️ Trial Report (In-Progress)
1
Fetching trial record and phase data…
2
Comparing baseline vs. trial phase…
3
Writing interim analysis with GPT-4o…
4
Assembling HTML report…
5
Saving to R2 storage…
✅ Post-Trial Report
Required for the Recommendations section.
1
Fetching complete trial history…
2
Computing statistical analysis…
3
Writing comprehensive narrative with GPT-4o…
4
Building multi-section report…
5
Saving to R2 storage…
Generated Report
📁 Report History
📂

Enter your Tenant ID above and click Refresh to load report history.

`; } } function closePreview() { document.getElementById('report-preview-section').classList.remove('visible'); state.currentReportHtml = null; } // ── History ─────────────────────────────────────────────────────────────────── const TYPE_CONFIG = { 'mill-ops': { label: 'Mill Ops', icon: '📊', cls: 'mill-ops' }, 'pre-trial': { label: 'Pre-Trial', icon: '📋', cls: 'pre-trial' }, 'trial': { label: 'In-Progress',icon: '⚗️', cls: 'trial' }, 'post-trial': { label: 'Post-Trial', icon: '✅', cls: 'post-trial'}, }; async function loadHistory() { const base = apiBase(); const tenantId = globalTenantId(); const historyEl = document.getElementById('history-list'); if (!base || !tenantId) { historyEl.innerHTML = '
📂

Enter your Worker URL and Tenant ID, then click Refresh.

'; return; } historyEl.innerHTML = '
Loading…
'; try { const res = await fetch(`${base}/report/list?tenant_id=${encodeURIComponent(tenantId)}`); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Failed to load history'); const reports = data.reports || []; if (reports.length === 0) { historyEl.innerHTML = '
📂

No reports generated yet for this tenant.

'; return; } historyEl.innerHTML = reports.map(r => { const cfg = TYPE_CONFIG[r.report_type] || { label: r.report_type, icon: '📄', cls: '' }; const meta = r.period_start ? `${fmtDateShort(r.period_start)} – ${fmtDateShort(r.period_end)}` : r.trial_id ? `Trial: ${r.trial_id}` : ''; const size = r.file_size_bytes ? fmtBytes(r.file_size_bytes) : ''; return `
${cfg.icon}
${r.report_title || 'Untitled Report'}
${fmtDateShort(r.created_at)}${meta ? ' · ' + meta : ''}${size ? ' · ' + size : ''}${r.prepared_by ? ' · ' + r.prepared_by : ''}
${cfg.label}
`; }).join(''); } catch(e) { historyEl.innerHTML = `
⚠️

Failed to load history: ${e.message}

`; } } async function viewHistoryReport(url, id) { await loadReportPreview(url, id); } // ── Init ────────────────────────────────────────────────────────────────────── // Restore last used API base URL from sessionStorage (function init() { const saved = sessionStorage.getItem('pulpiq_api_base'); if (saved) document.getElementById('api-base-url').value = saved; const savedTenant = sessionStorage.getItem('pulpiq_tenant_id'); if (savedTenant) { document.getElementById('tenant-id-global').value = savedTenant; ['mo-tenant','pt-tenant','tr-tenant','po-tenant'].forEach(id => { const el = document.getElementById(id); if (el) el.value = savedTenant; }); } })(); document.getElementById('api-base-url').addEventListener('change', function() { sessionStorage.setItem('pulpiq_api_base', this.value.trim()); }); document.getElementById('tenant-id-global').addEventListener('change', function() { sessionStorage.setItem('pulpiq_tenant_id', this.value.trim()); }); // ────────────────────────────────────────────────────────────────────────── // EXPORT HELPERS — Word (.doc) and Excel (.xls) // No external libraries — uses native Blob + SpreadsheetML / MHTML // ────────────────────────────────────────────────────────────────────────── function exportToWord(html, reportId) { const filename = 'pulpiq-report-' + (reportId || 'export') + '.doc'; const bodyMatch = html.match(/]*>([\s\S]*?)<\/body>/i); const bodyContent = bodyMatch ? bodyMatch[1] : html; const wordHtml = [ '', '', '', '', '', bodyContent, '', '' ].join('\n'); const blob = new Blob([wordHtml], { type: 'application/msword' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); setTimeout(function(){ document.body.removeChild(a); URL.revokeObjectURL(a.href); }, 5000); } function exportToExcel(html, reportId) { const filename = 'pulpiq-report-' + (reportId || 'export') + '.xls'; const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const tables = doc.querySelectorAll('table'); if (tables.length === 0) { alert('No data tables found in this report. Try a Mill Operations or Post-Trial report which includes process data tables.'); return; } var sheetsXml = ''; tables.forEach(function(table, idx) { var sheetName = 'Sheet ' + (idx + 1); var prevEl = table.previousElementSibling; if (prevEl && /^H[1-3]$/i.test(prevEl.tagName)) { sheetName = prevEl.textContent.trim().replace(/[:\\/?*\[\]]/g, '').slice(0, 31); } var rowsXml = ''; table.querySelectorAll('tr').forEach(function(tr) { var cellsXml = ''; tr.querySelectorAll('th, td').forEach(function(cell) { var val = cell.textContent.trim() .replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); var isHeader = cell.tagName === 'TH'; var style = isHeader ? ' ss:StyleID="Header"' : ''; var num = val.replace(/[%,]/g, ''); var isNum = !isNaN(parseFloat(num)) && isFinite(num) && val.length > 0; if (isNum) { cellsXml += '' + parseFloat(num) + ''; } else { cellsXml += '' + val + ''; } }); rowsXml += '' + cellsXml + ''; }); sheetsXml += [ '', '' + rowsXml + '
', '
' ].join(''); }); var xlsXml = [ '', '', '', '', '', '', sheetsXml, '' ].join('\n'); var blob = new Blob([xlsXml], { type: 'application/vnd.ms-excel' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); setTimeout(function(){ document.body.removeChild(a); URL.revokeObjectURL(a.href); }, 5000); }