Preserve runway history during chart refresh
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
|||||||
prefersHighFrequencyRunwayResolution,
|
prefersHighFrequencyRunwayResolution,
|
||||||
readCityDetailBatchDiagnostics,
|
readCityDetailBatchDiagnostics,
|
||||||
readSessionCache,
|
readSessionCache,
|
||||||
|
rememberHourlyDetailSnapshot,
|
||||||
selectCompactSecondaryTemp,
|
selectCompactSecondaryTemp,
|
||||||
selectDisplayRunwayTemp,
|
selectDisplayRunwayTemp,
|
||||||
selectInitialHourlyForRowChange,
|
selectInitialHourlyForRowChange,
|
||||||
@@ -743,7 +744,11 @@ export function LiveTemperatureThresholdChart({
|
|||||||
const temp = getLiveTempFromHourly(dataWithCurrentRow);
|
const temp = getLiveTempFromHourly(dataWithCurrentRow);
|
||||||
if (temp !== null) setLiveTemp(temp);
|
if (temp !== null) setLiveTemp(temp);
|
||||||
}
|
}
|
||||||
setHourly((prev) => mergeHourlyWithLiveObservations(dataWithCurrentRow, prev, latestRow));
|
setHourly((prev) => {
|
||||||
|
const mergedHourly = mergeHourlyWithLiveObservations(dataWithCurrentRow, prev, latestRow);
|
||||||
|
rememberHourlyDetailSnapshot(city, targetResolution, mergedHourly);
|
||||||
|
return mergedHourly;
|
||||||
|
});
|
||||||
setDetailError(null);
|
setDetailError(null);
|
||||||
setShowingStaleDetail(false);
|
setShowingStaleDetail(false);
|
||||||
setChartFreshness((prev) => ({
|
setChartFreshness((prev) => ({
|
||||||
@@ -756,7 +761,7 @@ export function LiveTemperatureThresholdChart({
|
|||||||
? "network"
|
? "network"
|
||||||
: prev.detailSource,
|
: prev.detailSource,
|
||||||
}));
|
}));
|
||||||
}, [getLatestRowSnapshot]);
|
}, [city, targetResolution, getLatestRowSnapshot]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!city || !currentRowObservationSignature) return;
|
if (!city || !currentRowObservationSignature) return;
|
||||||
@@ -766,13 +771,17 @@ export function LiveTemperatureThresholdChart({
|
|||||||
const rowSeed = seedHourlyForecastFromRow(row);
|
const rowSeed = seedHourlyForecastFromRow(row);
|
||||||
const temp = getLiveTempFromHourly(rowSeed);
|
const temp = getLiveTempFromHourly(rowSeed);
|
||||||
if (temp !== null) setLiveTemp(temp);
|
if (temp !== null) setLiveTemp(temp);
|
||||||
setHourly((prev) => mergeRowObservationIntoHourly(prev ?? rowSeed, row));
|
setHourly((prev) => {
|
||||||
|
const mergedHourly = mergeRowObservationIntoHourly(prev ?? rowSeed, row);
|
||||||
|
rememberHourlyDetailSnapshot(city, targetResolution, mergedHourly);
|
||||||
|
return mergedHourly;
|
||||||
|
});
|
||||||
setChartFreshness((prev) => ({
|
setChartFreshness((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
rowAppliedAtMs: now,
|
rowAppliedAtMs: now,
|
||||||
rowObservationTime: rowObservationTimeForFreshness(row),
|
rowObservationTime: rowObservationTimeForFreshness(row),
|
||||||
}));
|
}));
|
||||||
}, [city, currentRowObservationSignature, row]);
|
}, [city, currentRowObservationSignature, row, targetResolution]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!city) {
|
if (!city) {
|
||||||
@@ -913,7 +922,11 @@ export function LiveTemperatureThresholdChart({
|
|||||||
lastPatchAtRef.current = patchAppliedAtMs;
|
lastPatchAtRef.current = patchAppliedAtMs;
|
||||||
const tempValue = validNumber(latestPatch.changes.temp);
|
const tempValue = validNumber(latestPatch.changes.temp);
|
||||||
if (tempValue !== null) setLiveTemp(tempValue);
|
if (tempValue !== null) setLiveTemp(tempValue);
|
||||||
setHourly((prev) => mergePatchIntoHourly(prev ?? seedHourlyForecastFromRow(getLatestRowSnapshot()), latestPatch));
|
setHourly((prev) => {
|
||||||
|
const mergedHourly = mergePatchIntoHourly(prev ?? seedHourlyForecastFromRow(getLatestRowSnapshot()), latestPatch);
|
||||||
|
rememberHourlyDetailSnapshot(city, targetResolution, mergedHourly);
|
||||||
|
return mergedHourly;
|
||||||
|
});
|
||||||
setChartFreshness((prev) => ({
|
setChartFreshness((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
ssePatchAppliedAtMs: patchAppliedAtMs,
|
ssePatchAppliedAtMs: patchAppliedAtMs,
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ export async function runTests() {
|
|||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
chartSource.includes("fetchHourlyForecastForCity(city, { ignoreCache: true, resolution: targetResolution })") &&
|
chartSource.includes("fetchHourlyForecastForCity(city, { ignoreCache: true, resolution: targetResolution })") &&
|
||||||
chartSource.includes("setHourly((prev) => mergeHourlyWithLiveObservations(dataWithCurrentRow, prev, latestRow))"),
|
chartSource.includes("const mergedHourly = mergeHourlyWithLiveObservations(dataWithCurrentRow, prev, latestRow)") &&
|
||||||
|
chartSource.includes("return mergedHourly;"),
|
||||||
"visible chart fallback must refresh full city detail at the current chart resolution while preserving newer live observations",
|
"visible chart fallback must refresh full city detail at the current chart resolution while preserving newer live observations",
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
@@ -339,8 +340,15 @@ export async function runTests() {
|
|||||||
successfulHourlyDetailBlock.includes("setDetailError(null)") &&
|
successfulHourlyDetailBlock.includes("setDetailError(null)") &&
|
||||||
successfulHourlyDetailBlock.includes("setShowingStaleDetail(false)") &&
|
successfulHourlyDetailBlock.includes("setShowingStaleDetail(false)") &&
|
||||||
successfulHourlyDetailBlock.includes("getLatestRowSnapshot") &&
|
successfulHourlyDetailBlock.includes("getLatestRowSnapshot") &&
|
||||||
|
successfulHourlyDetailBlock.includes("rememberHourlyDetailSnapshot(city, targetResolution, mergedHourly)") &&
|
||||||
!/\[row\]/.test(successfulHourlyDetailBlock),
|
!/\[row\]/.test(successfulHourlyDetailBlock),
|
||||||
"successful city detail refreshes must read the latest row without depending on the row object",
|
"successful city detail refreshes must read the latest row, persist the merged snapshot, and avoid depending on the row object",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
chartSource.includes("rememberHourlyDetailSnapshot") &&
|
||||||
|
chartSource.includes("const mergedHourly = mergeRowObservationIntoHourly(prev ?? rowSeed, row);") &&
|
||||||
|
chartSource.includes("const mergedHourly = mergePatchIntoHourly(prev ?? seedHourlyForecastFromRow(getLatestRowSnapshot()), latestPatch);"),
|
||||||
|
"live row and SSE patch merges must persist their hourly snapshots so returning to terminal restores runway history immediately",
|
||||||
);
|
);
|
||||||
const coldDetailFetchBlock =
|
const coldDetailFetchBlock =
|
||||||
/useEffect\(\(\) => \{\s*if \(!city\) \{[\s\S]*?fetchHourlyForecastForCity\(city, \{ resolution: targetResolution \}\)[\s\S]*?\n \}, \[([\s\S]*?)\]\);/.exec(chartSource)?.[1] || "";
|
/useEffect\(\(\) => \{\s*if \(!city\) \{[\s\S]*?fetchHourlyForecastForCity\(city, \{ resolution: targetResolution \}\)[\s\S]*?\n \}, \[([\s\S]*?)\]\);/.exec(chartSource)?.[1] || "";
|
||||||
@@ -583,6 +591,74 @@ export async function runTests() {
|
|||||||
__readHourlyCacheEntryForTest(cacheKey, { allowStale: true })?.data?.temps[0] === 21,
|
__readHourlyCacheEntryForTest(cacheKey, { allowStale: true })?.data?.temps[0] === 21,
|
||||||
"stale city detail cache should still be available for immediate chart rendering",
|
"stale city detail cache should still be available for immediate chart rendering",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
clearCityDetailCache();
|
||||||
|
const runwayCacheKey = "chengdu:1m";
|
||||||
|
store.set(
|
||||||
|
`polyweather_city_detail_v1:${runwayCacheKey}`,
|
||||||
|
JSON.stringify({
|
||||||
|
ts: Date.now() - HOURLY_CACHE_TTL_MS - 1000,
|
||||||
|
data: {
|
||||||
|
forecastDaily: [],
|
||||||
|
localDate: "2026-06-15",
|
||||||
|
localTime: "2026-06-15T09:00:00Z",
|
||||||
|
multiModelDaily: {},
|
||||||
|
probabilities: null,
|
||||||
|
temps: [27.1],
|
||||||
|
times: ["09:00"],
|
||||||
|
runwayPlateHistory: {
|
||||||
|
"02L/20R": [{ time: "2026-06-15T09:00:00Z", temp: 27.1 }],
|
||||||
|
},
|
||||||
|
amos: {
|
||||||
|
runway_plate_history: {
|
||||||
|
"02L/20R": [{ time: "2026-06-15T09:00:00Z", temp: 27.1 }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
(globalThis as any).fetch = async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
cities: ["chengdu"],
|
||||||
|
details: {
|
||||||
|
chengdu: {
|
||||||
|
city: "chengdu",
|
||||||
|
local_date: "2026-06-15",
|
||||||
|
local_time: "2026-06-15T09:04:00Z",
|
||||||
|
hourly: {
|
||||||
|
times: ["09:00", "09:04"],
|
||||||
|
temps: [27.1, 27.8],
|
||||||
|
},
|
||||||
|
models_hourly: {
|
||||||
|
times: ["09:00", "09:04"],
|
||||||
|
curves: { ECMWF: [27.0, 27.6] },
|
||||||
|
},
|
||||||
|
deb: {
|
||||||
|
hourly_path: {
|
||||||
|
source: "deb_hourly_consensus",
|
||||||
|
times: ["09:00", "09:04"],
|
||||||
|
temps: [30.1, 30.4],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runway_plate_history: {},
|
||||||
|
amos: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
missing: [],
|
||||||
|
partial: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const forceRefreshedRunwayDetail = await fetchHourlyForecastForCity("chengdu", {
|
||||||
|
ignoreCache: true,
|
||||||
|
resolution: "1m",
|
||||||
|
});
|
||||||
|
assert(
|
||||||
|
forceRefreshedRunwayDetail?.runwayPlateHistory?.["02L/20R"]?.length === 1 &&
|
||||||
|
__readHourlyCacheEntryForTest(runwayCacheKey, { allowStale: true })?.data?.runwayPlateHistory?.["02L/20R"]?.length === 1,
|
||||||
|
"force-refresh chart detail must not overwrite cached runway history when the response omits it",
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
clearCityDetailCache();
|
clearCityDetailCache();
|
||||||
(globalThis as any).window = originalWindow;
|
(globalThis as any).window = originalWindow;
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import {
|
|||||||
mergeHourlyWithLiveObservations,
|
mergeHourlyWithLiveObservations,
|
||||||
mergePatchIntoHourly,
|
mergePatchIntoHourly,
|
||||||
mergeRowObservationIntoHourly,
|
mergeRowObservationIntoHourly,
|
||||||
|
rememberHourlyDetailSnapshot,
|
||||||
selectInitialHourlyForRowChange,
|
selectInitialHourlyForRowChange,
|
||||||
seedHourlyForecastFromRow,
|
seedHourlyForecastFromRow,
|
||||||
|
_hourlyCache,
|
||||||
} from "@/components/dashboard/scan-terminal/temperature-chart-logic";
|
} from "@/components/dashboard/scan-terminal/temperature-chart-logic";
|
||||||
|
|
||||||
function assert(condition: unknown, message: string) {
|
function assert(condition: unknown, message: string) {
|
||||||
@@ -749,4 +751,59 @@ export function runTests() {
|
|||||||
!uncachedNewCityInitial?.debHourlyPath,
|
!uncachedNewCityInitial?.debHourlyPath,
|
||||||
"selecting an uncached different city must not show another city's model and DEB curves",
|
"selecting an uncached different city must not show another city's model and DEB curves",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const chengduCacheKey = "chengdu:1m";
|
||||||
|
_hourlyCache.delete(chengduCacheKey);
|
||||||
|
const cachedChengduRow = {
|
||||||
|
city: "chengdu",
|
||||||
|
local_date: "2026-06-15",
|
||||||
|
local_time: "2026-06-15T09:00:00Z",
|
||||||
|
current_temp: 26.4,
|
||||||
|
current_max_so_far: 26.4,
|
||||||
|
temp_symbol: "°C",
|
||||||
|
tz_offset_seconds: 8 * 3600,
|
||||||
|
metar_context: { source: "amsc_awos" },
|
||||||
|
} as any;
|
||||||
|
rememberHourlyDetailSnapshot("chengdu", "1m", seedHourlyForecastFromRow(cachedChengduRow));
|
||||||
|
assert(
|
||||||
|
!_hourlyCache.has(chengduCacheKey),
|
||||||
|
"instant-restore cache must not persist a row-only seed that would block the full detail fetch",
|
||||||
|
);
|
||||||
|
|
||||||
|
const cachedChengduDetail = {
|
||||||
|
...seedHourlyForecastFromRow(cachedChengduRow),
|
||||||
|
localDate: "2026-06-15",
|
||||||
|
times: ["08:00", "09:00"],
|
||||||
|
temps: [25.8, 26.4],
|
||||||
|
modelTimes: ["08:00", "09:00"],
|
||||||
|
modelCurves: { ECMWF: [26.1, 26.5] },
|
||||||
|
debHourlyPath: {
|
||||||
|
source: "deb_hourly_consensus",
|
||||||
|
times: ["08:00", "09:00"],
|
||||||
|
temps: [30.1, 30.9],
|
||||||
|
},
|
||||||
|
runwayPlateHistory: {
|
||||||
|
"02L/20R": [{ timestamp: "2026-06-15T08:55:00Z", temp_c: 26.2 }],
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
const cachedChengduLivePatch = mergePatchIntoHourly(cachedChengduDetail, {
|
||||||
|
city: "chengdu",
|
||||||
|
revision: 7,
|
||||||
|
changes: {
|
||||||
|
temp: 26.8,
|
||||||
|
observed_at_utc: "2026-06-15T09:03:00Z",
|
||||||
|
runway_points: [{ runway: "02L/20R", temp: 26.8 }],
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
rememberHourlyDetailSnapshot("chengdu", "1m", cachedChengduLivePatch);
|
||||||
|
const restoredChengdu = _hourlyCache.get(chengduCacheKey)?.data;
|
||||||
|
assert(
|
||||||
|
restoredChengdu?.modelCurves?.ECMWF?.length === 2 &&
|
||||||
|
restoredChengdu?.debHourlyPath?.temps?.includes(30.9),
|
||||||
|
"instant-restore cache must keep the full DEB and multi-model detail after live merges",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
(restoredChengdu?.runwayPlateHistory?.["02L/20R"] || []).length === 2,
|
||||||
|
"instant-restore cache must include live-merged runway history so returning to terminal shows it immediately",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -466,6 +466,18 @@ function writeSessionCache(city: string, data: HourlyForecast) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rememberHourlyDetailSnapshot(
|
||||||
|
city: string,
|
||||||
|
resolution: string,
|
||||||
|
data: HourlyForecast,
|
||||||
|
) {
|
||||||
|
const cityKey = normalizeCityKey(city);
|
||||||
|
if (!cityKey || !data || !hasFullHourlyDetailPayload(data)) return;
|
||||||
|
const cacheKey = `${cityKey}:${resolution || "10m"}`;
|
||||||
|
_hourlyCache.set(cacheKey, { ts: Date.now(), data });
|
||||||
|
writeSessionCache(cacheKey, data);
|
||||||
|
}
|
||||||
|
|
||||||
function drainHourlyDetailRequestQueue() {
|
function drainHourlyDetailRequestQueue() {
|
||||||
while (
|
while (
|
||||||
_hourlyActiveDetailRequests < MAX_HOURLY_DETAIL_CONCURRENT_REQUESTS &&
|
_hourlyActiveDetailRequests < MAX_HOURLY_DETAIL_CONCURRENT_REQUESTS &&
|
||||||
@@ -1578,14 +1590,37 @@ function parseHourlyForecastFromCityDetail(json: CityDetail | null): HourlyForec
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preserveCachedRunwayHistory(cacheKey: string, data: HourlyForecast) {
|
||||||
|
if (!data) return data;
|
||||||
|
const cached = readHourlyCacheEntry(cacheKey, { allowStale: true })?.data;
|
||||||
|
if (!cached || hourlyLocalDatesConflict(cached, data, null)) return data;
|
||||||
|
const cachedHistory =
|
||||||
|
cached.runwayPlateHistory ||
|
||||||
|
((cached.amos as any)?.runway_plate_history as Record<string, Array<Record<string, unknown>>> | undefined);
|
||||||
|
const incomingHistory =
|
||||||
|
data.runwayPlateHistory ||
|
||||||
|
((data.amos as any)?.runway_plate_history as Record<string, Array<Record<string, unknown>>> | undefined);
|
||||||
|
const runwayPlateHistory = mergeRunwayPlateHistory(cachedHistory, incomingHistory);
|
||||||
|
if (!runwayPlateHistory) return data;
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
runwayPlateHistory,
|
||||||
|
amos: {
|
||||||
|
...(data.amos || {}),
|
||||||
|
runway_plate_history: runwayPlateHistory,
|
||||||
|
} as AmosData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function primeCityDetailCache(
|
function primeCityDetailCache(
|
||||||
city: string,
|
city: string,
|
||||||
resolution: string,
|
resolution: string,
|
||||||
detail: CityDetail | null | undefined,
|
detail: CityDetail | null | undefined,
|
||||||
): HourlyForecast {
|
): HourlyForecast {
|
||||||
const data = parseHourlyForecastFromCityDetail(detail || null);
|
let data = parseHourlyForecastFromCityDetail(detail || null);
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
const cacheKey = `${city}:${resolution}`;
|
const cacheKey = `${city}:${resolution}`;
|
||||||
|
data = preserveCachedRunwayHistory(cacheKey, data);
|
||||||
_hourlyCache.set(cacheKey, { ts: Date.now(), data });
|
_hourlyCache.set(cacheKey, { ts: Date.now(), data });
|
||||||
writeSessionCache(cacheKey, data);
|
writeSessionCache(cacheKey, data);
|
||||||
return data;
|
return data;
|
||||||
@@ -3086,6 +3121,7 @@ export {
|
|||||||
prefersHighFrequencyRunwayResolution,
|
prefersHighFrequencyRunwayResolution,
|
||||||
readCityDetailBatchDiagnostics,
|
readCityDetailBatchDiagnostics,
|
||||||
readSessionCache,
|
readSessionCache,
|
||||||
|
rememberHourlyDetailSnapshot,
|
||||||
selectCompactSecondaryTemp,
|
selectCompactSecondaryTemp,
|
||||||
selectDisplayRunwayTemp,
|
selectDisplayRunwayTemp,
|
||||||
selectInitialHourlyForRowChange,
|
selectInitialHourlyForRowChange,
|
||||||
|
|||||||
@@ -1157,6 +1157,61 @@ def test_chart_scope_overlays_collector_runway_history_from_db(monkeypatch):
|
|||||||
assert history[-1] == {"time": "2026-06-06T05:28:00+00:00", "temp": 24.8}
|
assert history[-1] == {"time": "2026-06-06T05:28:00+00:00", "temp": 24.8}
|
||||||
|
|
||||||
|
|
||||||
|
def test_chart_data_force_refresh_overlays_collector_runway_history(monkeypatch):
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
class FakeCache:
|
||||||
|
def get_runway_obs_recent(self, icao, minutes=60):
|
||||||
|
assert icao == "ZUUU"
|
||||||
|
assert minutes == 36 * 60
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"runway": "02L/20R",
|
||||||
|
"tdz_temp": 27.2,
|
||||||
|
"mid_temp": None,
|
||||||
|
"end_temp": 27.0,
|
||||||
|
"target_runway_max": 27.2,
|
||||||
|
"otime_utc": "2026-06-15T09:01:00+00:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runway": "02L/20R",
|
||||||
|
"tdz_temp": 27.8,
|
||||||
|
"mid_temp": None,
|
||||||
|
"end_temp": 27.5,
|
||||||
|
"target_runway_max": 27.8,
|
||||||
|
"otime_utc": "2026-06-15T09:04:00+00:00",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async def refreshed_full_payload(city, *, force_refresh):
|
||||||
|
assert force_refresh is True
|
||||||
|
return {
|
||||||
|
"name": city,
|
||||||
|
"display_name": "Chengdu",
|
||||||
|
"local_date": "2026-06-15",
|
||||||
|
"local_time": "17:04",
|
||||||
|
"temp_symbol": "°C",
|
||||||
|
"risk": {"icao": "ZUUU"},
|
||||||
|
"current": {"temp": 27.8},
|
||||||
|
"hourly": {"times": ["16:00"], "temps": [27.0]},
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(city_api.legacy_routes, "_CACHE_DB", FakeCache())
|
||||||
|
monkeypatch.setattr(city_api, "_get_city_full_data", refreshed_full_payload)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
city_api.legacy_routes,
|
||||||
|
"_overlay_latest_wunderground_current",
|
||||||
|
lambda city, payload: payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = asyncio.run(city_api._get_city_chart_data("chengdu", force_refresh=True))
|
||||||
|
|
||||||
|
assert payload["runway_plate_history"]["02L/20R"][-1] == {
|
||||||
|
"time": "2026-06-15T09:04:00+00:00",
|
||||||
|
"temp": 27.8,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_chart_data_cache_hit_starts_full_stale_refresh(monkeypatch):
|
def test_chart_data_cache_hit_starts_full_stale_refresh(monkeypatch):
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|||||||
@@ -404,7 +404,28 @@ async def _get_city_full_data(city: str, *, force_refresh: bool) -> Dict[str, An
|
|||||||
|
|
||||||
async def _get_city_chart_data(city: str, *, force_refresh: bool) -> Dict[str, Any]:
|
async def _get_city_chart_data(city: str, *, force_refresh: bool) -> Dict[str, Any]:
|
||||||
if force_refresh:
|
if force_refresh:
|
||||||
return await _get_city_full_data(city, force_refresh=True)
|
payload = await _get_city_full_data(city, force_refresh=True)
|
||||||
|
payload = await _run_optional_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="runway_history",
|
||||||
|
payload=payload,
|
||||||
|
fn=_overlay_cached_runway_history_from_db,
|
||||||
|
args=(city, payload),
|
||||||
|
)
|
||||||
|
payload = await _run_optional_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="amsc_latest_raw",
|
||||||
|
payload=payload,
|
||||||
|
fn=overlay_latest_amsc_observation,
|
||||||
|
args=(legacy_routes._CACHE_DB, city, payload),
|
||||||
|
)
|
||||||
|
return await _run_optional_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="wunderground_current",
|
||||||
|
payload=payload,
|
||||||
|
fn=legacy_routes._overlay_latest_wunderground_current,
|
||||||
|
args=(city, payload),
|
||||||
|
)
|
||||||
|
|
||||||
cached_entry = await run_in_threadpool(legacy_routes._CACHE_DB.get_city_cache, "full", city)
|
cached_entry = await run_in_threadpool(legacy_routes._CACHE_DB.get_city_cache, "full", city)
|
||||||
if cached_entry:
|
if cached_entry:
|
||||||
|
|||||||
Reference in New Issue
Block a user