This version of Antigravity is no longer supported. Please upgrade to receive the latest features.
This commit is contained in:
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|||||||
import {
|
import {
|
||||||
buildCityMarketScanCacheKey,
|
buildCityMarketScanCacheKey,
|
||||||
deriveCityMarketScanView,
|
deriveCityMarketScanView,
|
||||||
|
normalizeCityMarketScanPayload,
|
||||||
resolveCityMarketScanSnapshot,
|
resolveCityMarketScanSnapshot,
|
||||||
writeCachedCityMarketScan,
|
writeCachedCityMarketScan,
|
||||||
} from "@/components/dashboard/scan-terminal/market-scan-state";
|
} from "@/components/dashboard/scan-terminal/market-scan-state";
|
||||||
@@ -74,6 +75,35 @@ export function runTests() {
|
|||||||
assert.equal((cachedSnapshot.payload as unknown as { label: string }).label, "cached");
|
assert.equal((cachedSnapshot.payload as unknown as { label: string }).label, "cached");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage.clear();
|
||||||
|
writeCachedCityMarketScan(cacheKey, {
|
||||||
|
market_scan: {
|
||||||
|
available: true,
|
||||||
|
market_price: 0.41,
|
||||||
|
selected_slug: "highest-temperature-in-nyc-on-april-30-2026-60-61f",
|
||||||
|
},
|
||||||
|
selected_date: "2026-04-30",
|
||||||
|
} as unknown as MarketScan);
|
||||||
|
const wrappedCachedSnapshot = resolveCityMarketScanSnapshot({
|
||||||
|
detail: cityDetail(),
|
||||||
|
detailCityName: "Test City",
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
assert.equal(wrappedCachedSnapshot.action, "success");
|
||||||
|
if (wrappedCachedSnapshot.action === "success") {
|
||||||
|
assert.equal(wrappedCachedSnapshot.payload.available, true);
|
||||||
|
assert.equal(wrappedCachedSnapshot.payload.market_price, 0.41);
|
||||||
|
assert.equal(wrappedCachedSnapshot.payload.selected_date, "2026-04-30");
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrappedResponse = normalizeCityMarketScanPayload({
|
||||||
|
market_scan: { available: true, yes_buy: 0.42 } as MarketScan,
|
||||||
|
selected_date: "2026-04-30",
|
||||||
|
});
|
||||||
|
assert.equal(wrappedResponse?.available, true);
|
||||||
|
assert.equal(wrappedResponse?.yes_buy, 0.42);
|
||||||
|
assert.equal(wrappedResponse?.selected_date, "2026-04-30");
|
||||||
|
|
||||||
storage.clear();
|
storage.clear();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
resolveCityMarketScanSnapshot({
|
resolveCityMarketScanSnapshot({
|
||||||
|
|||||||
@@ -7,11 +7,19 @@ import type { RemoteData } from "@/components/dashboard/scan-terminal/scan-termi
|
|||||||
import type { CityDetail, MarketScan } from "@/lib/dashboard-types";
|
import type { CityDetail, MarketScan } from "@/lib/dashboard-types";
|
||||||
import { normalizeCityKey } from "./decision-utils";
|
import { normalizeCityKey } from "./decision-utils";
|
||||||
|
|
||||||
const CITY_MARKET_SCAN_CACHE_PREFIX = "polyWeather_cityMarketScan_v3";
|
const CITY_MARKET_SCAN_CACHE_PREFIX = "polyWeather_cityMarketScan_v4";
|
||||||
const CITY_MARKET_SCAN_CACHE_TTL_MS = 10 * 60 * 1000;
|
const CITY_MARKET_SCAN_CACHE_TTL_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
export type CityMarketScanStatus = "idle" | "loading" | "ready" | "failed";
|
export type CityMarketScanStatus = "idle" | "loading" | "ready" | "failed";
|
||||||
|
|
||||||
|
export type CityMarketScanApiPayload =
|
||||||
|
| MarketScan
|
||||||
|
| {
|
||||||
|
fetched_at?: string | null;
|
||||||
|
market_scan?: MarketScan | null;
|
||||||
|
selected_date?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type CityMarketScanSnapshot =
|
export type CityMarketScanSnapshot =
|
||||||
| { action: "reset"; cacheKey?: string }
|
| { action: "reset"; cacheKey?: string }
|
||||||
| { action: "success"; cacheKey: string; payload: MarketScan; shouldWriteCache?: boolean }
|
| { action: "success"; cacheKey: string; payload: MarketScan; shouldWriteCache?: boolean }
|
||||||
@@ -32,13 +40,30 @@ export function buildCityMarketScanCacheKey({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readCachedCityMarketScan(cacheKey: string) {
|
export function readCachedCityMarketScan(cacheKey: string) {
|
||||||
return readCachedPayload<MarketScan>(cacheKey, CITY_MARKET_SCAN_CACHE_TTL_MS);
|
return normalizeCityMarketScanPayload(
|
||||||
|
readCachedPayload<CityMarketScanApiPayload>(cacheKey, CITY_MARKET_SCAN_CACHE_TTL_MS),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeCachedCityMarketScan(cacheKey: string, payload: MarketScan) {
|
export function writeCachedCityMarketScan(cacheKey: string, payload: MarketScan) {
|
||||||
writeCachedPayload(cacheKey, payload);
|
writeCachedPayload(cacheKey, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeCityMarketScanPayload(
|
||||||
|
payload: CityMarketScanApiPayload | null | undefined,
|
||||||
|
): MarketScan | null {
|
||||||
|
if (!payload || typeof payload !== "object") return null;
|
||||||
|
if ("market_scan" in payload) {
|
||||||
|
const scan = payload.market_scan;
|
||||||
|
if (!scan || typeof scan !== "object") return null;
|
||||||
|
return {
|
||||||
|
...scan,
|
||||||
|
selected_date: scan.selected_date ?? payload.selected_date ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return payload as MarketScan;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveCityMarketScanSnapshot({
|
export function resolveCityMarketScanSnapshot({
|
||||||
detail,
|
detail,
|
||||||
detailCityName,
|
detailCityName,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { extractStreamingAirportRead } from "@/components/dashboard/scan-terminal/ai-city-stream";
|
import { extractStreamingAirportRead } from "@/components/dashboard/scan-terminal/ai-city-stream";
|
||||||
|
import {
|
||||||
|
normalizeCityMarketScanPayload,
|
||||||
|
type CityMarketScanApiPayload,
|
||||||
|
} from "@/components/dashboard/scan-terminal/market-scan-state";
|
||||||
import type { AiCityForecastPayload } from "@/components/dashboard/scan-terminal/types";
|
import type { AiCityForecastPayload } from "@/components/dashboard/scan-terminal/types";
|
||||||
import {
|
import {
|
||||||
buildBrowserBackendHeaders,
|
buildBrowserBackendHeaders,
|
||||||
@@ -292,7 +296,7 @@ async function getMarketScan(city: string, options: MarketScanQueryOptions = {})
|
|||||||
if (options.marketSlug) params.set("market_slug", options.marketSlug);
|
if (options.marketSlug) params.set("market_slug", options.marketSlug);
|
||||||
if (options.lite != null) params.set("lite", String(options.lite));
|
if (options.lite != null) params.set("lite", String(options.lite));
|
||||||
const headers = await buildBrowserBackendHeaders({ Accept: "application/json" });
|
const headers = await buildBrowserBackendHeaders({ Accept: "application/json" });
|
||||||
return readJsonOrThrow<MarketScan>(
|
const payload = await readJsonOrThrow<CityMarketScanApiPayload>(
|
||||||
`/api/city/${encodeURIComponent(city)}/market-scan?${params.toString()}`,
|
`/api/city/${encodeURIComponent(city)}/market-scan?${params.toString()}`,
|
||||||
{
|
{
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
@@ -300,6 +304,12 @@ async function getMarketScan(city: string, options: MarketScanQueryOptions = {})
|
|||||||
signal: options.signal,
|
signal: options.signal,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return (
|
||||||
|
normalizeCityMarketScanPayload(payload) ?? {
|
||||||
|
available: false,
|
||||||
|
reason: "No market scan payload returned.",
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function runQueuedAiCityReadTask<T>(task: () => Promise<T>, onQueued?: () => void) {
|
function runQueuedAiCityReadTask<T>(task: () => Promise<T>, onQueued?: () => void) {
|
||||||
|
|||||||
Reference in New Issue
Block a user