Compare EMOS peak with DEB in scan list
This commit is contained in:
@@ -9572,7 +9572,7 @@
|
||||
|
||||
.root :global(.scan-table-header) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(210px, 1.1fr) minmax(132px, 0.72fr) minmax(220px, 1.12fr) minmax(150px, 0.82fr) 96px;
|
||||
grid-template-columns: minmax(220px, 1.1fr) minmax(128px, 0.68fr) minmax(230px, 1.08fr) minmax(150px, 0.78fr) 92px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
@@ -9786,7 +9786,7 @@
|
||||
|
||||
.root :global(.scan-table-row) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(210px, 1.1fr) minmax(132px, 0.72fr) minmax(220px, 1.12fr) minmax(150px, 0.82fr) 96px;
|
||||
grid-template-columns: minmax(220px, 1.1fr) minmax(128px, 0.68fr) minmax(230px, 1.08fr) minmax(150px, 0.78fr) 92px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-height: 132px;
|
||||
@@ -9892,76 +9892,76 @@
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-preview) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-card) {
|
||||
.root :global(.scan-model-compare) {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
padding: 8px 6px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(87, 119, 166, 0.1);
|
||||
background: rgba(15, 28, 46, 0.72);
|
||||
color: #b4c7df;
|
||||
font-size: 10px;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(86, 120, 168, 0.16);
|
||||
border-radius: 12px;
|
||||
background: rgba(13, 27, 45, 0.7);
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-card strong) {
|
||||
display: block;
|
||||
margin-bottom: 1px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-card.featured strong) {
|
||||
color: #22eb98;
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-line) {
|
||||
.root :global(.scan-model-primary) {
|
||||
display: grid;
|
||||
grid-template-columns: 12px minmax(22px, 1fr) auto;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
color: rgba(180, 199, 223, 0.9);
|
||||
grid-template-columns: minmax(76px, 1fr) auto auto;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-line b) {
|
||||
color: rgba(148, 163, 184, 0.78);
|
||||
font-size: 9px;
|
||||
.root :global(.scan-model-primary span),
|
||||
.root :global(.scan-model-rows b) {
|
||||
color: rgba(146, 168, 197, 0.92);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-line i) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-line.model i) {
|
||||
background: linear-gradient(90deg, #17d98b, #20e3d2);
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-line.market i) {
|
||||
background: linear-gradient(90deg, #3f8cff, #57a8ff);
|
||||
}
|
||||
|
||||
.root :global(.scan-distribution-line em) {
|
||||
overflow: hidden;
|
||||
color: rgba(226, 237, 252, 0.92);
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 760;
|
||||
.root :global(.scan-model-primary strong) {
|
||||
color: #22eb98;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.root :global(.scan-model-primary em) {
|
||||
color: #d7e7fb;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.root :global(.scan-model-rows) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.root :global(.scan-model-rows span) {
|
||||
display: grid;
|
||||
grid-template-columns: 42px minmax(54px, auto) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.root :global(.scan-model-rows strong) {
|
||||
color: #eef7ff;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.root :global(.scan-model-rows em) {
|
||||
overflow: hidden;
|
||||
color: #9fb4cf;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ function formatPercent(value?: number | null, signed = false) {
|
||||
return `${signed && numeric >= 0 ? "+" : ""}${numeric.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
function formatSignedTemperatureDelta(value?: number | null, tempSymbol?: string | null) {
|
||||
if (value == null || Number.isNaN(Number(value))) return "--";
|
||||
const numeric = Number(value);
|
||||
if (Math.abs(numeric) < 0.05) return "0";
|
||||
const absValue = formatTemperatureValue(Math.abs(numeric), tempSymbol);
|
||||
return `${numeric > 0 ? "+" : "-"}${absValue}`;
|
||||
}
|
||||
|
||||
function formatWindowMinutes(value: number | null | undefined, locale: string) {
|
||||
if (value == null || !Number.isFinite(Number(value))) return "--";
|
||||
const minutes = Math.max(0, Math.round(Number(value)));
|
||||
@@ -144,40 +152,58 @@ function ProbabilityPreview({
|
||||
});
|
||||
}
|
||||
|
||||
const modelLabel = "EMOS";
|
||||
const marketLabel = locale === "en-US" ? "Market" : "市场";
|
||||
const peak =
|
||||
preview.reduce<DistributionPreviewPoint | null>((best, item) => {
|
||||
const probability = Number(item.model_probability ?? -1);
|
||||
const bestProbability = Number(best?.model_probability ?? -1);
|
||||
return probability > bestProbability ? item : best;
|
||||
}, null) || preview.find((item) => item.highlighted) || preview[0];
|
||||
const peakValue = peak?.value ?? row.peak_value ?? null;
|
||||
const peakLabel =
|
||||
peakValue != null
|
||||
? formatTemperatureValue(Number(peakValue), tempSymbol)
|
||||
: normalizeTemperatureLabel(peak?.label, tempSymbol) || "--";
|
||||
const peakProbability =
|
||||
peak?.model_probability != null
|
||||
? Number(peak.model_probability) * 100
|
||||
: row.peak_probability != null
|
||||
? Number(row.peak_probability) * 100
|
||||
: null;
|
||||
const debLabel =
|
||||
row.deb_prediction != null
|
||||
? formatTemperatureValue(Number(row.deb_prediction), tempSymbol)
|
||||
: "--";
|
||||
const delta =
|
||||
peakValue != null && row.deb_prediction != null
|
||||
? Number(peakValue) - Number(row.deb_prediction)
|
||||
: null;
|
||||
const targetLabel =
|
||||
normalizeTemperatureLabel(row.target_label, tempSymbol) ||
|
||||
(row.target_value != null ? formatTemperatureValue(Number(row.target_value), tempSymbol) : "--");
|
||||
const targetProbability =
|
||||
row.model_event_probability != null ? Number(row.model_event_probability) * 100 : null;
|
||||
|
||||
return (
|
||||
<div className="scan-distribution-preview">
|
||||
{preview.slice(0, 6).map((item) => {
|
||||
const modelPercent =
|
||||
item.model_probability != null ? item.model_probability * 100 : null;
|
||||
const marketPercent =
|
||||
item.market_probability != null ? item.market_probability * 100 : null;
|
||||
const modelWidth = Math.max(3, Math.min(100, Number(modelPercent || 0)));
|
||||
const marketWidth = Math.max(3, Math.min(100, Number(marketPercent || 0)));
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${item.label}-${item.value ?? ""}`}
|
||||
className={`scan-distribution-card ${item.highlighted ? "featured" : ""}`}
|
||||
>
|
||||
<strong>
|
||||
{normalizeTemperatureLabel(item.label, tempSymbol) || item.label || "--"}
|
||||
</strong>
|
||||
<span className="scan-distribution-line model">
|
||||
<b>{modelLabel}</b>
|
||||
<i style={{ width: `${modelWidth}%` }} />
|
||||
<em>{formatPercent(modelPercent)}</em>
|
||||
</span>
|
||||
<span className="scan-distribution-line market">
|
||||
<b>{marketLabel}</b>
|
||||
<i style={{ width: `${marketWidth}%` }} />
|
||||
<em>{formatPercent(marketPercent)}</em>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="scan-model-compare">
|
||||
<div className="scan-model-primary">
|
||||
<span>{locale === "en-US" ? "EMOS peak" : "EMOS峰值"}</span>
|
||||
<strong>{peakLabel}</strong>
|
||||
<em>{formatPercent(peakProbability)}</em>
|
||||
</div>
|
||||
<div className="scan-model-rows">
|
||||
<span>
|
||||
<b>DEB</b>
|
||||
<strong>{debLabel}</strong>
|
||||
<em>{delta == null ? "--" : formatSignedTemperatureDelta(delta, tempSymbol)}</em>
|
||||
</span>
|
||||
<span>
|
||||
<b>{locale === "en-US" ? "Target" : "目标"}</b>
|
||||
<strong>{targetLabel}</strong>
|
||||
<em>EMOS {formatPercent(targetProbability)}</em>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -249,7 +275,7 @@ export const OpportunityTable = React.memo(function OpportunityTable({
|
||||
<div className="scan-table-header">
|
||||
<span>{isEn ? "City / Market" : "城市 / 市场"}</span>
|
||||
<span>{isEn ? "Local Time / Phase" : "当前时间 / 阶段"}</span>
|
||||
<span>{isEn ? "EMOS / Market" : "EMOS / 市场"}</span>
|
||||
<span>{isEn ? "EMOS / DEB" : "EMOS / DEB"}</span>
|
||||
<span>{isEn ? "Quote / Model" : "买价 / 模型"}</span>
|
||||
<span>{isEn ? "Edge" : "边际优势"}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user