Reorganize intraday signal cards for clearer trade guidance

This commit is contained in:
2569718930@qq.com
2026-03-24 01:49:00 +08:00
parent 58a09fe6fe
commit 5796d380c5
5 changed files with 306 additions and 133 deletions
@@ -2056,6 +2056,27 @@
letter-spacing: 0.02em;
}
.root :global(.future-v2-stack) {
margin-top: 14px;
display: grid;
gap: 12px;
}
.root :global(.future-v2-subpanel) {
border: 1px solid var(--border-subtle);
border-radius: 12px;
background: rgba(255, 255, 255, 0.025);
padding: 12px;
}
.root :global(.future-v2-subpanel-title) {
margin: 0;
color: var(--text-primary);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.02em;
}
.root :global(.future-v2-market-v3) {
margin-top: 16px;
display: flex;
@@ -2308,6 +2329,13 @@
line-height: 1.45;
}
.root :global(.future-subsection-title) {
color: var(--text-primary);
font-size: 0.95rem;
font-weight: 700;
margin-top: 18px;
}
.root :global(.future-trend-value.warm) {
color: #34d399;
}
@@ -2444,6 +2472,10 @@
padding: 10px;
}
.root :global(.future-v2-subpanel) {
padding: 10px;
}
.root :global(.future-trend-value) {
font-size: 16px;
}
@@ -2454,6 +2486,11 @@
line-height: 1.6;
}
.root :global(.future-subsection-title) {
font-size: 0.9rem;
margin-top: 14px;
}
.root :global(.future-front-meta) {
gap: 8px;
}
@@ -828,6 +828,76 @@ export function FutureForecastModal() {
<strong>{marketSignal}</strong>
</div>
</section>
<section className="future-v2-card">
<h4 className="future-v2-card-title">
{locale === "en-US"
? "City Risk Profile & Airport Narrative"
: "城市风险档案与机场报文解读"}
</h4>
<div className="future-v2-stack">
<div className="future-v2-subpanel">
<h5 className="future-v2-subpanel-title">
{locale === "en-US" ? "City Risk Profile" : "城市风险档案"}
</h5>
<div className="risk-info" style={{ marginTop: "10px" }}>
{!risk.airport ? (
<span style={{ color: "var(--text-muted)" }}>
{t("section.noRiskProfile")}
</span>
) : (
<>
<div className="risk-row">
<span className="risk-label">
{settlementProfileLabel}
</span>
<span>{settlementProfileValue}</span>
</div>
<div className="risk-row">
<span className="risk-label">
{t("section.distance")}
</span>
<span>{risk.distance_km ?? "--"}km</span>
</div>
{risk.warning ? (
<div className="risk-row">
<span className="risk-label">
{t("section.note")}
</span>
<span>{risk.warning}</span>
</div>
) : null}
</>
)}
</div>
</div>
<div className="future-v2-subpanel">
<h5 className="future-v2-subpanel-title">
{locale === "en-US" ? "Airport Narrative" : "机场报文解读"}
</h5>
<div className="ai-box" style={{ marginTop: "10px" }}>
{!ai.summary && ai.bullets.length === 0 ? (
<span className="ai-placeholder">
{t("future.noAi")}
</span>
) : (
<>
{ai.summary ? (
<div className="ai-summary">{ai.summary}</div>
) : null}
{ai.bullets.length > 0 ? (
<ul className="ai-list">
{ai.bullets.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
) : null}
</>
)}
</div>
</div>
</div>
</section>
</aside>
<main className="future-v2-right">
@@ -915,6 +985,9 @@ export function FutureForecastModal() {
</div>
) : null}
</div>
<div className="future-subsection-title">
{locale === "en-US" ? "Surface Structure" : "近地面信号"}
</div>
<div className="future-trend-grid">
{view.front.metrics.slice(0, 6).map((metric) => (
<div key={metric.label} className="future-trend-card">
@@ -935,14 +1008,7 @@ export function FutureForecastModal() {
{view.front.upperAirSummary ||
(view.front.upperAirMetrics?.length || 0) > 0 ? (
<>
<div
style={{
color: "var(--text-primary)",
fontSize: "0.95rem",
fontWeight: 700,
marginTop: "18px",
}}
>
<div className="future-subsection-title">
{locale === "en-US" ? "Upper-Air Structure" : "高空结构信号"}
</div>
{view.front.upperAirSummary ? (
@@ -970,74 +1036,6 @@ export function FutureForecastModal() {
</>
) : null}
</section>
<section className="future-modal-section">
<h3>
{locale === "en-US"
? "City Risk Profile & Airport Narrative"
: "城市风险档案与机场报文解读"}
</h3>
<div className="future-modal-grid">
<div>
<h4 className="future-v2-card-title">
{locale === "en-US" ? "City Risk Profile" : "城市风险档案"}
</h4>
<div className="risk-info" style={{ marginTop: "10px" }}>
{!risk.airport ? (
<span style={{ color: "var(--text-muted)" }}>
{t("section.noRiskProfile")}
</span>
) : (
<>
<div className="risk-row">
<span className="risk-label">
{settlementProfileLabel}
</span>
<span>{settlementProfileValue}</span>
</div>
<div className="risk-row">
<span className="risk-label">
{t("section.distance")}
</span>
<span>{risk.distance_km ?? "--"}km</span>
</div>
{risk.warning ? (
<div className="risk-row">
<span className="risk-label">
{t("section.note")}
</span>
<span>{risk.warning}</span>
</div>
) : null}
</>
)}
</div>
</div>
<div>
<h4 className="future-v2-card-title">{t("future.ai")}</h4>
<div className="ai-box" style={{ marginTop: "10px" }}>
{!ai.summary && ai.bullets.length === 0 ? (
<span className="ai-placeholder">
{t("future.noAi")}
</span>
) : (
<>
{ai.summary ? (
<div className="ai-summary">{ai.summary}</div>
) : null}
{ai.bullets.length > 0 ? (
<ul className="ai-list">
{ai.bullets.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
) : null}
</>
)}
</div>
</div>
</div>
</section>
</main>
</div>
) : (
+2
View File
@@ -317,6 +317,8 @@ export interface CityDetail {
trigger_risk?: string | null;
mixing_strength?: string | null;
shear_risk?: string | null;
heating_setup?: string | null;
heating_score?: number | null;
summary_zh?: string | null;
summary_en?: string | null;
};
+76 -44
View File
@@ -580,17 +580,77 @@ export function computeFrontTrendSignal(
locale: Locale = "zh-CN",
) {
const upperAirSignal = detail.vertical_profile_signal || {};
const upperAirSummary = upperAirSignal.source
? (() => {
const hasMetrics =
upperAirSignal.cape_max != null ||
upperAirSignal.cin_min != null ||
upperAirSignal.boundary_layer_height_max != null ||
upperAirSignal.shear_10m_180m_max != null;
if (!hasMetrics) {
return isEnglish(locale)
? "Upper-air inputs are incomplete. For now, trade direction should rely more on surface structure."
: "高空输入还不完整,当前交易方向先更多参考近地面结构信号。";
}
if (upperAirSignal.heating_setup === "supportive") {
return isEnglish(locale)
? "Upper-air structure still favors further warming. Leaning too early against higher buckets is risky."
: "高空结构仍偏向继续增温,过早去押更低温区间风险较高。";
}
if (upperAirSignal.heating_setup === "suppressed") {
return isEnglish(locale)
? "Upper-air structure leans toward capping the afternoon high. Chasing higher buckets needs caution."
: "高空结构更偏向压住午后峰值,追更高温区间要更谨慎。";
}
return isEnglish(locale)
? "Upper-air structure is fairly neutral. It does not give a clean edge by itself, so surface trends still decide the trade."
: "高空结构整体偏中性,单看这层不给明确边,交易仍要让近地面走势来定。";
})()
: "";
const upperAirMetrics = upperAirSignal.source
? [
{
label: isEnglish(locale) ? "Convective suppression" : "对流压温风险",
label: isEnglish(locale) ? "Peak setup" : "冲高环境",
note:
upperAirSignal.heating_setup === "supportive"
? isEnglish(locale)
? "Still supportive of more daytime heating. Fading stronger buckets too early is risky."
: "仍偏向白天继续冲高,过早去反着做更高温区间比较危险。"
: upperAirSignal.heating_setup === "suppressed"
? isEnglish(locale)
? "Leans toward capping the afternoon peak. Be careful chasing stronger buckets."
: "更偏向压住午后峰值,追更高温区间要谨慎。"
: isEnglish(locale)
? "Neutral on its own. Surface structure still decides the side."
: "单看这层偏中性,最终还是要看近地面信号站哪边。",
tone:
upperAirSignal.heating_setup === "supportive"
? "warm"
: upperAirSignal.heating_setup === "suppressed"
? "cold"
: "",
value:
upperAirSignal.heating_setup === "supportive"
? isEnglish(locale)
? "Supportive"
: "偏支持"
: upperAirSignal.heating_setup === "suppressed"
? isEnglish(locale)
? "Suppressed"
: "偏压制"
: isEnglish(locale)
? "Neutral"
: "中性",
},
{
label: isEnglish(locale) ? "Peak suppression risk" : "压温风险",
note:
upperAirSignal.cape_max != null || upperAirSignal.cin_min != null
? isEnglish(locale)
? `CAPE max ${Math.round(Number(upperAirSignal.cape_max ?? 0))}, CIN min ${Number(upperAirSignal.cin_min ?? 0).toFixed(0)}.`
: `CAPE 峰值 ${Math.round(Number(upperAirSignal.cape_max ?? 0))}CIN 最低 ${Number(upperAirSignal.cin_min ?? 0).toFixed(0)}`
? `How likely clouds or showers are to cap the high. CAPE ${Math.round(Number(upperAirSignal.cape_max ?? 0))}, CIN ${Number(upperAirSignal.cin_min ?? 0).toFixed(0)}.`
: `看云和阵雨有多大概率把峰值压住。CAPE ${Math.round(Number(upperAirSignal.cape_max ?? 0))}CIN ${Number(upperAirSignal.cin_min ?? 0).toFixed(0)}`
: isEnglish(locale)
? "Derived from the next 48h upper-air profile."
? "Estimated from the next 48h upper-air profile."
: "根据未来 48 小时高空剖面估算。",
tone:
upperAirSignal.suppression_risk === "high"
@@ -612,12 +672,12 @@ export function computeFrontTrendSignal(
: "低",
},
{
label: isEnglish(locale) ? "Trigger setup" : "午后触发性",
label: isEnglish(locale) ? "Afternoon disruption" : "午后扰动",
note:
upperAirSignal.lifted_index_min != null
? isEnglish(locale)
? `Lifted index min ${Number(upperAirSignal.lifted_index_min).toFixed(1)}.`
: `Lifted Index 最低 ${Number(upperAirSignal.lifted_index_min).toFixed(1)}`
? `How easily the afternoon can turn noisy. Lifted Index ${Number(upperAirSignal.lifted_index_min).toFixed(1)}.`
: `看午后是否容易突然起云、起对流,把走势搅乱。Lifted Index ${Number(upperAirSignal.lifted_index_min).toFixed(1)}`
: isEnglish(locale)
? "Uses instability and lifted-index structure."
: "结合不稳定能量与抬升指数判断。",
@@ -641,15 +701,15 @@ export function computeFrontTrendSignal(
: "低",
},
{
label: isEnglish(locale) ? "Deep mixing" : "深层混合",
label: isEnglish(locale) ? "Heating efficiency" : "冲高效率",
note:
upperAirSignal.boundary_layer_height_max != null
? isEnglish(locale)
? `Boundary-layer height peaks near ${Math.round(Number(upperAirSignal.boundary_layer_height_max))} m.`
: `边界层高度峰值约 ${Math.round(Number(upperAirSignal.boundary_layer_height_max))} 米。`
? `How efficiently surface warmth can keep translating upward. Mixing depth peaks near ${Math.round(Number(upperAirSignal.boundary_layer_height_max))} m.`
: `看地面热量能不能持续往上送,决定冲高效率。混合层高度峰值约 ${Math.round(Number(upperAirSignal.boundary_layer_height_max))} 米。`
: isEnglish(locale)
? "Tracks daytime boundary-layer depth."
: "跟踪白天边界层混合深度。",
? "Tracks daytime mixing depth."
: "跟踪白天混合深度。",
tone:
upperAirSignal.mixing_strength === "strong"
? "warm"
@@ -666,32 +726,8 @@ export function computeFrontTrendSignal(
? "Medium"
: "中"
: isEnglish(locale)
? "Weak"
: "弱",
},
{
label: isEnglish(locale) ? "Shear proxy" : "高空风切变",
note:
upperAirSignal.shear_10m_180m_max != null
? isEnglish(locale)
? `10m-180m shear proxy peaks near ${Number(upperAirSignal.shear_10m_180m_max).toFixed(1)}.`
: `10m-180m 风切变代理峰值约 ${Number(upperAirSignal.shear_10m_180m_max).toFixed(1)}`
: isEnglish(locale)
? "Uses 10m vs 180m wind-vector spread as a simple proxy."
: "使用 10m 与 180m 风矢量差做简化代理。",
tone: upperAirSignal.shear_risk === "high" ? "cold" : "",
value:
upperAirSignal.shear_risk === "high"
? isEnglish(locale)
? "High"
: "高"
: upperAirSignal.shear_risk === "medium"
? isEnglish(locale)
? "Medium"
: "中"
: isEnglish(locale)
? "Low"
: "低",
? "Weak"
: "弱",
},
]
: [];
@@ -719,9 +755,7 @@ export function computeFrontTrendSignal(
value: string;
}>,
upperAirMetrics,
upperAirSummary: isEnglish(locale)
? String(upperAirSignal.summary_en || "").trim()
: String(upperAirSignal.summary_zh || "").trim(),
upperAirSummary,
precipMax: 0,
score: 0,
summary:
@@ -1204,9 +1238,7 @@ export function computeFrontTrendSignal(
label,
metrics,
upperAirMetrics,
upperAirSummary: isEnglish(locale)
? String(upperAirSignal.summary_en || "").trim()
: String(upperAirSignal.summary_zh || "").trim(),
upperAirSummary,
precipMax,
score,
summary: backendSummary || summary,