fix: stabilize realtime chart axis and redis deployment

This commit is contained in:
2569718930@qq.com
2026-05-27 11:51:22 +08:00
parent 573768846e
commit 7ff1d21b95
4 changed files with 80 additions and 6 deletions
+25 -4
View File
@@ -1,6 +1,24 @@
services:
polyweather_redis:
image: redis:7-alpine
container_name: polyweather_redis
command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy noeviction
restart: unless-stopped
healthcheck:
interval: 10s
retries: 5
test:
- CMD
- redis-cli
- ping
timeout: 5s
volumes:
- polyweather_redis_data:/data
polyweather:
container_name: polyweather_bot
depends_on:
polyweather_redis:
condition: service_healthy
logging:
driver: "json-file"
options:
@@ -52,10 +70,8 @@ services:
interval: 30s
retries: 3
test:
- CMD
- wget
- -qO-
- http://localhost:3000
- CMD-SHELL
- wget -qO- http://$(hostname):3000
timeout: 5s
image: ghcr.io/yangyuan-zhen/polyweather-frontend:${IMAGE_TAG:-latest}
ports:
@@ -63,6 +79,9 @@ services:
restart: unless-stopped
polyweather_web:
command: python web/app.py
depends_on:
polyweather_redis:
condition: service_healthy
logging:
driver: "json-file"
options:
@@ -90,3 +109,5 @@ services:
x-polyweather-base:
env_file: *id001
image: ghcr.io/yangyuan-zhen/polyweather-backend:${IMAGE_TAG:-latest}
volumes:
polyweather_redis_data:
@@ -1109,6 +1109,39 @@ export function runTests() {
"Full-day chart should start at local 00:00 when the DEB hourly path has a midnight point",
);
const qingdaoPartialDetailFullDay = __buildTemperatureChartDataForTest(
{
city: "qingdao",
local_date: "2026-05-26",
local_time: "11:11",
tz_offset_seconds: 8 * 60 * 60,
deb_prediction: 22,
runway_plate_history: {
"16/34": [
{ time: "2026-05-26T00:05:00+08:00", temp: 23.5 },
{ time: "2026-05-26T09:28:00+08:00", temp: 19.8 },
{ time: "2026-05-26T11:11:00+08:00", temp: 19.8 },
],
},
} as any,
{
localDate: "2026-05-26",
localTime: "11:11",
times: ["00:00", "06:00", "09:00", "10:00"],
temps: [22, 18.5, 19.1, 19.6],
debPrediction: 22,
} as any,
"1D",
);
assert(
qingdaoPartialDetailFullDay.data[0]?.ts === qingdaoDayStart,
"All-day view should keep the local-day start even when Qingdao detail data is partial",
);
assert(
qingdaoPartialDetailFullDay.data.some((point) => point.ts === qingdaoDayEnd - 60 * 60 * 1000),
"All-day view should keep an end-of-day axis slot even when Qingdao detail data only reaches the morning",
);
const chongqingRolledToNextDay = __buildTemperatureChartDataForTest(
{
city: "chongqing",
@@ -1320,6 +1320,13 @@ function sortedTimeline(timestamps: Iterable<number>) {
return Array.from(new Set(Array.from(timestamps).filter((ts) => Number.isFinite(ts)))).sort((a, b) => a - b);
}
function addLocalDayAxisSlots(timeline: Set<number>, bounds: LocalDayBounds | null) {
if (!bounds) return;
for (let ts = bounds.start; ts < bounds.end; ts += 60 * 60 * 1000) {
timeline.add(ts);
}
}
function resolveFullDayFallbackAnchor(
row: ScanOpportunityRow | null,
hourly: HourlyForecast,
@@ -1582,6 +1589,7 @@ function buildFullDayChartData(
finalSettlementObs.forEach((point) => timelineSet.add(point.ts));
if (!isRunwaySensorAggregateSource) finalMadisObs.forEach((point) => timelineSet.add(point.ts));
if (shouldRenderMetar) metarObs.forEach((point) => timelineSet.add(point.ts));
addLocalDayAxisSlots(timelineSet, localDayBounds);
let debPath: ReturnType<typeof buildDebBaselinePath> | null = null;
if (hourly?.times?.length && hourly?.temps?.length) {
+14 -2
View File
@@ -18,6 +18,8 @@ from web.realtime_patch_schema import EVENT_TYPE
DEFAULT_STREAM_KEY = "stream:city_observation"
DEFAULT_COUNTER_KEY = "counter:city_observation_revision"
DEFAULT_MAXLEN = 50000
DEFAULT_SOCKET_TIMEOUT_SECONDS = 15.0
DEFAULT_SOCKET_CONNECT_TIMEOUT_SECONDS = 5.0
APPEND_EVENT_SCRIPT = """
local revision = redis.call('INCR', KEYS[2])
@@ -60,6 +62,13 @@ def _int_or_zero(value: Any) -> int:
return 0
def _float_env(name: str, default: float) -> float:
try:
return float(os.getenv(name) or default)
except (TypeError, ValueError):
return default
class RedisRealtimeEventStore:
"""Persist replayable observation patch events in a Redis Stream."""
@@ -94,8 +103,11 @@ class RedisRealtimeEventStore:
url = redis_url or os.getenv("POLYWEATHER_REDIS_URL") or "redis://127.0.0.1:6379/0"
client = redis.Redis.from_url(
url,
socket_timeout=5,
socket_connect_timeout=5,
socket_timeout=_float_env("POLYWEATHER_REDIS_SOCKET_TIMEOUT_SECONDS", DEFAULT_SOCKET_TIMEOUT_SECONDS),
socket_connect_timeout=_float_env(
"POLYWEATHER_REDIS_SOCKET_CONNECT_TIMEOUT_SECONDS",
DEFAULT_SOCKET_CONNECT_TIMEOUT_SECONDS,
),
health_check_interval=30,
)
client.ping()