0 ? "mt-1.5 grid gap-1 border-t border-slate-100 pt-1.5" : "grid gap-1"}>
- {probabilityRows.slice(0, 8).map((item) => (
+
0 ? "mt-1.5 grid max-h-[260px] gap-1 overflow-y-auto border-t border-slate-100 pt-1.5 pr-1" : "grid max-h-[260px] gap-1 overflow-y-auto pr-1"}>
+ {probabilityRows.map((item) => (
@@ -118,6 +118,18 @@ function buildTooltipRows(
.filter((item): item is TooltipRow => item !== null);
}
+function formatProbabilityRangeValue(value: number) {
+ return Number(value.toFixed(1)).toString();
+}
+
+function formatProbabilityRangeLabel(
+ lower: number,
+ upper: number,
+ tempSymbol: string,
+) {
+ return `${formatProbabilityRangeValue(lower)}-${formatProbabilityRangeValue(upper)}${tempSymbol}`;
+}
+
function buildTooltipProbabilityRows(
probabilityOverlay: ProbabilityOverlay | null | undefined,
tempSymbol: string,
@@ -135,18 +147,20 @@ function buildTooltipProbabilityRows(
});
}
- const topBand = [...probabilityOverlay.bands]
- .filter((band) => validNumber(band.probability) !== null)
- .sort((a, b) => b.probability - a.probability)[0];
- if (topBand) {
- const probabilityPct = Math.round(topBand.probability * 100);
- rows.push({
- key: topBand.key,
- label: topBand.label,
- value: `${probabilityPct}%`,
- color: "#a78bfa",
- });
- }
+ rows.push(
+ ...[...probabilityOverlay.bands]
+ .filter((band) => validNumber(band.probability) !== null && band.probability > 0)
+ .sort((a, b) => a.lower - b.lower)
+ .map((band) => {
+ const probabilityPct = Math.round(band.probability * 100);
+ return {
+ key: band.key,
+ label: formatProbabilityRangeLabel(band.lower, band.upper, tempSymbol),
+ value: `${probabilityPct}%`,
+ color: "#a78bfa",
+ };
+ }),
+ );
return rows;
}
diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureTooltipContent.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureTooltipContent.test.ts
index 8500b8a4..bafcf8af 100644
--- a/frontend/components/dashboard/scan-terminal/__tests__/temperatureTooltipContent.test.ts
+++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureTooltipContent.test.ts
@@ -61,6 +61,15 @@ export function runTests() {
engine: "legacy",
muLine: { value: 27.4, label: "Gaussian μ 27.4°C" },
bands: [
+ {
+ key: "legacy_probability_26_0",
+ value: 26,
+ lower: 25.5,
+ upper: 26.5,
+ probability: 0.18,
+ label: "26°C 18%",
+ opacity: 0.08,
+ },
{
key: "legacy_probability_27_0",
value: 27,
@@ -70,6 +79,15 @@ export function runTests() {
label: "27°C 42%",
opacity: 0.13,
},
+ {
+ key: "legacy_probability_28_0",
+ value: 28,
+ lower: 27.5,
+ upper: 28.5,
+ probability: 0.31,
+ label: "28°C 31%",
+ opacity: 0.11,
+ },
],
},
"°C",
@@ -77,13 +95,15 @@ export function runTests() {
);
assert(
- probabilityRows.length === 2,
- "temperature tooltip should show Gaussian μ and the leading probability bucket as compact context",
+ probabilityRows.length === 4,
+ "temperature tooltip should show Gaussian μ and every available probability bucket as compact context",
);
assert(
probabilityRows.some((row) => row.key === "legacy_probability_mu" && row.value === "27.4°C") &&
- probabilityRows.some((row) => row.key === "legacy_probability_27_0" && row.value === "42%"),
- "temperature tooltip should format Gaussian μ and probability values without drawing a full probability band on the main chart",
+ probabilityRows.some((row) => row.key === "legacy_probability_26_0" && row.label === "25.5-26.5°C" && row.value === "18%") &&
+ probabilityRows.some((row) => row.key === "legacy_probability_27_0" && row.label === "26.5-27.5°C" && row.value === "42%") &&
+ probabilityRows.some((row) => row.key === "legacy_probability_28_0" && row.label === "27.5-28.5°C" && row.value === "31%"),
+ "temperature tooltip should format the full Gaussian probability distribution by temperature range without drawing it on the main chart",
);
const projectRoot = process.cwd();