/** Weibull reliability analysis renderers. */ import { html, svg, nothing } from "lit"; import { t } from "../styles"; import type { MaintenanceTask } from "../types"; export function renderWeibullSection(task: MaintenanceTask, lang: string) { const analysis = task.interval_analysis; const beta = analysis?.weibull_beta; const eta = analysis?.weibull_eta; if (beta == null || eta == null || eta <= 0) return nothing; const currentInterval = task.interval_days ?? 0; const rec = task.suggested_interval ?? currentInterval; return html`
${t("weibull_reliability_curve", lang)} ${renderBetaBadge(beta, lang)}
${renderWeibullChart(beta, eta, currentInterval, rec, lang)} ${renderWeibullInfo(analysis!, lang)} ${analysis?.confidence_interval_low != null ? renderConfidenceInterval(analysis!, task, lang) : nothing}
`; } function renderBetaBadge(beta: number, lang: string) { let cls: string; let icon: string; let key: string; if (beta < 0.8) { cls = "early_failures"; icon = "M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z"; key = "beta_early_failures"; } else if (beta <= 1.2) { cls = "random_failures"; icon = "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z"; key = "beta_random_failures"; } else if (beta <= 3.5) { cls = "wear_out"; icon = "M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z"; key = "beta_wear_out"; } else { cls = "highly_predictable"; icon = "M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"; key = "beta_highly_predictable"; } return html` ${t(key, lang)} (\u03B2=${beta.toFixed(2)}) `; } function renderWeibullChart(beta: number, eta: number, currentInterval: number, recommended: number, lang: string) { const W = 300, H = 160; const PAD_L = 32, PAD_R = 8, PAD_T = 8, PAD_B = 24; const chartW = W - PAD_L - PAD_R; const chartH = H - PAD_T - PAD_B; const maxT = Math.max(currentInterval, recommended, eta, 1) * 1.3; const N = 50; const points: Array<[number, number]> = []; for (let i = 0; i <= N; i++) { const t_val = (i / N) * maxT; const cdf = 1.0 - Math.exp(-Math.pow(t_val / eta, beta)); const x = PAD_L + (t_val / maxT) * chartW; const y = PAD_T + chartH - cdf * chartH; points.push([x, y]); } const polyline = points.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(" "); const areaPath = `M${PAD_L},${PAD_T + chartH} ` + points.map(([x, y]) => `L${x.toFixed(1)},${y.toFixed(1)}`).join(" ") + ` L${points[N][0].toFixed(1)},${PAD_T + chartH} Z`; const curX = PAD_L + (currentInterval / maxT) * chartW; const curCdf = 1.0 - Math.exp(-Math.pow(currentInterval / eta, beta)); const curY = PAD_T + chartH - curCdf * chartH; const reliability = ((1.0 - curCdf) * 100).toFixed(0); const recX = PAD_L + (recommended / maxT) * chartW; const yTicks = [0, 0.25, 0.5, 0.75, 1.0]; return html`
${yTicks.map(tick => { const y = PAD_T + chartH - tick * chartH; return svg` ${(tick * 100).toFixed(0)}% `; })} 0 ${Math.round(maxT / 2)} ${Math.round(maxT)} ${currentInterval > 0 ? svg` R=${reliability}% ` : nothing} ${recommended > 0 && recommended !== currentInterval ? svg` ` : nothing}
${t("weibull_failure_probability", lang)} ${currentInterval > 0 ? html` ${t("current_interval_marker", lang)}` : nothing} ${recommended > 0 && recommended !== currentInterval ? html` ${t("recommended_marker", lang)}` : nothing}
`; } function renderWeibullInfo(analysis: NonNullable, lang: string) { return html`
${t("characteristic_life", lang)} ${Math.round(analysis.weibull_eta!)} ${t("days", lang)}
${analysis.weibull_r_squared != null ? html`
${t("weibull_r_squared", lang)} ${analysis.weibull_r_squared!.toFixed(3)}
` : nothing}
`; } function renderConfidenceInterval(analysis: NonNullable, task: MaintenanceTask, lang: string) { const low = analysis.confidence_interval_low!; const high = analysis.confidence_interval_high!; const rec = task.suggested_interval ?? task.interval_days ?? 0; const current = task.interval_days ?? 0; const barMin = Math.max(0, low - 5); const barMax = high + 5; const range = barMax - barMin; const fillLeft = ((low - barMin) / range) * 100; const fillWidth = ((high - low) / range) * 100; const recPos = ((rec - barMin) / range) * 100; const curPos = current > 0 ? ((current - barMin) / range) * 100 : -1; return html`
${t("confidence_interval", lang)}: ${rec} ${t("days", lang)} (${low}\u2013${high})
${curPos >= 0 ? html`
` : nothing}
${t("confidence_conservative", lang)} (${low}${t("days", lang).charAt(0)}) ${t("confidence_aggressive", lang)} (${high}${t("days", lang).charAt(0)})
`; }