/** 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`
${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)})
`;
}