修复天气页面提交信息

This commit is contained in:
2569718930@qq.com
2026-04-22 00:53:58 +08:00
parent e5facb041c
commit 8e949821f2
+110 -19
View File
@@ -90,6 +90,45 @@ function displayTempToMarketCelsius(
return value;
}
function formatBucketDisplayLabel(
bucket: ProbabilityBucket,
detail: Pick<CityDetail, "temp_symbol">,
) {
let bucketLabel = bucket.label || `${bucket.value}${detail.temp_symbol}`;
if (!bucketLabel) return "";
let str = String(bucketLabel).toUpperCase().replace(/\s+/g, "");
const symbol = detail.temp_symbol || "°C";
if (isFahrenheitSymbol(symbol)) {
str = str.replace(/℃/g, "°F").replace(/°C/g, "°F");
} else {
str = str.replace(/℃/g, "°C").replace(/°F/g, "°C");
}
str = str.replace(/°?C($|\+|-)/g, "°C$1");
str = str.replace(/°?F($|\+|-)/g, "°F$1");
if (!/[°℃][CF]/.test(str) && /[0-9]/.test(str)) {
str += symbol;
}
return str;
}
function getMarketBucketUnit(bucket?: MarketTopBucket | null) {
return String(bucket?.unit || "").toUpperCase();
}
function isMarketBucketAbove(bucket?: MarketTopBucket | null) {
const text = `${bucket?.label || ""} ${bucket?.slug || ""} ${bucket?.question || ""}`
.toLowerCase()
.replace(/\s+/g, "");
return text.includes("+") || text.includes("orhigher") || text.includes("or-higher");
}
function isMarketBucketBelow(bucket?: MarketTopBucket | null) {
const text = `${bucket?.label || ""} ${bucket?.slug || ""} ${bucket?.question || ""}`
.toLowerCase()
.replace(/\s+/g, "");
return text.includes("<=") || text.includes("orlower") || text.includes("or-lower");
}
function findMarketBucketForDisplayTemp(
buckets: MarketTopBucket[],
displayTemp: number | null,
@@ -134,6 +173,60 @@ function findMarketBucketForDisplayTemp(
return best && bestDelta <= tolerance ? best : null;
}
function marketBucketContainsDisplayTemp(
bucket: MarketTopBucket | null,
displayTemp: number | null,
detail: Pick<CityDetail, "temp_symbol">,
) {
if (!bucket || displayTemp == null || !Number.isFinite(displayTemp)) return false;
const bucketUnit = getMarketBucketUnit(bucket);
const compareTemp =
bucketUnit === "F" ? displayTemp : displayTempToMarketCelsius(displayTemp, detail);
if (compareTemp == null) return false;
const lower = bucket.lower != null ? Number(bucket.lower) : null;
const upper = bucket.upper != null ? Number(bucket.upper) : null;
if (lower != null && !Number.isFinite(lower)) return false;
if (upper != null && !Number.isFinite(upper)) return false;
if (lower != null && upper != null) {
return compareTemp >= lower - 0.01 && compareTemp <= upper + 0.01;
}
if (lower != null && isMarketBucketAbove(bucket)) {
return compareTemp >= lower - 0.01;
}
if (lower != null && isMarketBucketBelow(bucket)) {
return compareTemp <= lower + 0.01;
}
const reference = bucket.temp ?? bucket.value ?? lower;
const numeric = reference != null ? Number(reference) : null;
if (numeric == null || !Number.isFinite(numeric)) return false;
const tolerance = bucketUnit === "F" ? 0.56 : 0.26;
return Math.abs(compareTemp - numeric) <= tolerance;
}
function getAggregatedModelProbabilityForMarketBucket(
probabilities: ProbabilityBucket[],
bucket: MarketTopBucket | null,
detail: Pick<CityDetail, "temp_symbol">,
) {
if (!bucket) return null;
let total = 0;
let matched = 0;
for (const probabilityBucket of probabilities) {
const temp = getBucketTemp(probabilityBucket);
if (!marketBucketContainsDisplayTemp(bucket, temp, detail)) continue;
const probability = Number(probabilityBucket.probability);
if (!Number.isFinite(probability)) continue;
total += probability;
matched += 1;
}
return matched > 0 ? Math.max(0, Math.min(1, total)) : null;
}
type ModelMetadata = NonNullable<
NonNullable<CityDetail["source_forecasts"]>["open_meteo_multi_model"]
>["model_metadata"];
@@ -712,7 +805,7 @@ export function ProbabilityDistribution({
)[0];
const topProbabilityText = toPercent(topProbability?.probability);
const topProbabilityLabel = topProbability
? topProbability.label || `${topProbability.value}${detail.temp_symbol}`
? formatBucketDisplayLabel(topProbability, detail)
: null;
const topProbabilityTemp = topProbability ? getBucketTemp(topProbability) : null;
const linkedMarketBucket = useMemo(() => {
@@ -732,8 +825,17 @@ export function ProbabilityDistribution({
yesPriceView?.ask ??
null;
const linkedNoAsk = linkedMarketBucket?.no_buy ?? noPriceView?.ask ?? null;
const linkedContractLabel =
linkedMarketBucket?.label || topProbabilityLabel || null;
const aggregatedMarketProbability = getAggregatedModelProbabilityForMarketBucket(
view.probabilities || [],
linkedMarketBucket,
detail,
);
const linkedMarketProbability =
topProbability?.probability != null ? Number(topProbability.probability) : null;
aggregatedMarketProbability ??
(topProbability?.probability != null ? Number(topProbability.probability) : null);
const linkedMarketProbabilityText = toPercent(linkedMarketProbability);
const linkedMarketEdge =
linkedMarketProbability != null && linkedMarketAsk != null
? linkedMarketProbability - Number(linkedMarketAsk)
@@ -897,8 +999,8 @@ export function ProbabilityDistribution({
: "未匹配到活跃盘口"
: linkedMarketBucket
? locale === "en-US"
? `${actionText}: ${topProbabilityLabel || "top bucket"}`
: `${actionText}${topProbabilityLabel || "最高概率桶"}`
? `${actionText}: ${linkedContractLabel || "contract bucket"}`
: `${actionText}${linkedContractLabel || "合约桶"}`
: preferredPriceView?.edge != null
? locale === "en-US"
? `${actionText} · edge ${formatSignedPercent(preferredPriceView.edge)}`
@@ -910,9 +1012,9 @@ export function ProbabilityDistribution({
</div>
<div className="prob-price-grid">
<div>
<span>{locale === "en-US" ? "Model bucket" : "概率主桶"}</span>
<strong>{topProbabilityLabel || "--"}</strong>
<em>{topProbabilityText || "--"}</em>
<span>{locale === "en-US" ? "Contract bucket" : "合约桶口径"}</span>
<strong>{linkedContractLabel || topProbabilityLabel || "--"}</strong>
<em>{linkedMarketProbabilityText || topProbabilityText || "--"}</em>
</div>
<div>
<span>{locale === "en-US" ? "Candidate" : "可关注"}</span>
@@ -979,18 +1081,7 @@ export function ProbabilityDistribution({
? `Market ref: ${yesPriceText || "--"}`
: `市场参考: ${yesPriceText || "--"}`
: null;
let bucketLabel =
bucket.label || `${bucket.value}${detail.temp_symbol}`;
if (bucketLabel) {
let str = String(bucketLabel).toUpperCase().replace(/\s+/g, "");
const symbol = detail.temp_symbol || "°C";
str = str.replace(/°?C($|\+|-)/g, "°C$1");
str = str.replace(/°?F($|\+|-)/g, "°F$1");
if (!/[°℃][CF]/.test(str) && /[0-9]/.test(str)) {
str += symbol;
}
bucketLabel = str;
}
const bucketLabel = formatBucketDisplayLabel(bucket, detail);
return (
<div