diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 9daf5448..fe24ccbf 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -20,7 +20,7 @@ const jetbrainsMono = JetBrains_Mono({ export const metadata: Metadata = { title: "PolyWeather | Weather Intelligence", description: - "PolyWeather paid professional terminal for weather-market intelligence, city decision cards, and subscription-only analytics.", + "PolyWeather paid professional terminal for weather-signal intelligence, city decision cards, and subscription-only analytics.", manifest: "/site.webmanifest", icons: { icon: [ diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index af7fd8f8..f293cb9c 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -3,9 +3,9 @@ import { redirect } from "next/navigation"; import { InstitutionalLandingPage } from "@/components/landing/InstitutionalLandingPage"; export const metadata: Metadata = { - title: "PolyWeather | Institutional Weather Market Intelligence", + title: "PolyWeather | Institutional Weather Signal Intelligence", description: - "PolyWeather is a paid professional weather-market intelligence terminal with METAR evidence, DEB forecast blending, and AI decision cards.", + "PolyWeather is a paid professional weather-signal intelligence terminal with METAR evidence, DEB forecast blending, and AI decision cards.", }; export default async function HomePage({ diff --git a/frontend/app/terminal/page.tsx b/frontend/app/terminal/page.tsx index 2ace10f9..6ca280d9 100644 --- a/frontend/app/terminal/page.tsx +++ b/frontend/app/terminal/page.tsx @@ -4,7 +4,7 @@ import { ScanTerminalDashboard } from "@/components/dashboard/ScanTerminalDashbo export const metadata: Metadata = { title: "PolyWeather Terminal | Paid Product", description: - "Paid PolyWeather decision terminal for weather-market analysis and city decision cards.", + "Paid PolyWeather decision terminal for weather-signal analysis and city decision cards.", }; export default function TerminalPage() { diff --git a/frontend/components/auth/LoginClient.tsx b/frontend/components/auth/LoginClient.tsx index 9b47cb76..f4590c1d 100644 --- a/frontend/components/auth/LoginClient.tsx +++ b/frontend/components/auth/LoginClient.tsx @@ -105,8 +105,8 @@ export function LoginClient({ nextPath, initialMode }: LoginClientProps) { ? "By proceeding, you agree to the Privacy Policy and Terms & Conditions." : "继续操作即代表您同意隐私政策与服务条款。", desc: isEn - ? "Access robust METAR observations, advanced DEB forecast blends, and real-time AI decision cards that bring clarity to your weather-market portfolios." - : "提供精准的机场 METAR 实况、先进的 DEB 智能融合预测和实时 AI 决策卡片,助您看清天气市场脉络。", + ? "Access robust METAR observations, advanced DEB forecast blends, and real-time AI decision cards that bring clarity to your weather-signal portfolios." + : "提供精准的机场 METAR 实况、先进的 DEB 智能融合预测和实时 AI 决策卡片,助您看清天气信号脉络。", trusted: isEn ? "Trusted by institutional traders" : "深受机构交易员信赖", } as const; diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index e2dce062..37124103 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -82,18 +82,18 @@ function createLocalAccess(): ProAccessState { const TERM = { - cityContract: { en: "City / Contract", zh: "城市 / 合约" }, + cityThreshold: { en: "City / Threshold", zh: "城市 / 阈值" }, live: { en: "Live", zh: "实测" }, deb: { en: "DEB", zh: "DEB" }, - mkt: { en: "Mkt", zh: "市场" }, + mkt: { en: "Mkt", zh: "信号" }, edge: { en: "Edge", zh: "优势" }, liq: { en: "Liq", zh: "流动性" }, signal: { en: "Signal", zh: "信号" }, - searchPlaceholder: { en: "Search city, contract, station, or signal", zh: "搜索城市、合约、站点或信号" }, - weatherContracts: { en: "Weather Contracts", zh: "天气合约" }, - selectedContractMonitor: { en: "Selected Contract Monitor", zh: "选中合约监控" }, + searchPlaceholder: { en: "Search city, threshold, station, or signal", zh: "搜索城市、阈值、站点或信号" }, + weatherThresholds: { en: "Weather Thresholds", zh: "天气阈值" }, + selectedThresholdMonitor: { en: "Selected Threshold Monitor", zh: "选中阈值监控" }, probabilityDistribution: { en: "Probability Distribution", zh: "概率分布" }, - marketList: { en: "Market List", zh: "市场列表" }, + signalList: { en: "Signal List", zh: "信号列表" }, watchlist: { en: "Watchlist", zh: "观察列表" }, rows: { en: "Rows", zh: "行数" }, avgEdge: { en: "Avg Edge", zh: "平均优势" }, @@ -103,9 +103,9 @@ const TERM = { model: { en: "Model", zh: "模型" }, noData: { en: "No data", zh: "无数据" }, noDistributionData: { en: "No distribution data", zh: "无分布数据" }, - selectContract: { - en: "Select a weather contract to inspect model edge, market price, and live evidence.", - zh: "选择天气合约以查看模型优势、市场价格和实况证据。", + selectThreshold: { + en: "Select a weather threshold to inspect model edge, signal price, and live evidence.", + zh: "选择天气阈值以查看模型优势、信号价格和实况证据。", }, signInToContinue: { en: "Sign in to continue", zh: "请先登录" }, signInHint: { @@ -117,8 +117,8 @@ const TERM = { learnAbout: { en: "Learn about PolyWeather", zh: "了解 PolyWeather" }, proAccessRequired: { en: "Pro Access Required", zh: "需要付费订阅" }, proDesc: { - en: "The PolyWeather terminal is a paid product. Subscribe to unlock real-time weather-market intelligence.", - zh: "PolyWeather 决策台为付费产品。订阅以解锁实时天气市场情报。", + en: "The PolyWeather terminal is a paid product. Subscribe to unlock real-time weather-signal intelligence.", + zh: "PolyWeather 决策台为付费产品。订阅以解锁实时天气信号情报。", }, subscriptionTerms: { en: "Billed monthly. Cancel anytime. Payment via USDC on Polygon.", @@ -131,7 +131,7 @@ const TERM = { zh: "你需要开通有效订阅才能访问决策台。", }, backToProduct: { en: "Back to product overview", zh: "返回产品介绍页" }, - dashboard: { en: "PolyWeather Terminal", zh: "PolyWeather 交易决策台" }, + dashboard: { en: "PolyWeather Terminal", zh: "PolyWeather 天气决策台" }, refresh: { en: "Refresh", zh: "刷新" }, switchLang: { en: "Switch to Chinese", zh: "切换到英文" }, globalWeatherFactors: { en: "Global Weather Factors", zh: "全球天气因子" }, @@ -356,7 +356,7 @@ function PolyWeatherTerminal({ return () => window.removeEventListener("keydown", handleKeyDown); }, [searchInputRef, setSearchQuery]); const [navExpanded, setNavExpanded] = useState(false); - const [activeNavKey, setActiveNavKey] = useState("contracts"); + const [activeNavKey, setActiveNavKey] = useState("thresholds"); const [gridCols, setGridCols] = useState(() => { if (typeof window !== "undefined") { @@ -473,7 +473,7 @@ function PolyWeatherTerminal({ }; const NAV_ITEMS = [ - { key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" }, + { key: "thresholds", Icon: Table2, labelEn: "Thresholds", labelZh: "天气阈值" }, { key: "training", Icon: GraduationCap, labelEn: "Training", labelZh: "训练数据" }, ]; diff --git a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx index a524646f..79589cee 100644 --- a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx +++ b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx @@ -51,6 +51,21 @@ function pairKey(pair: [string, string]) { return pair.map(normalizeRunwayLabel).sort().join("/"); } +function runwaySeriesKey(rwy: string) { + return `runway_${String(rwy || "unknown") + .split("/") + .map(normalizeRunwayLabel) + .filter(Boolean) + .join("_")}`; +} + +function isTemperatureSeriesVisibleByDefault(city: string, seriesKey: string) { + if (seriesKey.startsWith("model_curve_")) { + return normalizeCityKey(city) === "paris" && seriesKey === "model_curve_AROME HD"; + } + return true; +} + function buildRunwayPlates( amos: AmosData | null | undefined, row: ScanOpportunityRow | null, @@ -289,6 +304,8 @@ type HourlyForecast = { airportPrimary?: AirportCurrentConditions | null; forecastDaily?: ForecastDay[]; multiModelDaily?: Record; + settlementTodayObs?: ObsPoint[]; + metarTodayObs?: ObsPoint[]; } | null; function parseRunwayHistoryValue(point: Record) { @@ -335,7 +352,7 @@ function buildRunwayHistorySeries( .slice(-MAX_OBS_POINTS); const isSettlement = isSettlementRunway(row, normalizedRwy); return { - key: `runway_${index}`, + key: runwaySeriesKey(normalizedRwy), label: `${normalizedRwy}${isSettlement ? (row ? " 结算跑道" : " Settlement") : ""}`, rwy: normalizedRwy, isSettlement, @@ -374,7 +391,7 @@ function buildRunwayHistorySeries( .filter((point): point is { ts: number; value: number } => point !== null); if (values.length <= 1) return null; return { - key: `runway_${index}`, + key: runwaySeriesKey(rwy), label: `${rwy}${isSettlement ? " 结算跑道" : ""}`, rwy, isSettlement, @@ -605,8 +622,8 @@ function buildFullDayChartData( const tzOffset = row?.tz_offset_seconds ?? 0; const localDateStr = row?.local_date || new Date().toISOString().slice(0, 10); - const settlementObs = normObs(row?.settlement_today_obs || row?.metar_context?.settlement_today_obs, tzOffset); - const metarObs = normObs(row?.metar_today_obs || row?.metar_context?.today_obs || row?.metar_recent_obs || row?.metar_context?.recent_obs, tzOffset); + const settlementObs = normObs(hourly?.settlementTodayObs || row?.settlement_today_obs || row?.metar_context?.settlement_today_obs, tzOffset); + const metarObs = normObs(hourly?.metarTodayObs || row?.metar_today_obs || row?.metar_context?.today_obs || row?.metar_recent_obs || row?.metar_context?.recent_obs, tzOffset); const runwayHistorySeries = buildRunwayHistorySeries(row, hourly, tzOffset, localDateStr); const slots = generateFullDaySlots(localDateStr); @@ -633,7 +650,7 @@ function buildFullDayChartData( }); // ── Settlement observations ── - if (!runwayHistorySeries.length && settlementObs.length) { + if (settlementObs.length) { const svals = binObservationsToSlots(slots, settlementObs); if (svals.some((v) => v !== null)) { series.push({ @@ -653,7 +670,7 @@ function buildFullDayChartData( if (mvals.some((v) => v !== null)) { series.push({ key: "metar", - label: "METAR", + label: row?.metar_context?.station_label || "METAR", source: row?.airport || "METAR", color: "#0ea5e9", dashed: true, @@ -888,6 +905,8 @@ export function LiveTemperatureThresholdChart({ airportPrimary: json.airport_primary || null, forecastDaily: json.forecast?.daily || [], multiModelDaily: json.multi_model_daily || {}, + settlementTodayObs: (json as any).timeseries?.settlement_today_obs || (json as any)?.settlement_today_obs || undefined, + metarTodayObs: (json as any).timeseries?.metar_today_obs || (json as any)?.metar_today_obs || undefined, }; _hourlyCache.set(city, { ts: Date.now(), data }); setHourly(data); @@ -905,8 +924,15 @@ export function LiveTemperatureThresholdChart({ const tzOffset = row?.tz_offset_seconds ?? 0; const settlementObs = useMemo(() => { - return normObs(row?.settlement_today_obs || row?.metar_context?.settlement_today_obs, tzOffset); - }, [row, tzOffset]); + let obs = normObs(hourly?.settlementTodayObs || row?.settlement_today_obs || row?.metar_context?.settlement_today_obs, tzOffset); + if (!obs.length && !hourly?.runwayPlateHistory) { + const mObs = normObs(hourly?.metarTodayObs || row?.metar_today_obs || row?.metar_context?.today_obs, tzOffset); + if (mObs.length > 0) { + obs = mObs; + } + } + return obs; + }, [row, hourly, tzOffset]); const runwayPlates = useMemo(() => buildRunwayPlates(hourly?.amos, row, settlementObs), [hourly?.amos, row, settlementObs]); const hasRunwayData = runwayPlates.length > 0; @@ -920,13 +946,7 @@ export function LiveTemperatureThresholdChart({ if (userToggledKeys[sKey] !== undefined) { return userToggledKeys[sKey]; } - if (sKey.startsWith("model_curve_")) { - if (city === "paris" && sKey === "model_curve_AROME HD") { - return true; - } - return false; - } - return true; + return isTemperatureSeriesVisibleByDefault(city, sKey); }; const activeSeries = useMemo(() => { @@ -1436,3 +1456,13 @@ export function LiveTemperatureThresholdChart({ ); } + +export function __buildTemperatureChartDataForTest( + row: ScanOpportunityRow | null, + hourly: HourlyForecast, + timeframe: "1D" | "3D" = "1D", +) { + return timeframe === "3D" ? build3DayChartData(row, hourly) : buildFullDayChartData(row, hourly); +} + +export const __isTemperatureSeriesVisibleByDefaultForTest = isTemperatureSeriesVisibleByDefault; diff --git a/frontend/components/dashboard/scan-terminal/ProductAccessRequired.tsx b/frontend/components/dashboard/scan-terminal/ProductAccessRequired.tsx index 454be7f3..aa6cf5d6 100644 --- a/frontend/components/dashboard/scan-terminal/ProductAccessRequired.tsx +++ b/frontend/components/dashboard/scan-terminal/ProductAccessRequired.tsx @@ -22,13 +22,13 @@ function SubscriptionGate({ isEn }: { isEn: boolean }) { "Real-time METAR observations across 500+ stations", "DEB forecast blends with 0–240h horizon", "AI decision cards with Poly-score ranking", - "Historical backtesting & weather market signals", + "Historical backtesting & weather signals", ] : [ "500+ 气象站实时 METAR 实况", "DEB 智能融合预测(0–240 小时)", "AI 决策卡片 + Poly-score 排名", - "历史回测与天气市场交易信号", + "历史回测与天气信号", ]; return ( @@ -46,7 +46,7 @@ function SubscriptionGate({ isEn }: { isEn: boolean }) {

{isEn ? "Unlock the Weather Terminal" - : "解锁天气交易决策台"} + : "解锁天气决策台"}

{isEn diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts new file mode 100644 index 00000000..be674986 --- /dev/null +++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts @@ -0,0 +1,110 @@ +import { + __buildTemperatureChartDataForTest, + __isTemperatureSeriesVisibleByDefaultForTest, +} from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart"; + +function assert(condition: unknown, message: string) { + if (!condition) throw new Error(message); +} + +function seriesByKey(series: Array<{ key: string }>, key: string) { + return series.find((item) => item.key === key); +} + +export function runTests() { + const guangzhou = { + city: "guangzhou", + local_date: "2026-05-25", + local_time: "10:00", + tz_offset_seconds: 8 * 60 * 60, + airport: "ZGGG", + deb_prediction: 31, + runway_plate_history: { + "02L/20R": [ + { time: "00:05", temp: 29.1 }, + { time: "00:35", temp: 29.3 }, + ], + "01L/19R": [ + { time: "00:05", temp: 28.7 }, + { time: "00:35", temp: 28.9 }, + ], + }, + settlement_today_obs: [ + { time: "00:05", temp: 29.0 }, + { time: "00:35", temp: 29.2 }, + ], + metar_today_obs: [ + { time: "00:05", temp: 28.0 }, + { time: "00:35", temp: 28.5 }, + ], + } as any; + + const hourly = { + localTime: "10:00", + times: ["00:00", "00:30"], + temps: [29, 30], + modelCurves: { + ECMWF: [30.1, 30.2], + GFS: [29.7, 29.9], + }, + } as any; + + const { series } = __buildTemperatureChartDataForTest(guangzhou, hourly, "1D"); + + const settlementRunway = seriesByKey(series, "runway_02L_20R") as any; + assert(settlementRunway, "settlement runway should use a stable runway-pair key"); + assert(settlementRunway.label.includes("结算跑道"), "settlement runway should be labeled as settlement runway"); + assert(settlementRunway.color === "#009688", "settlement runway should use the highlight cyan color"); + assert(settlementRunway.featured === true, "settlement runway should be featured"); + assert(!settlementRunway.dashed, "settlement runway should be solid"); + + const auxiliaryRunway = seriesByKey(series, "runway_01L_19R") as any; + assert(auxiliaryRunway, "auxiliary runway should be displayed by default in the chart data"); + assert(auxiliaryRunway.dashed === true, "auxiliary runway should be dashed"); + assert(auxiliaryRunway.featured !== true, "auxiliary runway should not be featured"); + + assert(seriesByKey(series, "settlement"), "settlement/HKO observation series should still be present when runway data exists"); + assert(seriesByKey(series, "metar"), "METAR observation series should be present by its own key"); + + assert( + __isTemperatureSeriesVisibleByDefaultForTest("guangzhou", "runway_02L_20R"), + "runway series should be visible by default", + ); + assert( + __isTemperatureSeriesVisibleByDefaultForTest("guangzhou", "settlement"), + "settlement/HKO observations should be visible by default", + ); + assert( + __isTemperatureSeriesVisibleByDefaultForTest("guangzhou", "metar"), + "METAR observations should be visible by default", + ); + assert( + !__isTemperatureSeriesVisibleByDefaultForTest("guangzhou", "model_curve_ECMWF"), + "multi-model curves should be hidden by default", + ); + assert( + __isTemperatureSeriesVisibleByDefaultForTest("paris", "model_curve_AROME HD"), + "Paris AROME HD should be the only default-visible model curve exception", + ); + + const shenzhen = __buildTemperatureChartDataForTest( + { + city: "shenzhen", + local_date: "2026-05-25", + local_time: "10:00", + tz_offset_seconds: 8 * 60 * 60, + metar_context: { + station: "Lau Fau Shan", + station_label: "HKO Lau Fau Shan", + today_obs: [ + { time: "00:05", temp: 28.4 }, + { time: "00:35", temp: 28.5 }, + ], + }, + } as any, + null, + "1D", + ); + assert(seriesByKey(shenzhen.series, "metar"), "Shenzhen/Lau Fau Shan observations should stay as METAR/HKO observations, not runway data"); + assert(!shenzhen.series.some((item) => item.key.startsWith("runway_")), "Shenzhen should not be treated as an AMSC runway city"); +} diff --git a/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts b/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts index 67450caa..dabf8503 100644 --- a/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts +++ b/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts @@ -466,8 +466,8 @@ export function buildMarketDecisionView({ ? "Quote is available, but model probability or YES price is incomplete." : "已获取报价,但模型概率或 YES 价格不完整。" : isEn - ? `Model probability is ${formatMarketPercent(modelProbability)} versus market-implied ${formatMarketPercent(implied)}.${bucketMappingExplanation ? ` ${bucketMappingExplanation}` : ""}` - : `模型概率 ${formatMarketPercent(modelProbability)},市场隐含约 ${formatMarketPercent(implied)}。${bucketMappingExplanation ? ` ${bucketMappingExplanation}` : ""}`, + ? `Model probability is ${formatMarketPercent(modelProbability)} versus signal-implied ${formatMarketPercent(implied)}.${bucketMappingExplanation ? ` ${bucketMappingExplanation}` : ""}` + : `模型概率 ${formatMarketPercent(modelProbability)},信号隐含约 ${formatMarketPercent(implied)}。${bucketMappingExplanation ? ` ${bucketMappingExplanation}` : ""}`, status: "ready", title, tone, diff --git a/frontend/components/landing/InstitutionalLandingPage.tsx b/frontend/components/landing/InstitutionalLandingPage.tsx index 3d25b313..241551be 100644 --- a/frontend/components/landing/InstitutionalLandingPage.tsx +++ b/frontend/components/landing/InstitutionalLandingPage.tsx @@ -41,7 +41,7 @@ const RAW_MARKET_ROWS_ZH = [ const COVERAGE_EN = [ "Live airport observations", "DEB blend forecast", - "Market-implied temperature", + "Signal-implied temperature", "Intraday settlement windows", "AI weather evidence", "Paid Telegram alerts", @@ -50,7 +50,7 @@ const COVERAGE_EN = [ const COVERAGE_ZH = [ "机场实况观测数据", "DEB 智能融合预报", - "市场隐含温度定价", + "信号隐含温度定价", "日内分段结算窗口", "AI 气象证据链解读", "付费电报实时通知", @@ -60,7 +60,7 @@ const PRO_FEATURES_EN = [ "Real-time METAR observations & runway sensor data", "Real-time METAR observations & alerts", "DEB blend forecast model", - "Market-implied temperature pricing", + "Signal-implied temperature pricing", "Intraday settlement windows & risk metrics", "Paid Telegram alerts & Webhook API", "24/7 priority professional support", @@ -70,7 +70,7 @@ const PRO_FEATURES_ZH = [ "实时 METAR 机场实测与跑道传感器数据", "实时 METAR 机场实测与预警", "DEB 智能融合预测模型", - "市场隐含温度定价与估值", + "信号隐含温度定价与估值", "日内结算窗口与风险度量指标", "付费电报群通知与 API 接口推送", "7×24小时专业技术与客服支持", @@ -106,7 +106,7 @@ function InstitutionalLandingScreen() { { icon: Gauge, title: "Decision Workflow", - body: "City cards combine model forecast, current deviation, risk, and target contract context.", + body: "City cards combine model forecast, current deviation, risk, and target threshold context.", }, { icon: ShieldCheck, @@ -123,18 +123,18 @@ function InstitutionalLandingScreen() { { icon: Gauge, title: "决策工作流", - body: "城市决策卡片融合了气象预报、实况偏差、历史风险系数及目标合约盘口。", + body: "城市决策卡片融合了气象预报、实况偏差、历史风险系数及目标阈值盘口。", }, { icon: ShieldCheck, title: "付费准入", - body: "除公开介绍和账户管理外,气象交易决策台仅向付费活跃订阅用户开放。", + body: "除公开介绍和账户管理外,气象决策台仅向付费活跃订阅用户开放。", }, ]; const modelLabels = isEn - ? ["DEB Blend", "Live METAR", "Market Implied"] - : ["DEB 融合预测", "METAR 机场实测", "市场隐含价格"]; + ? ["DEB Blend", "Live METAR", "Signal Implied"] + : ["DEB 融合预测", "METAR 机场实测", "信号隐含价格"]; return (

@@ -220,12 +220,12 @@ function InstitutionalLandingScreen() { {isEn ? "Paid professional terminal" : "付费专业交易终端"}

- {isEn ? "Institutional weather market intelligence for paid users." : "面向付费用户的机构级天气交易决策台"} + {isEn ? "Institutional weather signal intelligence for paid users." : "面向付费用户的机构级天气决策台"}

{isEn - ? "PolyWeather turns live METAR observations, DEB forecast blends, model probabilities, and market settlement logic into one professional decision workspace." - : "PolyWeather 将 METAR 机场实测、DEB 智能融合预报、模型概率及市场结算逻辑整合于一体,打造气象风险管理专业决策环境。"} + ? "PolyWeather turns live METAR observations, DEB forecast blends, model probabilities, and signal settlement logic into one professional decision workspace." + : "PolyWeather 将 METAR 机场实测、DEB 智能融合预报、模型概率及信号结算逻辑整合于一体,打造气象风险管理专业决策环境。"}

- {isEn ? "Weather Markets Dashboard" : "天气市场交易面板"} + {isEn ? "Weather Signals Dashboard" : "天气信号交易面板"}
@@ -263,7 +263,7 @@ function InstitutionalLandingScreen() {
- {isEn ? "Temperature Contracts" : "温度天气合约"} + {isEn ? "Temperature Thresholds" : "温度天气阈值"} {isEn ? "Price / Edge / Signal" : "价格 / 偏差 / 信号"} @@ -325,8 +325,8 @@ function InstitutionalLandingScreen() {

{isEn - ? "New York high-temperature market shows a positive observation deviation with confirmed airport evidence." - : "纽约高温合约市场出现显著的正向观测偏差,机场天气实况已验证确认。"} + ? "New York high-temperature signal shows a positive observation deviation with confirmed airport evidence." + : "纽约高温阈值信号出现显著的正向观测偏差,机场天气实况已验证确认。"}

@@ -356,7 +356,7 @@ function InstitutionalLandingScreen() { {isEn ? "Data Coverage" : "数据覆盖范围"}

- {isEn ? "Everything weather-market users need in one place." : "天气市场交易者所需的一切,在此集结。"} + {isEn ? "Everything weather-signal users need in one place." : "天气信号交易者所需的一切,在此集结。"}

@@ -404,8 +404,8 @@ function InstitutionalLandingScreen() {

{isEn - ? "Full access to the institutional weather-market terminal. Live METAR, DEB forecasts, probability distribution, AI decision cards, and real-time alerts." - : "完整访问机构级天气市场终端。实时 METAR、DEB 预报、概率分布、AI 决策卡片、实时通知。"} + ? "Full access to the institutional weather-signal terminal. Live METAR, DEB forecasts, probability distribution, AI decision cards, and real-time alerts." + : "完整访问机构级天气信号终端。实时 METAR、DEB 预报、概率分布、AI 决策卡片、实时通知。"}

diff --git a/frontend/content/docs/docs.ts b/frontend/content/docs/docs.ts index e781170b..4ac31e11 100644 --- a/frontend/content/docs/docs.ts +++ b/frontend/content/docs/docs.ts @@ -202,11 +202,11 @@ export const DOCS_PAGES: DocsPage[] = [ }, { id: "market-layer", - title: "市场价格层怎么读", + title: "信号价格层怎么读", blocks: [ - { type: "paragraph", text: "温度桶标签来自完整市场桶列表,并会按 label / slug / question 识别 exact、or higher、or lower、range 等方向,避免把 30.5°C 错配到不合理的 16°C 或反向尾部桶。" }, - { type: "callout", tone: "info", title: "模型-市场差", text: "模型-市场差 = 模型概率 − 市场隐含概率。正数表示天气概率高于市场报价;负数表示市场已经把该 YES 计价得更充分。它不是温度变化,也不是收益率。" }, - { type: "paragraph", text: "YES 买入价以可执行报价为主;没有可靠模型概率或 YES 价格时,决策卡会显示报价已匹配但暂不计算模型-市场差。" }, + { type: "paragraph", text: "温度桶标签来自完整信号桶列表,并会按 label / slug / question 识别 exact、or higher、or lower、range 等方向,避免把 30.5°C 错配到不合理的 16°C 或反向尾部桶。" }, + { type: "callout", tone: "info", title: "模型-信号差", text: "模型-信号差 = 模型概率 − 信号隐含概率。正数表示天气概率高于信号报价;负数表示信号已经把该 YES 计价得更充分。它不是温度变化,也不是收益率。" }, + { type: "paragraph", text: "YES 买入价以可执行报价为主;没有可靠模型概率或 YES 价格时,决策卡会显示报价已匹配但暂不计算模型-信号差。" }, ], }, { @@ -240,11 +240,11 @@ export const DOCS_PAGES: DocsPage[] = [ }, { id: "market-layer", - title: "How to read the market layer", + title: "How to read the signal layer", blocks: [ - { type: "paragraph", text: "Bucket labels come from the full market bucket list. The card reads label / slug / question text to distinguish exact, or-higher, or-lower, and range buckets, so a 30.5°C weather center is not matched to an unreasonable 16°C or reverse-tail bucket." }, - { type: "callout", tone: "info", title: "Model-market difference", text: "Model-market difference = model probability minus market-implied probability. A positive value means the weather probability is above market pricing; a negative value means the YES is already priced more fully by the market. It is not a temperature delta or return." }, - { type: "paragraph", text: "YES buy uses executable quote data when available. If either model probability or YES price is incomplete, the card shows the quote match but withholds the model-market difference." }, + { type: "paragraph", text: "Bucket labels come from the full signal bucket list. The card reads label / slug / question text to distinguish exact, or-higher, or-lower, and range buckets, so a 30.5°C weather center is not matched to an unreasonable 16°C or reverse-tail bucket." }, + { type: "callout", tone: "info", title: "Model-signal difference", text: "Model-signal difference = model probability minus signal-implied probability. A positive value means the weather probability is above signal pricing; a negative value means the YES is already priced more fully by the signal. It is not a temperature delta or return." }, + { type: "paragraph", text: "YES buy uses executable quote data when available. If either model probability or YES price is incomplete, the card shows the quote match but withholds the model-signal difference." }, ], }, {