删除 AI 预报系统全部死代码:11 个文件,扫描客户端 AI 流、状态管理、类型定义

This commit is contained in:
2569718930@qq.com
2026-05-25 22:27:02 +08:00
parent 9291ab9b3d
commit 5d0d1e2673
17 changed files with 20 additions and 2033 deletions
@@ -1,169 +0,0 @@
import assert from "node:assert/strict";
import {
buildAiCityErrorForecastState,
buildAiCityForecastCacheKey,
buildAiCityForecastKey,
buildAiCityProgressForecastState,
buildAiCityReadyForecastState,
readReadyCachedAiForecastState,
} from "@/components/dashboard/scan-terminal/ai-city-forecast-stream-state";
import { readCachedPayload, writeCachedPayload } from "@/components/dashboard/scan-terminal/scan-terminal-cache";
import type { AiCityForecastPayload, AiCityForecastState } from "@/components/dashboard/scan-terminal/types";
import type { CityDetail } from "@/lib/dashboard-types";
function installLocalStorageMock() {
const store = new Map<string, string>();
const localStorage = {
clear: () => store.clear(),
getItem: (key: string) => store.get(key) ?? null,
removeItem: (key: string) => {
store.delete(key);
},
setItem: (key: string, value: string) => {
store.set(key, value);
},
};
Object.defineProperty(globalThis, "window", {
configurable: true,
value: { localStorage },
});
return localStorage;
}
function cityDetail(extra: Partial<CityDetail> = {}): CityDetail {
return {
airport_current: {
raw_metar: "METAR TEST 010000Z 34004KT CAVOK 21/10 Q1012",
temp: 21,
},
current: {
temp: 21,
},
local_date: "2026-04-28",
metar_status: {
last_observation_time: "2026-04-28T00:00:00Z",
stale_for_today: false,
},
name: "Test City",
temp_symbol: "°C",
...extra,
} as CityDetail;
}
function readyPayload(extra: Partial<AiCityForecastPayload> = {}): AiCityForecastPayload {
return {
city_forecast: {
confidence: "medium",
final_judgment_en: "Centered near 25°C.",
final_judgment_zh: "预计最高温以 25°C 为中枢。",
metar_read_en: "Latest METAR supports the path.",
metar_read_zh: "最新 METAR 支撑当前路径。",
model_cluster_note_en: "Models are clustered.",
model_cluster_note_zh: "模型较集中。",
predicted_max: 25,
range_high: 26,
range_low: 24,
reasoning_en: "Evidence is aligned.",
reasoning_zh: "证据一致。",
risks_en: [],
risks_zh: [],
unit: "°C",
},
status: "ready",
...extra,
};
}
export function runTests() {
const storage = installLocalStorageMock();
storage.clear();
const forecastKey = buildAiCityForecastKey({
detail: cityDetail(),
detailCityName: "Test City",
locale: "zh-CN",
report: "METAR TEST 010000Z 34004KT CAVOK 21/10 Q1012",
});
const cacheKey = buildAiCityForecastCacheKey(forecastKey);
const payload = readyPayload();
writeCachedPayload(cacheKey, payload);
const cachedReady = readReadyCachedAiForecastState(cacheKey, 0);
assert.equal(cachedReady?.status, "ready");
assert.equal(cachedReady?.payload?.city_forecast?.predicted_max, 25);
const degradedCacheKey = `${cacheKey}:degraded`;
writeCachedPayload(degradedCacheKey, readyPayload({ degraded: true }));
assert.equal(readReadyCachedAiForecastState(degradedCacheKey, 0), null);
assert.equal(readCachedPayload(degradedCacheKey, 60 * 60 * 1000), null);
const currentLoading: AiCityForecastState = {
status: "loading",
streamText: "已有快速判断",
};
const callingAiProgress = buildAiCityProgressForecastState({
cacheKey: `${cacheKey}:progress`,
current: currentLoading,
isEn: false,
progress: {
message_zh: "DeepSeek 正在补充机场报文细节",
stage: "calling_ai",
},
});
assert.equal(callingAiProgress?.streamText, "已有快速判断");
const errorState = buildAiCityErrorForecastState({
cacheKey: `${cacheKey}:error`,
detail: cityDetail(),
error: new Error("timeout"),
isEn: false,
report: "METAR TEST 010000Z 34004KT CAVOK 21/10 Q1012",
});
assert.equal(errorState.status, "ready");
assert.equal(errorState.payload?.status, "timeout_fallback");
assert.match(errorState.payload?.reason_zh || "", /DeepSeek|DEB|METAR/);
const modelFallbackState = buildAiCityErrorForecastState({
cacheKey: `${cacheKey}:models`,
detail: cityDetail({
deb: { prediction: 29 },
multi_model: { ECMWF: 30, GFS: 32, ICON: 31 },
} as unknown as Partial<CityDetail>),
error: new Error("timeout"),
isEn: false,
report: "",
});
assert.equal(modelFallbackState.payload?.city_forecast?.predicted_max, 31);
assert.equal(modelFallbackState.payload?.city_forecast?.range_low, 30);
assert.equal(modelFallbackState.payload?.city_forecast?.range_high, 32);
const hkoState = buildAiCityErrorForecastState({
cacheKey: `${cacheKey}:hko`,
detail: cityDetail({
airport_current: null,
current: {
settlement_source: "hko",
temp: 30,
},
settlement_station: {
settlement_source: "hko",
},
} as unknown as Partial<CityDetail>),
error: new Error("timeout"),
isEn: false,
report: "",
});
const hkoRead = hkoState.payload?.city_forecast?.metar_read_zh || "";
assert.doesNotMatch(hkoRead, /METAR|机场报文/);
assert.match(hkoRead, /官方观测|30\.0°C/);
const degradedReadyState = buildAiCityReadyForecastState({
cacheKey: `${cacheKey}:ready-degraded`,
detail: cityDetail(),
isEn: false,
payload: readyPayload({ degraded: true }),
report: "METAR TEST 010000Z 34004KT CAVOK 21/10 Q1012",
});
assert.equal(degradedReadyState.status, "ready");
assert.equal(readCachedPayload(`${cacheKey}:ready-degraded`, 60 * 60 * 1000), null);
}
@@ -1,30 +0,0 @@
import fs from "node:fs";
import path from "node:path";
function assert(condition: unknown, message: string) {
if (!condition) throw new Error(message);
}
export function runTests() {
const projectRoot = process.cwd();
const clientPath = path.join(
projectRoot,
"components",
"dashboard",
"scan-terminal",
"scan-terminal-client.ts",
);
const source = fs.readFileSync(clientPath, "utf8");
const concurrencyMatch = source.match(/AI_CITY_READ_MAX_CONCURRENT_STREAMS\s*=\s*(\d+)/);
const concurrency = Number(concurrencyMatch?.[1]);
assert(
Number.isFinite(concurrency) && concurrency >= 4,
"AI airport-read streams should allow at least 4 concurrent cards to avoid slow decision-card evidence queues",
);
assert(
source.includes("queuedAiCityReadTasks.unshift(run)") &&
source.includes("queuedAiCityReadTasks.pop()"),
"newer AI airport-read requests should be prioritized instead of waiting behind older pinned cards",
);
}
@@ -1,33 +0,0 @@
import fs from "node:fs";
import path from "node:path";
function assert(condition: unknown, message: string) {
if (!condition) throw new Error(message);
}
export function runTests() {
const projectRoot = process.cwd();
const hookPath = path.join(
projectRoot,
"components",
"dashboard",
"scan-terminal",
"use-ai-city-forecast.ts",
);
const source = fs.readFileSync(hookPath, "utf8");
assert(
source.includes("AI_CITY_READ_SOFT_TIMEOUT_MS") &&
source.includes("softTimeoutId"),
"AI city read hook must use a soft timeout so airport-read loading does not linger",
);
assert(
source.includes("ai_soft_timeout_fallback") &&
source.includes("buildAiCityErrorForecastState"),
"AI city read soft timeout must switch to the fast fallback state while allowing later merge",
);
assert(
!source.includes("controller.abort()"),
"AI city read soft timeout should not abort the stream; late AI results should still merge",
);
}
@@ -1,7 +1,6 @@
import assert from "node:assert/strict";
import { buildCityDecisionState } from "@/components/dashboard/scan-terminal/city-decision-state";
import type { MarketDecisionView } from "@/components/dashboard/scan-terminal/city-card-decision-utils";
import type { AiCityForecastState } from "@/components/dashboard/scan-terminal/types";
function market(status: MarketDecisionView["status"]): MarketDecisionView {
return {
@@ -18,15 +17,8 @@ function market(status: MarketDecisionView["status"]): MarketDecisionView {
};
}
function ai(status: AiCityForecastState["status"], extra: Partial<AiCityForecastState> = {}): AiCityForecastState {
return { status, ...extra };
}
export function runTests() {
const breakout = buildCityDecisionState({
aiCityForecast: null,
aiForecast: ai("ready"),
aiRuleEvidenceMode: false,
isEn: false,
isHkoObservation: false,
modelHighlyConsistent: true,
@@ -45,9 +37,6 @@ export function runTests() {
assert.ok(breakout.badges.some((badge) => badge.label === "实测突破"));
const marketUnavailable = buildCityDecisionState({
aiCityForecast: null,
aiForecast: ai("ready"),
aiRuleEvidenceMode: false,
isEn: false,
isHkoObservation: false,
modelHighlyConsistent: false,
@@ -61,9 +50,6 @@ export function runTests() {
const fallback = buildCityDecisionState({
aiCityForecast: null,
aiForecast: ai("ready", { payload: { degraded: true } }),
aiRuleEvidenceMode: true,
isEn: false,
isHkoObservation: false,
modelHighlyConsistent: false,
@@ -75,14 +61,9 @@ export function runTests() {
peakHasPassed: false,
});
assert.equal(fallback.aiStatus, "fallback");
assert.equal(fallback.aiStatusLabel, "规则证据模式");
assert.notEqual(fallback.aiStatusLabel, "AI 解读已完成");
assert.equal(fallback.recommendation, "watch");
const partialStream = buildCityDecisionState({
aiCityForecast: null,
aiForecast: ai("loading", { streamText: "partial" }),
aiRuleEvidenceMode: false,
isEn: false,
isHkoObservation: false,
modelHighlyConsistent: false,
@@ -94,6 +75,6 @@ export function runTests() {
peakHasPassed: false,
});
assert.equal(partialStream.aiStatus, "deepseek-loading");
assert.equal(partialStream.aiStatusLabel, "快速判断已完成");
assert.equal(partialStream.urgency, "soon");
assert.equal(partialStream.recommendation, "wait");
}
@@ -17,9 +17,6 @@ const readyMarket: MarketDecisionView = {
export function runTests() {
const peakPassed = buildCityDecisionState({
aiCityForecast: null,
aiForecast: { status: "ready" },
aiRuleEvidenceMode: false,
isEn: false,
isHkoObservation: false,
modelHighlyConsistent: true,
@@ -41,9 +38,6 @@ export function runTests() {
);
const staleMetar = buildCityDecisionState({
aiCityForecast: null,
aiForecast: { status: "ready" },
aiRuleEvidenceMode: false,
isEn: false,
isHkoObservation: false,
modelHighlyConsistent: false,