From e840557d22885f125f90ca2fe8cec0ef19fd4582 Mon Sep 17 00:00:00 2001 From: eezd Date: Sat, 31 Oct 2026 12:57:14 +0800 Subject: [PATCH] Add authenticated global economic calendar with Jin10 fallback Collect macroeconomic releases, events and holidays through the official browser SDK with resumable history from 2025. Preserve evidence, revisions and observation times, and merge verified series with official fields taking priority. Add category filters, field provenance, coverage and connection status. Verify calendar persistence and deployment recovery in isolated acceptance checks. --- .../20261031_a8b1cd465ef3_jin10_calendar.py | 28 + backend/pyproject.toml | 1 + backend/src/alphapulse/api/calendar.py | 46 +- backend/src/alphapulse/api/crawler.py | 17 +- backend/src/alphapulse/api/jin10.py | 15 +- backend/src/alphapulse/calendar/jin10.py | 152 + .../src/alphapulse/calendar/jin10_browser.py | 78 + .../src/alphapulse/calendar/jin10_history.py | 73 + .../src/alphapulse/calendar/jin10_store.py | 72 + backend/src/alphapulse/calendar/jin10_view.py | 206 + .../src/alphapulse/calendar/jin10_worker.py | 140 + backend/src/alphapulse/calendar/service.py | 30 +- backend/src/alphapulse/calendar/worker.py | 1 + .../src/alphapulse/core/official_sources.py | 2 +- backend/src/alphapulse/models/__init__.py | 3 +- backend/src/alphapulse/models/jin10.py | 14 +- backend/tests/fixtures/calendar/jin10.json | 5346 +++++++++++++++++ backend/tests/test_calendar.py | 6 +- backend/tests/test_jin10.py | 1 + backend/tests/test_jin10_calendar.py | 207 + backend/uv.lock | 33 + deploy/Dockerfile.backend | 2 + deploy/README.md | 12 + deploy/docker-compose.yml | 5 + scripts/ci.py | 2 + scripts/deploy.py | 4 +- scripts/smoke_calendar.py | 105 + scripts/tests/test_cicd.py | 6 +- web/src/common/apis/calendar/type.ts | 24 +- web/src/common/apis/jin10/type.ts | 4 +- .../calendar/components/CalendarDetail.vue | 96 +- web/src/pages/calendar/index.vue | 69 +- web/src/pages/calendar/presentation.ts | 20 + .../settings/components/Jin10Settings.vue | 12 +- web/tests/calendar.test.ts | 90 + 35 files changed, 6828 insertions(+), 94 deletions(-) create mode 100644 backend/alembic/versions/20261031_a8b1cd465ef3_jin10_calendar.py create mode 100644 backend/src/alphapulse/calendar/jin10.py create mode 100644 backend/src/alphapulse/calendar/jin10_browser.py create mode 100644 backend/src/alphapulse/calendar/jin10_history.py create mode 100644 backend/src/alphapulse/calendar/jin10_store.py create mode 100644 backend/src/alphapulse/calendar/jin10_view.py create mode 100644 backend/src/alphapulse/calendar/jin10_worker.py create mode 100644 backend/tests/fixtures/calendar/jin10.json create mode 100644 backend/tests/test_jin10_calendar.py create mode 100644 scripts/smoke_calendar.py create mode 100644 web/src/pages/calendar/presentation.ts create mode 100644 web/tests/calendar.test.ts diff --git a/backend/alembic/versions/20261031_a8b1cd465ef3_jin10_calendar.py b/backend/alembic/versions/20261031_a8b1cd465ef3_jin10_calendar.py new file mode 100644 index 0000000..8b498f8 --- /dev/null +++ b/backend/alembic/versions/20261031_a8b1cd465ef3_jin10_calendar.py @@ -0,0 +1,28 @@ +"""Register the authenticated global calendar and resumable daily coverage.""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision = "a8b1cd465ef3" +down_revision = "f7a0bc354de2" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table("jin10_sync_days", sa.Column("day", sa.Date(), primary_key=True), + sa.Column("captured_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("counts", postgresql.JSONB(), nullable=False)) + source = sa.table("sources", *[sa.column(key, kind) for key, kind in [ + ("code", sa.String), ("name", sa.String), ("list_url", sa.String), ("collector", sa.String), + ("enabled", sa.Boolean), ("interval_seconds", sa.Integer), ("backfill_hours", sa.Integer), + ("consecutive_failures", sa.Integer), + ]]) + op.bulk_insert(source, [{"code": "jin10", "name": "金十·全球宏观日历", "list_url": "https://rili.jin10.com/", + "collector": "calendar", "enabled": True, "interval_seconds": 10, + "backfill_hours": 72, "consecutive_failures": 0}]) + + +def downgrade() -> None: + op.execute("DELETE FROM sources WHERE code = 'jin10'") + op.drop_table("jin10_sync_days") diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 2d258af..f53093a 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "httpx>=0.28.1", "openai>=3.30.0", "pillow>=13.0.0", + "playwright>=1.64.1,<2", "pydantic-settings>=2.15.0", "pyjwt>=2.15.1", "pypdf>=6.21.2", diff --git a/backend/src/alphapulse/api/calendar.py b/backend/src/alphapulse/api/calendar.py index 97786ca..7ca4a8b 100644 --- a/backend/src/alphapulse/api/calendar.py +++ b/backend/src/alphapulse/api/calendar.py @@ -9,11 +9,14 @@ from sqlalchemy.ext.asyncio import AsyncSession from alphapulse.api.response import ApiResponse, success from alphapulse.calendar.catalog import COUNTRIES, FAMILIES -from alphapulse.calendar.service import indicator_history, release_rows +from alphapulse.calendar.service import release_rows +from alphapulse.calendar.jin10_history import calendar_history +from alphapulse.calendar.jin10_view import merge_items from alphapulse.core.auth import require_admin from alphapulse.core.db import get_session from alphapulse.core.redis import KEY_PREFIX, redis from alphapulse.models import EconomicRelease, EconomicRevision, EconomicSeriesValue, RawResponse +from alphapulse.models.jin10 import Jin10Connection, Jin10SyncDay router = APIRouter(prefix="/api/calendar", tags=["calendar"], dependencies=[Depends(require_admin)]) LOCAL_TZ = ZoneInfo("Asia/Shanghai") @@ -25,12 +28,32 @@ async def calendar_data(session: AsyncSession, start: datetime, end: datetime) - ).order_by(EconomicRelease.scheduled_at, EconomicRelease.id))) raw = await redis.get(f"{KEY_PREFIX}calendar:status") status = json.loads(raw) if raw else None + raw_jin10 = await redis.get(f"{KEY_PREFIX}calendar:jin10") + jin10_status = json.loads(raw_jin10) if raw_jin10 else None + if jin10_status: + connection = await session.get(Jin10Connection, 1) + if not connection or not connection.token_encrypted or jin10_status.get("connected_at") != connection.connected_at.isoformat(): + jin10_status = {**jin10_status, "state": "disconnected", "error": None} + coverage = list(await session.scalars(select(Jin10SyncDay).where( + Jin10SyncDay.day >= start.astimezone(LOCAL_TZ).date(), Jin10SyncDay.day < end.astimezone(LOCAL_TZ).date(), + ).order_by(Jin10SyncDay.day))) now = datetime.now(UTC) - return {"items": [item for row in rows for item in release_rows(row, now)], "worker": status, + items = merge_items([(row, item) for row in rows if row.source != "jin10" for item in release_rows(row, now)], + [row for row in rows if row.source == "jin10"], now) + countries = {**COUNTRIES, **{item["country_code"]: item["country_name"] for item in items}} + return {"items": items, "worker": status, + "jin10": jin10_status, + "coverage": [{"date": day.day, "captured_at": day.captured_at, "counts": day.counts} for day in coverage], "server_time": now, "timezone": "Asia/Shanghai", - "countries": [{"code": code, "name": name} for code, name in COUNTRIES.items()], - "forecast_available": any(row.values.get(key, {}).get("forecast") is not None - for row in rows for key in FAMILIES[row.family].indicators)} + "countries": [{"code": code, "name": name} for code, name in countries.items()], + "forecast_available": any(item["forecast"] is not None for item in items)} + + +def indicators(row: EconomicRelease) -> tuple[str, ...]: + if row.source == "jin10": + meta = row.values["_jin10"] + return (meta["indicator"],) if meta["kind"] == "data" else () + return FAMILIES[row.family].indicators @router.get("") @@ -42,11 +65,12 @@ async def list_calendar(session: Annotated[AsyncSession, Depends(get_session)], @router.get("/{release_id}/revisions") async def revisions(release_id: int, session: Annotated[AsyncSession, Depends(get_session)]) -> ApiResponse[list[dict]]: - if await session.get(EconomicRelease, release_id) is None: + row = await session.get(EconomicRelease, release_id) + if row is None: raise HTTPException(404, "日历事件不存在") rows = await session.scalars(select(EconomicRevision).where(EconomicRevision.release_id == release_id) .order_by(EconomicRevision.revision_no.desc())) - return success([{"revision_no": r.revision_no, "captured_at": r.captured_at, + return success([{"source": row.source, "revision_no": r.revision_no, "captured_at": r.captured_at, "payload": r.payload, "raw_response_id": r.raw_response_id} for r in rows]) @@ -56,9 +80,9 @@ async def history(release_id: int, indicator: str, session: Annotated[AsyncSessi row = await session.get(EconomicRelease, release_id) if row is None: raise HTTPException(404, "日历事件不存在") - if indicator not in FAMILIES[row.family].indicators: + if indicator not in indicators(row): raise HTTPException(400, "指标不属于该事件") - return success(await indicator_history(session, row, indicator, limit)) + return success(await calendar_history(session, row, indicator, limit)) @router.get("/{release_id}/series") @@ -67,9 +91,9 @@ async def series(release_id: int, indicator: str, session: Annotated[AsyncSessio row = await session.get(EconomicRelease, release_id) if row is None: raise HTTPException(404, "日历事件不存在") - if indicator not in FAMILIES[row.family].indicators: + if indicator not in indicators(row): raise HTTPException(400, "指标不属于该事件") - if row.period is None: + if row.period is None or row.source == "jin10": return success([]) # 此接口是当前官方序列,采集时间可以晚于所选事件;不得用作历史时点回测输入。 rows = (await session.execute(select(EconomicSeriesValue, RawResponse.url).join( diff --git a/backend/src/alphapulse/api/crawler.py b/backend/src/alphapulse/api/crawler.py index 49ec231..3aff2d9 100644 --- a/backend/src/alphapulse/api/crawler.py +++ b/backend/src/alphapulse/api/crawler.py @@ -119,7 +119,6 @@ async def calendar_sources(session: AsyncSession) -> list[CalendarSourceOut]: now = datetime.now(UTC) raw = await redis.get(f"{KEY_PREFIX}calendar:status") heartbeat = json.loads(raw) if raw else None - alive = bool(heartbeat and now - datetime.fromisoformat(heartbeat["at"]) < timedelta(seconds=30)) pending = EconomicRelease.first_received_at.is_(None) & (EconomicRelease.schedule_status == "scheduled") published = EconomicRelease.first_received_at.is_not(None) rows = (await session.execute(select( @@ -138,18 +137,26 @@ async def calendar_sources(session: AsyncSession) -> list[CalendarSourceOut]: EconomicRelease.scheduled_at >= now - timedelta(days=45), ).order_by(EconomicRelease.last_checked_at.desc())): result_errors.setdefault(source, error) - codes = {family.source for family in FAMILIES.values()} + codes = {family.source for family in FAMILIES.values()} | {"jin10"} + raw_jin10 = await redis.get(f"{KEY_PREFIX}calendar:jin10") + jin10_heartbeat = json.loads(raw_jin10) if raw_jin10 else None sources = list(await session.scalars(select(Source).where(Source.code.in_(codes)).order_by(Source.code))) output = [] for source in sources: - checks = [s for s in heartbeat["sources"].values() if s["source"] == source.code] if heartbeat else [] + current_heartbeat = jin10_heartbeat if source.code == "jin10" else heartbeat + source_alive = bool(current_heartbeat and now - datetime.fromisoformat(current_heartbeat["at"]) < timedelta(seconds=30)) + if source.code == "jin10": + checks = [{"source": "jin10", "checked_at": jin10_heartbeat["at"], + "error": jin10_heartbeat.get("error") or (None if jin10_heartbeat.get("state") in ("ready", "syncing") else "金十同步尚未就绪")}] if jin10_heartbeat else [] + else: + checks = [s for s in heartbeat["sources"].values() if s["source"] == source.code] if heartbeat else [] errors = [s["error"] for s in checks if s["error"]] if source.code in result_errors: errors.append(result_errors[source.code]) data = stats.get(source.code, {}) output.append(CalendarSourceOut( - code=source.code, name=source.name, region_code=REGIONS[source.code], worker_alive=alive, - heartbeat_at=heartbeat["at"] if heartbeat else None, + code=source.code, name=source.name, region_code=REGIONS[source.code], worker_alive=source_alive, + heartbeat_at=current_heartbeat["at"] if current_heartbeat else None, schedule_checked_at=max((s["checked_at"] for s in checks), default=None), **{key: data.get(key) for key in ("schedule_success_at", "latest_result_at", "next_event_at", "history_start", "history_end")}, overdue_count=data.get("overdue_count", 0), errors=list(dict.fromkeys(errors)), diff --git a/backend/src/alphapulse/api/jin10.py b/backend/src/alphapulse/api/jin10.py index 0362da7..9bed5bf 100644 --- a/backend/src/alphapulse/api/jin10.py +++ b/backend/src/alphapulse/api/jin10.py @@ -1,5 +1,6 @@ """Administrator-only Jin10 connection; credentials never leave the backend.""" from datetime import UTC, datetime, timedelta +import json import secrets from typing import Annotated @@ -12,6 +13,7 @@ from alphapulse.calendar import jin10_auth from alphapulse.core.auth import AuthSession, check_session, require_admin from alphapulse.core.crypto import decrypt, encrypt from alphapulse.core.db import get_session +from alphapulse.core.redis import KEY_PREFIX, redis from alphapulse.models.jin10 import Jin10Connection router = APIRouter(prefix="/api/settings/jin10", tags=["settings"], dependencies=[Depends(require_admin)]) @@ -33,14 +35,21 @@ def clear_task(row: Jin10Connection) -> None: row.task_id = row.owner = row.qr_encrypted = row.expires_at = row.next_poll_at = None -def public_status(row: Jin10Connection) -> dict: +def public_status(row: Jin10Connection, sync: dict | None = None) -> dict: + current = bool(row.token_encrypted and row.connected_at and sync and sync.get("connected_at") == row.connected_at.isoformat() + and datetime.now(UTC) - datetime.fromisoformat(sync["at"]) < timedelta(seconds=30)) return {"connected": row.token_encrypted is not None, "connected_at": row.connected_at, - "error": row.error, "sync_state": "awaiting_validation" if row.token_encrypted else "disconnected"} + "error": row.error or (sync.get("error") if current else None), + "sync_state": sync["state"] if current else "awaiting_validation" if row.token_encrypted else "disconnected", + "last_success_at": sync.get("last_success_at") if current else None, + "history_next": sync.get("history_next") if current else None} @router.get("") async def status(session: Session) -> ApiResponse[dict]: - return success(public_status(await connection(session))) + row = await connection(session) + raw = await redis.get(f"{KEY_PREFIX}calendar:jin10") + return success(public_status(row, json.loads(raw) if raw else None)) @router.post("/qr", status_code=201) diff --git a/backend/src/alphapulse/calendar/jin10.py b/backend/src/alphapulse/calendar/jin10.py new file mode 100644 index 0000000..028ebca --- /dev/null +++ b/backend/src/alphapulse/calendar/jin10.py @@ -0,0 +1,152 @@ +"""Normalize authenticated Jin10 calendar responses without interpreting gold/USD signals.""" +import base64 +import binascii +import json +import re +import zlib +from dataclasses import dataclass +from datetime import UTC, date, datetime, time, timedelta +from zoneinfo import ZoneInfo + +LOCAL = ZoneInfo("Asia/Shanghai") +START = date(2025, 1, 1) +KINDS = ("data", "event", "holiday") +COUNTRY_CODES = { + "美国": "US", "欧元区": "EA", "英国": "GB", "德国": "DE", "法国": "FR", + "中国": "CN", "中国香港": "HK", "中国台湾": "TW", "日本": "JP", "韩国": "KR", + "加拿大": "CA", "澳大利亚": "AU", "新西兰": "NZ", "瑞士": "CH", "瑞典": "SE", + "挪威": "NO", "意大利": "IT", "西班牙": "ES", "印度": "IN", "俄罗斯": "RU", + "巴西": "BR", "南非": "ZA", "新加坡": "SG", "乌克兰": "UA", "墨西哥": "MX", + "土耳其": "TR", "波兰": "PL", "印度尼西亚": "ID", "马来西亚": "MY", "泰国": "TH", +} + + +@dataclass(frozen=True) +class Entry: + identity: str + at: datetime + title: str + values: dict + + +def day_url(day: date) -> str: + return f"https://rili.jin10.com/day/{day.isoformat()}/" + + +def records(body: dict, kind: str, day: date) -> list[dict]: + if kind not in KINDS or not isinstance(body, dict) or body.get("date") != day.isoformat(): + raise ValueError("金十响应日期或类别与请求不符") + rows = body.get("list") + if kind == "holiday": + # The public SDK's legacy daily holiday method returns zlib/base64 text. + if not isinstance(rows, str) or len(rows) > 2_000_000: + raise ValueError("金十假期响应格式已变化") + try: + decoder = zlib.decompressobj() + decoded = decoder.decompress(base64.b64decode(rows, validate=True), 4_000_000) + if not decoder.eof or decoder.unused_data or decoder.unconsumed_tail: + raise ValueError("金十假期压缩内容不完整或过大") + rows = json.loads(decoded) + except (ValueError, zlib.error, binascii.Error) as exc: + raise ValueError("金十假期内容无法解析") from exc + if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows): + raise ValueError("金十日历列表格式已变化") + return rows + + +def text(row: dict, key: str, *, nullable: bool = False) -> str | None: + value = row.get(key) + if nullable and value is None: + return None + if not isinstance(value, str) or not value.strip(): + raise ValueError(f"金十字段 {key} 缺失或类型错误") + return value.strip() + + +def identifier(row: dict, key: str) -> int: + value = row.get(key) + if type(value) is not int or value <= 0: + raise ValueError(f"金十字段 {key} 不是有效标识") + return value + + +def normalize(body: dict, kind: str, day: date) -> list[Entry]: + if day < START: + raise ValueError("金十历史范围不得早于2025-01-01北京时间") + result, identities = [], set() + for raw in records(body, kind, day): + identity = f"{kind}:{identifier(raw, 'data_id' if kind == 'data' else 'id')}" + if identity in identities: + raise ValueError("金十同一响应存在重复事件标识") + identities.add(identity) + country = text(raw, "country", nullable=True) + precision = "day" if kind == "holiday" else "minute" + if kind == "holiday": + if raw.get("date") != day.isoformat(): + raise ValueError("金十假期记录日期与请求不符") + at = datetime.combine(day, time.min, LOCAL) + title = text(raw, "name") + description = " · ".join(v for v in [text(raw, "exchange_name"), text(raw, "rest_note", nullable=True), + text(raw, "remark", nullable=True)] if v) + else: + timestamp = text(raw, "pub_time" if kind == "data" else "event_time") + at = datetime.strptime(timestamp, "%Y-%m-%d %H:%M").replace(tzinfo=LOCAL) + if at.date() != day: + raise ValueError("金十记录公布日期与请求不符") + unix = raw.get("pub_time_unix" if kind == "data" else "event_time_unix") + if type(unix) is not int or not int(at.timestamp()) <= unix < int(at.timestamp()) + 60: + raise ValueError("金十公布时间与时间戳不符") + at = datetime.fromtimestamp(unix, LOCAL) + if raw.get("determine") not in (0, 1): + raise ValueError("金十时间确定性字段已变化") + if raw["determine"] == 1 or raw.get("time_status"): + precision = "tentative" + title = text(raw, "indicator_name" if kind == "data" else "event_content") + description = text(raw, "note", nullable=True) if kind == "event" else None + if kind != "data" and raw.get("category") != "cj": + raise ValueError("金十记录不属于宏观日历") + importance = raw.get("star") if kind != "holiday" else 0 + if type(importance) is not int or not 0 <= importance <= 5: + raise ValueError("金十重要性字段已变化") + key = f"jin10_{identifier(raw, 'indicator_id')}" if kind == "data" else f"jin10_{kind}" + meta = {"kind": kind, "date": day.isoformat(), "precision": precision, "country_name": country or "未标注", + "country_code": COUNTRY_CODES.get(country, country or "UNKNOWN"), + "name": title, "description": description, "importance": importance, + "period_label": text(raw, "time_period", nullable=True) if kind == "data" else None, + "indicator": key, "unit": ("" if raw.get("unit") == "" else text(raw, "unit", nullable=True)) if kind == "data" else None, + "exchange": text(raw, "exchange_name") if kind == "holiday" else None} + values = {"_jin10": meta} + if kind == "data": + # Preserve nonnumeric values (including ranges); never coerce a missing value to zero. + values[key] = {"actual": text(raw, "actual", nullable=True), "previous": text(raw, "previous", nullable=True), + "forecast": text(raw, "consensus", nullable=True), + "previous_revised": text(raw, "revised", nullable=True)} + result.append(Entry(identity, at.astimezone(UTC), title, values)) + return result + + +def period(label: str | None, at: datetime) -> str | None: + """Resolve only verified monthly/weekly labels close to their publication date.""" + local = at.astimezone(LOCAL).date() + month = re.fullmatch(r"(\d{1,2})月", label or "") + week = re.fullmatch(r"至(\d{1,2})月(\d{1,2})日", label or "") + if month: + number = int(month[1]) + if not 1 <= number <= 12: + return None + year = local.year - (number > local.month) + return f"{year}-{number:02}" + if week: + try: + day = date(local.year, int(week[1]), int(week[2])) + if day > local: + day = day.replace(year=day.year - 1) + return day.isoformat() if local - day < timedelta(days=45) else None + except ValueError: + return None + quarter = re.fullmatch(r"第([一二三四])季度", label or "") + if quarter: + number = "一二三四".index(quarter[1]) + 1 + year = local.year - (number > (local.month - 1) // 3 + 1) + return f"{year}-Q{number}" + return None diff --git a/backend/src/alphapulse/calendar/jin10_browser.py b/backend/src/alphapulse/calendar/jin10_browser.py new file mode 100644 index 0000000..802585a --- /dev/null +++ b/backend/src/alphapulse/calendar/jin10_browser.py @@ -0,0 +1,78 @@ +"""Use Jin10's own browser SDK rather than reproducing its encrypted transport.""" +import asyncio +from datetime import date + +from playwright.async_api import Error as BrowserError, async_playwright + +from alphapulse.calendar.jin10 import KINDS, day_url, normalize +from alphapulse.calendar.jin10_auth import Jin10Error, uc_request + +METHODS = {"data": "getCalendarData", "event": "getCalendarEvent", "holiday": "getCalendarHoliday"} +VALIDATION_DAY = date(2025, 1, 8) + + +def validate_access(responses: dict) -> None: + # This archived day has verified economic releases and events. An authenticated + # account alone does not prove that the SDK can read the economic calendar. + normalized = {kind: normalize(responses[kind], kind, VALIDATION_DAY) for kind in KINDS} + if not normalized["data"] or not normalized["event"]: + raise Jin10Error("金十历史日历读取权限未通过验证,请重新连接后检查账号权限") + + +class Jin10Browser: + def __init__(self) -> None: + self.playwright = self.browser = self.context = self.page = None + self.credential = None + self.lock = asyncio.Lock() + + async def close(self) -> None: + if self.browser: + await self.browser.close() + if self.playwright: + await self.playwright.stop() + self.playwright = self.browser = self.context = self.page = None + self.credential = None + + async def connect(self, credential: str) -> None: + await self.close() + info = await uc_request("/userinfo", token=credential) + if not isinstance(info.get("data"), dict) or not info["data"]: + raise Jin10Error("金十登录态已失效,请重新扫码") + try: + self.playwright = await async_playwright().start() + self.browser = await self.playwright.chromium.launch(headless=True, args=["--no-sandbox", "--disable-dev-shm-usage"]) + self.context = await self.browser.new_context() + await self.context.add_cookies([{"name": "x-token", "value": credential, "domain": ".jin10.com", + "path": "/", "secure": True, "sameSite": "Lax"}]) + self.page = await self.context.new_page() + # Keep the host page outside the collection window so its initial requests do not compete. + await self.page.goto(day_url(date(2024, 1, 1)), wait_until="domcontentloaded", timeout=30000) + await self.page.wait_for_function( + "window.Jin10FlashInstance && window.Jin10FlashInstance.socket && " + "window.Jin10FlashInstance.socket.readyState === 1", timeout=30000) + await self.page.wait_for_timeout(2000) + self.credential = credential + validate_access(await self.fetch_day(VALIDATION_DAY)) + except BrowserError: + await self.close() + raise Jin10Error("金十浏览器连接失败,请检查网络与浏览器运行环境") from None + + async def fetch_day(self, day: date) -> dict[str, dict]: + async with self.lock: + if self.page is None: + raise Jin10Error("金十浏览器尚未连接") + try: + result = {} + for kind in KINDS: + result[kind] = await self.page.evaluate( + "async ({method, day}) => {" + "if (!window.Jin10FlashInstance || window.Jin10FlashInstance.socket.readyState !== 1) throw Error('disconnected');" + "return await window.Jin10FlashInstance[method](day, {timeout: 15000});}", + {"method": METHODS[kind], "day": day.isoformat()}) + info = await uc_request("/userinfo", token=self.credential) + if not isinstance(info.get("data"), dict) or not info["data"]: + raise Jin10Error("金十登录态已失效,未将空列表记为完整日历") + return result + except BrowserError: + # Browser exceptions may include URLs or script contents; never forward them to logs. + raise Jin10Error("金十日历请求失败或超时,未将本次响应记为成功") from None diff --git a/backend/src/alphapulse/calendar/jin10_history.py b/backend/src/alphapulse/calendar/jin10_history.py new file mode 100644 index 0000000..d493381 --- /dev/null +++ b/backend/src/alphapulse/calendar/jin10_history.py @@ -0,0 +1,73 @@ +"""Read the same official/provider merge for history without rewriting either source.""" +from datetime import UTC, datetime +from decimal import Decimal, InvalidOperation + +from sqlalchemy import or_, select +from sqlalchemy.ext.asyncio import AsyncSession + +from alphapulse.calendar.catalog import FAMILIES +from alphapulse.calendar.jin10_view import MAPPINGS, definition, merge_items +from alphapulse.calendar.service import indicator_history, release_rows +from alphapulse.models import EconomicRelease, EconomicRevision + + +def changed(initial: str | None, current: str) -> bool: + if initial is None: + return False + try: + return Decimal(initial) != Decimal(current) + except InvalidOperation: + return initial != current + + +async def calendar_history(session: AsyncSession, selected: EconomicRelease, indicator: str, limit: int) -> list[dict]: + if selected.source == "jin10": + match = definition(selected.values["_jin10"]) + if match is None: + return await indicator_history(session, selected, indicator, limit) + family, key, stage = match + else: + family, key, stage = selected.family, indicator, selected.stage + # IDs and exact definitions are verified by the normal read merge as well. + provider_keys = [f"jin10_{id_}" for mapping in MAPPINGS.values() for (id_, _), value in mapping.items() + if value == (family, key, stage)] + if not provider_keys: + return await indicator_history(session, selected, indicator, limit) + candidates = list(await session.scalars(select(EconomicRelease).where( + EconomicRelease.scheduled_at <= selected.scheduled_at, + EconomicRelease.schedule_status != "withdrawn", + or_((EconomicRelease.source == FAMILIES[family].source) & (EconomicRelease.family == family) + & EconomicRelease.stage.is_not_distinct_from(stage), + (EconomicRelease.source == "jin10") & EconomicRelease.values["_jin10"]["indicator"].as_string().in_(provider_keys)), + ).order_by(EconomicRelease.scheduled_at.desc(), EconomicRelease.id.desc()).limit(limit * 2))) + now = datetime.now(UTC) + official = [(row, item) for row in candidates if row.source != "jin10" for item in release_rows(row, now) if item["indicator"] == key] + extras = [row for row in candidates if row.source == "jin10" and definition(row.values["_jin10"]) == (family, key, stage)] + items = [item for item in merge_items(official, extras, now) if item["actual"] is not None] + items.sort(key=lambda r: (r["scheduled_at"], r["release_id"]), reverse=True) + items = items[:limit] + by_id = {row.id: row for row in candidates} + revisions = list(await session.scalars(select(EconomicRevision).where( + EconomicRevision.release_id.in_(by_id), + ).order_by(EconomicRevision.release_id, EconomicRevision.revision_no))) + first = {} + for revision in revisions: + owner = by_id[revision.release_id] + value_key = owner.values["_jin10"]["indicator"] if owner.source == "jin10" else key + actual = revision.payload["values"].get(value_key, {}).get("actual") + if actual is not None: + first.setdefault(owner.id, actual) + output = [] + for item in items: + owner = by_id[item["release_id"]] + if item["field_sources"]["actual"]["source"] == "jin10" and owner.source != "jin10": + owner = by_id[item["related_release_id"]] + initial = first.get(owner.id) + output.append({"release_id": item["release_id"], "scheduled_at": item["scheduled_at"], "title": item["title"], + "period": item["period"], "period_label": item["period_label"], "stage": item["stage"], + "actual": item["actual"], "previous": item["previous"], "forecast": item["forecast"], + "forecast_before_release": item["forecast_before_release"], "forecast_observed_at": item["forecast_observed_at"], + "first_recorded_actual": initial, "first_received_at": owner.first_received_at, + "release_url": item["field_sources"]["actual"]["url"], "field_sources": item["field_sources"], + "revised": changed(initial, item["actual"])}) + return output diff --git a/backend/src/alphapulse/calendar/jin10_store.py b/backend/src/alphapulse/calendar/jin10_store.py new file mode 100644 index 0000000..b2342cc --- /dev/null +++ b/backend/src/alphapulse/calendar/jin10_store.py @@ -0,0 +1,72 @@ +"""Keep provider identities, raw evidence, field observation times and revisions together.""" +from datetime import date, datetime + +from sqlalchemy import select +from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.ext.asyncio import AsyncSession + +from alphapulse.calendar.jin10 import Entry, day_url +from alphapulse.calendar.service import record_revision +from alphapulse.models import EconomicRelease +from alphapulse.models.jin10 import Jin10SyncDay + + +async def save_entry(session: AsyncSession, entry: Entry, raw_id: int, now: datetime) -> None: + row = await session.scalar(select(EconomicRelease).where( + EconomicRelease.source == "jin10", EconomicRelease.external_id == entry.identity, + ).with_for_update()) + values = {key: dict(value) for key, value in entry.values.items()} + meta = values["_jin10"] + key = meta["indicator"] + if meta["kind"] == "data": + current = values[key] + old = row.values.get(key, {}) if row else {} + observed = {} + for field in ("actual", "previous", "forecast", "previous_revised"): + if current[field] is not None: + observed[field] = old.get("observed_at", {}).get(field, now.isoformat()) if current[field] == old.get(field) else now.isoformat() + current["observed_at"] = observed + forecast_at = datetime.fromisoformat(observed["forecast"]) if "forecast" in observed else None + if forecast_at == now: + before = current["actual"] is None + else: + before = old.get("forecast_before_release", False) + current["forecast_before_release"] = bool(before and forecast_at and forecast_at < entry.at and meta["precision"] == "minute") + received = meta["kind"] != "data" or values[key]["actual"] is not None + if row is None: + row = EconomicRelease(source="jin10", external_id=entry.identity, family="jin10", title=entry.title, + scheduled_at=entry.at, schedule_status="scheduled", schedule_url=day_url(date.fromisoformat(meta["date"])), + release_url=None, period=None, stage=None, schedule_checked_at=now, last_checked_at=now, + first_received_at=now if received else None, next_check_at=now, error=None, + values=values, revision_no=1) + session.add(row) + await session.flush() + record_revision(session, row, raw_id) + return + changed = row.values != values or row.scheduled_at != entry.at or row.title != entry.title + row.schedule_checked_at = row.last_checked_at = now + row.error = None + if row.first_received_at is None and received: + row.first_received_at = now + changed = True + if changed: + row.title, row.scheduled_at, row.values = entry.title, entry.at, values + row.schedule_url = day_url(date.fromisoformat(meta["date"])) + row.revision_no += 1 + record_revision(session, row, raw_id) + + +async def mark_day(session: AsyncSession, day: date, counts: dict, now: datetime) -> None: + stmt = insert(Jin10SyncDay).values(day=day, captured_at=now, counts=counts) + await session.execute(stmt.on_conflict_do_update(index_elements=["day"], set_={"captured_at": now, "counts": counts})) + + +async def next_history_day(session: AsyncSession, start: date, end: date) -> date | None: + from datetime import timedelta + covered = set(await session.scalars(select(Jin10SyncDay.day).where(Jin10SyncDay.day.between(start, end)))) + day = start + while day <= end: + if day not in covered: + return day + day += timedelta(days=1) + return None diff --git a/backend/src/alphapulse/calendar/jin10_view.py b/backend/src/alphapulse/calendar/jin10_view.py new file mode 100644 index 0000000..14087e0 --- /dev/null +++ b/backend/src/alphapulse/calendar/jin10_view.py @@ -0,0 +1,206 @@ +"""Join only verified series definitions; official values win independently for each field.""" +from copy import deepcopy +from datetime import datetime +from decimal import Decimal, InvalidOperation + +from alphapulse.calendar.catalog import FAMILIES, INDICATORS +from alphapulse.calendar.jin10 import LOCAL, period +from alphapulse.calendar.service import impact +from alphapulse.models import EconomicRelease + +# (Jin10 indicator, exact name) -> (official family, indicator, release stage). +# CPI annual rates are NSA, monthly rates SA; other adjustments must not be substituted. +US = { + (234, "未季调CPI年率"): ("cpi", "cpi_yoy", None), + (230, "季调后CPI月率"): ("cpi", "cpi_mom", None), + (232, "未季调核心CPI年率"): ("cpi", "core_cpi_yoy", None), + (233, "季调后核心CPI月率"): ("cpi", "core_cpi_mom", None), + (178, "PPI年率"): ("ppi", "ppi_yoy", None), + (177, "PPI月率"): ("ppi", "ppi_mom", None), + (75, "季调后非农就业人口"): ("jobs", "payrolls", None), + (76, "失业率"): ("jobs", "unemployment", None), + (212, "PCE物价指数年率"): ("pce", "pce_yoy", None), + (213, "PCE物价指数月率"): ("pce", "pce_mom", None), + (211, "核心PCE物价指数年率"): ("pce", "core_pce_yoy", None), + (214, "核心PCE物价指数月率"): ("pce", "core_pce_mom", None), + (144, "实际GDP年化季率修正值"): ("gdp", "gdp", "second"), + (872, "实际GDP年化季率终值"): ("gdp", "gdp", "third"), + (198, "密歇根大学消费者信心指数初值"): ("umich", "umich_sentiment", "preliminary"), + (208, "密歇根大学消费者信心指数终值"): ("umich", "umich_sentiment", "final"), + (216, "实际GDP年化季率初值"): ("gdp", "gdp", "advance"), + (77, "美联储利率决定(上限)"): ("fomc", "fed_rate", None), + (78, "ADP就业人数"): ("adp_jobs", "adp_jobs", None), + (79, "当周初请失业金人数"): ("initial_claims", "initial_claims", None), + (96, "当周续请失业金人数"): ("continued_claims", "continued_claims", None), + (111, "当周EIA原油库存"): ("eia_stocks", "eia_crude", None), + (114, "当周EIA汽油库存"): ("eia_stocks", "eia_gasoline", None), + (113, "当周EIA精炼油库存"): ("eia_stocks", "eia_distillate", None), +} +EA = { + (763, "GDP季率初值"): ("ea_gdp", "gdp_qoq", "preliminary"), + (762, "GDP年率初值"): ("ea_gdp", "gdp_yoy", "preliminary"), + (930, "CPI年率初值"): ("ea_hicp", "hicp_yoy", "flash"), + (413, "CPI月率初值"): ("ea_hicp", "hicp_mom", "flash"), + (759, "核心CPI年率初值"): ("ea_hicp", "core_hicp_yoy", "flash"), + (764, "CPI年率终值"): ("ea_hicp", "hicp_yoy", "final"), + (8125, "CPI月率终值"): ("ea_hicp", "hicp_mom", "final"), + (622, "核心CPI年率终值"): ("ea_hicp", "core_hicp_yoy", "final"), + (396, "失业率"): ("ea_jobs", "ilo_unemployment", None), + (395, "欧洲央行存款机制利率"): ("ecb_rate", "ecb_deposit", None), + (753, "欧洲央行主要再融资利率"): ("ecb_rate", "ecb_main", None), + (419, "欧洲央行边际贷款利率"): ("ecb_rate", "ecb_marginal", None), +} + +FR = { + (831, "GDP年率初值"): ("fr_gdp", "gdp_yoy", "initial"), + (829, "GDP年率终值"): ("fr_gdp", "gdp_yoy", "final"), + (830, "GDP季率终值"): ("fr_gdp", "gdp_qoq", "final"), + (8111, "CPI年率初值"): ("fr_cpi", "eu_cpi_yoy", "preliminary"), + (8112, "CPI月率初值"): ("fr_cpi", "eu_cpi_mom", "preliminary"), + (8113, "调和CPI年率初值"): ("fr_cpi", "hicp_yoy", "preliminary"), + (8114, "调和CPI月率初值"): ("fr_cpi", "hicp_mom", "preliminary"), + (567, "CPI年率终值"): ("fr_cpi", "eu_cpi_yoy", "final"), + (568, "CPI月率终值"): ("fr_cpi", "eu_cpi_mom", "final"), + (569, "调和CPI年率终值"): ("fr_cpi", "hicp_yoy", "final"), + (570, "调和CPI月率终值"): ("fr_cpi", "hicp_mom", "final"), + (832, "GDP季率初值"): ("fr_gdp", "gdp_qoq", "initial"), +} +DE = { + (270, "季调后GDP季率终值"): ("de_gdp", "gdp_qoq", "final"), + (842, "工作日调整后GDP年率终值"): ("de_gdp", "gdp_yoy", "final"), + (846, "CPI年率初值"): ("de_cpi", "eu_cpi_yoy", "preliminary"), + (847, "CPI月率初值"): ("de_cpi", "eu_cpi_mom", "preliminary"), + (839, "调和CPI年率初值"): ("de_cpi", "hicp_yoy", "preliminary"), + (840, "调和CPI月率初值"): ("de_cpi", "hicp_mom", "preliminary"), + (844, "CPI年率终值"): ("de_cpi", "eu_cpi_yoy", "final"), + (845, "CPI月率终值"): ("de_cpi", "eu_cpi_mom", "final"), + (837, "调和CPI年率终值"): ("de_cpi", "hicp_yoy", "final"), + (838, "调和CPI月率终值"): ("de_cpi", "hicp_mom", "final"), + (245, "季调后GDP季率初值"): ("de_gdp", "gdp_qoq", "initial"), + (843, "工作日调整后GDP年率初值"): ("de_gdp", "gdp_yoy", "initial"), +} +GB = { + (649, "三个月ILO失业率"): ("uk_jobs", "uk_unemployment", None), + (390, "GDP年率初值"): ("uk_gdp", "gdp_yoy", "initial"), + (391, "GDP季率初值"): ("uk_gdp", "gdp_qoq", "initial"), + (385, "GDP年率终值"): ("uk_gdp", "gdp_yoy", "final"), + (658, "GDP季率终值"): ("uk_gdp", "gdp_qoq", "final"), + (344, "CPI年率"): ("uk_cpi", "eu_cpi_yoy", None), + (348, "CPI月率"): ("uk_cpi", "eu_cpi_mom", None), + (349, "核心CPI年率"): ("uk_cpi", "eu_core_cpi_yoy", None), + (638, "央行利率决定"): ("boe_rate", "bank_rate", None), +} +MAPPINGS = {"US": US, "EA": EA, "FR": FR, "DE": DE, "GB": GB} + + +def definition(meta: dict) -> tuple[str, str, str | None] | None: + if meta["kind"] != "data" or meta["precision"] != "minute": + return None + mapping = MAPPINGS.get(meta["country_code"], {}) + match = mapping.get((int(meta["indicator"].removeprefix("jin10_")), meta["name"])) + # Jin10 omits units for the verified Michigan index, whose official series is an index in points. + unit = "点" if meta["unit"] is None and meta["country_code"] == "US" and meta["indicator"] in ("jin10_198", "jin10_208") else meta["unit"] + return match if match and unit == INDICATORS[match[1]].unit else None + + +def provenance(source: str, at: str | datetime | None, url: str) -> dict: + return {"source": source, "observed_at": at, "url": url} + + +def jin10_item(row: EconomicRelease, now: datetime) -> dict: + meta = row.values["_jin10"] + key = meta["indicator"] + value = row.values.get(key, {}) + actual, previous, forecast = value.get("actual"), value.get("previous"), value.get("forecast") + revised = value.get("previous_revised") + match = definition(meta) + eligible = value.get("forecast_before_release", False) + if meta["kind"] != "data": + status = "recorded" + direction, explanation = "uncertain", "日程记录不代表事件已经发生;需结合实际消息判断 BTC/ETH 影响。" + else: + status = "released" if actual is not None else "historical" if (now - row.scheduled_at).days > 45 else "scheduled" if now < row.scheduled_at else "delayed" + direction, explanation = direction_for(match[1] if match else None, actual, revised if revised is not None else previous, forecast if eligible else None) + observed = value.get("observed_at", {}) + fields = {field: provenance("jin10", observed.get(field), row.schedule_url) + for field in ("actual", "previous", "forecast", "previous_revised") if value.get(field) is not None} + fields["scheduled_at"] = provenance("jin10", row.schedule_checked_at, row.schedule_url) + if revised is not None: + if "previous" in fields: + fields["previous_original"] = fields["previous"] + fields["previous"] = fields["previous_revised"] + return {"release_id": row.id, "indicator": key, "name": meta["name"], "unit": meta["unit"], + "country_code": meta["country_code"], "country_name": meta["country_name"], + "event_name": f"{meta['country_name']}{meta['period_label'] or ''}{meta['name']}", + "period": None, "period_label": meta["period_label"], "stage": None, "stage_name": None, + "title": meta["description"] or meta["name"], "source": "jin10", "scheduled_at": row.scheduled_at, + "calendar_date": meta["date"], "time_precision": meta["precision"], "kind": meta["kind"], + "importance": meta["importance"], "importance_source": "jin10", "status": status, + "actual": actual, "previous": revised if revised is not None else previous, + "previous_original": previous, "previous_revised": revised, "forecast": forecast, + "previous_basis": "金十列示的修订前值(原前值单独保留)" if revised is not None else "金十本次记录列示前值" if previous is not None else None, + "forecast_before_release": eligible, "forecast_observed_at": observed.get("forecast"), + "impact": direction, "explanation": explanation, "impact_basis": "forecast" if forecast is not None and eligible else "previous" if previous is not None or revised is not None else None, + "release_url": None, "schedule_url": row.schedule_url, "first_received_at": row.first_received_at, + "last_checked_at": row.last_checked_at, "error": row.error, "revision_no": row.revision_no, + "field_sources": fields, "related_release_id": None, "related_revision_no": None} + + +def direction_for(key: str | None, actual: str | None, previous: str | None, forecast: str | None) -> tuple[str, str]: + if key is None: + return "uncertain", "尚无经核实的 BTC/ETH 传导判断;金十的黄金/美元影响标签未用于加密资产。" + try: + if any(not Decimal(v).is_finite() for v in (actual, previous, forecast) if v is not None): + raise InvalidOperation() + return impact(key, actual, previous, forecast) + except InvalidOperation: + return "uncertain", "公布值或比较基准不是单一数值,暂不能判断 BTC/ETH 方向。" + + +def merge_items(official: list[tuple[EconomicRelease, dict]], third_party: list[EconomicRelease], now: datetime) -> list[dict]: + output = [] + used = set() + for row, original in official: + item = deepcopy(original) + item.update(kind="data", time_precision="minute", calendar_date=row.scheduled_at.astimezone(LOCAL).date().isoformat(), + importance_source="project", field_sources={field: provenance(row.source, row.first_received_at, row.release_url or row.schedule_url) + for field in ("actual", "previous", "forecast") if item[field] is not None}, + related_release_id=None, related_revision_no=None, period_label=None, previous_original=None, previous_revised=None, + forecast_before_release=False, forecast_observed_at=None) + item["field_sources"]["scheduled_at"] = provenance(row.source, row.schedule_checked_at, row.schedule_url) + matches = [] + for extra in third_party: + meta = extra.values["_jin10"] + match = definition(meta) + if not match or match != (row.family, item["indicator"], row.stage): + continue + if FAMILIES[row.family].source != row.source or extra.scheduled_at != row.scheduled_at: + continue + # Rate decisions have no statistical reference period. All data series must agree on it. + if row.family not in ("fomc", "ecb_rate", "boe_rate") and (row.period is None or period(meta["period_label"], extra.scheduled_at) != row.period): + continue + matches.append(extra) + if len(matches) == 1: + extra = matches[0] + fallback = jin10_item(extra, now) + used.add(extra.id) + item.update(related_release_id=extra.id, related_revision_no=extra.revision_no, + previous_original=fallback["previous_original"], previous_revised=fallback["previous_revised"], + forecast_before_release=fallback["forecast_before_release"], forecast_observed_at=fallback["forecast_observed_at"]) + for field in ("actual", "previous", "forecast"): + if item[field] is None and fallback[field] is not None: + item[field] = fallback[field] + provider_field = "previous_revised" if field == "previous" and fallback["previous_revised"] is not None else field + item["field_sources"][field] = fallback["field_sources"][provider_field] + if field == "previous": + item["previous_basis"] = fallback["previous_basis"] + if field == "actual": + item["first_received_at"] = fallback["first_received_at"] + if item["actual"] is not None and item["status"] != "withdrawn": + item["status"] = "released" + forecast = item["forecast"] if item.get("forecast_before_release") else None + item["impact"], item["explanation"] = direction_for(item["indicator"], item["actual"], item["previous"], forecast) + item["impact_basis"] = "forecast" if forecast is not None else "previous" if item["previous"] is not None else None + output.append(item) + output.extend(jin10_item(row, now) for row in third_party if row.id not in used) + return sorted(output, key=lambda r: (r["calendar_date"], r["scheduled_at"], r["release_id"], r["indicator"])) diff --git a/backend/src/alphapulse/calendar/jin10_worker.py b/backend/src/alphapulse/calendar/jin10_worker.py new file mode 100644 index 0000000..87cab5d --- /dev/null +++ b/backend/src/alphapulse/calendar/jin10_worker.py @@ -0,0 +1,140 @@ +"""Independent browser collector: live dates take priority over resumable daily history.""" +import asyncio +import json +import logging +import time +from datetime import UTC, date, datetime, timedelta + +from sqlalchemy import select + +from alphapulse.calendar.jin10 import KINDS, LOCAL, START, day_url, normalize +from alphapulse.calendar.jin10_auth import Jin10Error +from alphapulse.calendar.jin10_browser import Jin10Browser +from alphapulse.calendar.jin10_store import mark_day, next_history_day, save_entry +from alphapulse.calendar.service import save_raw +from alphapulse.core.crypto import decrypt +from alphapulse.core.db import SessionLocal +from alphapulse.core.redis import KEY_PREFIX, redis +from alphapulse.models import Source +from alphapulse.models.jin10 import Jin10Connection + +logger = logging.getLogger("alphapulse.calendar.jin10") +STATUS_KEY = f"{KEY_PREFIX}calendar:jin10" + + +class ConnectionChanged(Exception): + pass + + +def same_connection(row: Jin10Connection | None, encrypted: bytes, connected_at: datetime) -> bool: + return bool(row and row.token_encrypted == encrypted and row.connected_at == connected_at) + + +async def persist_day(day: date, responses: dict, encrypted: bytes, connected_at: datetime, now: datetime) -> dict: + raw_ids = {} + async with SessionLocal() as session: + # Disconnect/reconnect takes this same lock. An obsolete fetch must never commit data or progress. + row = await session.scalar(select(Jin10Connection).where(Jin10Connection.id == 1).with_for_update()) + if not same_connection(row, encrypted, connected_at): + raise ConnectionChanged() + for kind in KINDS: + raw = json.dumps(responses[kind], ensure_ascii=False, separators=(",", ":")).encode() + raw_ids[kind] = await save_raw(session, "jin10", day_url(day) + f"#{kind}", 200, + {"content-type": "application/json", "transport": "jin10-official-browser-sdk"}, raw, now) + await session.commit() + # Retain failed contract responses as evidence, but never advance coverage for them. + entries = {kind: normalize(responses[kind], kind, day) for kind in KINDS} + async with SessionLocal() as session: + row = await session.scalar(select(Jin10Connection).where(Jin10Connection.id == 1).with_for_update()) + if not same_connection(row, encrypted, connected_at): + raise ConnectionChanged() + for kind in KINDS: + for entry in entries[kind]: + await save_entry(session, entry, raw_ids[kind], now) + counts = {kind: len(entries[kind]) for kind in KINDS} + await mark_day(session, day, counts, now) + await session.commit() + return counts + + +class Jin10Worker: + def __init__(self) -> None: + self.browser = Jin10Browser() + self.identity = None + self.live_due: dict[date, float] = {} + self.state = {"state": "disconnected", "error": None, "last_success_at": None} + + async def heartbeat(self) -> None: + while True: + await redis.set(STATUS_KEY, json.dumps({**self.state, "at": datetime.now(UTC).isoformat()}), ex=60) + await asyncio.sleep(5) + + async def step(self) -> None: + async with SessionLocal() as session: + row = await session.get(Jin10Connection, 1) + source = await session.get(Source, "jin10") + identity = (row.token_encrypted, row.connected_at) if row and row.token_encrypted and source and source.enabled else None + if identity is None: + await self.browser.close() + self.identity = None + self.state.update(state="disconnected", error=None, connected_at=None) + await asyncio.sleep(5) + return + if identity != self.identity: + self.state = {"state": "connecting", "error": None, "connected_at": identity[1].isoformat(), "last_success_at": None} + await self.browser.connect(decrypt(identity[0])) + self.identity = identity + self.live_due.clear() + today = datetime.now(LOCAL).date() + dates = [today + timedelta(days=i) for i in range(-1, 8)] + self.live_due = {d: self.live_due.get(d, 0) for d in dates if d >= START} + due = sorted((deadline, d) for d, deadline in self.live_due.items() if deadline <= time.monotonic()) + if due: + # Refresh today first when a process starts, then adjacent days in deadline order. + day = today if self.live_due.get(today, float("inf")) == 0 else due[0][1] + history = False + else: + async with SessionLocal() as session: + day = await next_history_day(session, START, today - timedelta(days=1)) + history = True + if day is None: + self.state.update(state="ready", history_next=None) + await asyncio.sleep(2) + return + self.state.update(state="syncing", current_day=day.isoformat(), history=history, error=None) + responses = await self.browser.fetch_day(day) + counts = await persist_day(day, responses, *identity, datetime.now(UTC)) + if not history: + self.live_due[day] = time.monotonic() + (10 if day == today else 300) + self.state.update(state="ready", last_success_at=datetime.now(UTC).isoformat(), counts=counts) + if history: + self.state["history_next"] = (day + timedelta(days=1)).isoformat() + # A single collector owns all retries and limits upstream traffic, including backfill. + await asyncio.sleep(5) + + async def collect(self) -> None: + while True: + try: + await self.step() + except ConnectionChanged: + self.identity = None + self.state.update(state="disconnected", error=None) + except (Jin10Error, ValueError, KeyError) as exc: + self.state.update(state="error", error=str(exc)) + logger.warning("金十日历同步失败:%s", exc) + self.identity = None + await self.browser.close() + await asyncio.sleep(30) + + async def run(self) -> None: + try: + await asyncio.gather(self.heartbeat(), self.collect()) + finally: + await self.browser.close() + await redis.aclose() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + logging.getLogger("httpx").setLevel(logging.WARNING) + asyncio.run(Jin10Worker().run()) diff --git a/backend/src/alphapulse/calendar/service.py b/backend/src/alphapulse/calendar/service.py index 2fb731b..3af6852 100644 --- a/backend/src/alphapulse/calendar/service.py +++ b/backend/src/alphapulse/calendar/service.py @@ -170,6 +170,9 @@ def impact(key: str, actual: str | None, previous: str | None, forecast: str | N def release_rows(row: EconomicRelease, now: datetime) -> list[dict]: + if row.source == "jin10": + from alphapulse.calendar.jin10_view import jin10_item + return [jin10_item(row, now)] if row.schedule_status == "withdrawn": state = "withdrawn" elif row.first_received_at is not None: @@ -221,17 +224,30 @@ async def indicator_history(session: AsyncSession, row: EconomicRelease, key: st .where(EconomicRevision.release_id == EconomicRelease.id, EconomicRevision.payload["values"][key]["actual"].as_string().is_not(None)) .order_by(EconomicRevision.revision_no).limit(1).correlate(EconomicRelease).scalar_subquery()) + filters = [] + if row.source == "jin10": + meta = row.values["_jin10"] + filters = [EconomicRelease.values["_jin10"][field].as_string().is_not_distinct_from(meta[field]) + for field in ("country_code", "name", "unit")] rows = (await session.execute(select(EconomicRelease, first_actual).where( EconomicRelease.source == row.source, EconomicRelease.family == row.family, EconomicRelease.stage.is_not_distinct_from(row.stage), EconomicRelease.scheduled_at <= row.scheduled_at, EconomicRelease.schedule_status != "withdrawn", EconomicRelease.first_received_at.is_not(None), EconomicRelease.values[key]["actual"].as_string().is_not(None), + *filters, ).order_by(EconomicRelease.scheduled_at.desc(), EconomicRelease.id.desc()).limit(limit))).all() - return [{"release_id": item.id, "scheduled_at": item.scheduled_at, "title": item.title, - "period": item.period, "stage": item.stage, - "actual": item.values[key]["actual"], "previous": item.values[key].get("previous"), - "forecast": item.values[key].get("forecast"), "first_recorded_actual": initial, - "first_received_at": item.first_received_at, "release_url": item.release_url, - "revised": initial is not None and Decimal(initial) != Decimal(item.values[key]["actual"])} - for item, initial in rows] + output = [] + for item, initial in rows: + value = item.values[key] + view = release_rows(item, datetime.now(item.scheduled_at.tzinfo))[0] if item.source == "jin10" else None + output.append({"release_id": item.id, "scheduled_at": item.scheduled_at, "title": item.title, + "period": item.period, "stage": item.stage, + "actual": value["actual"], "previous": view["previous"] if view else value.get("previous"), + "forecast": value.get("forecast"), "first_recorded_actual": initial, + "first_received_at": item.first_received_at, "release_url": item.schedule_url if view else item.release_url, + "period_label": item.values["_jin10"]["period_label"] if view else None, + "forecast_before_release": value.get("forecast_before_release", False), + "forecast_observed_at": value.get("observed_at", {}).get("forecast"), + "revised": initial is not None and (initial != value["actual"] if view else Decimal(initial) != Decimal(value["actual"]))}) + return output diff --git a/backend/src/alphapulse/calendar/worker.py b/backend/src/alphapulse/calendar/worker.py index 533f223..d0941bf 100644 --- a/backend/src/alphapulse/calendar/worker.py +++ b/backend/src/alphapulse/calendar/worker.py @@ -374,6 +374,7 @@ class CalendarWorker: now = datetime.now(UTC) async with SessionLocal() as session: ids = list(await session.scalars(select(EconomicRelease.id).where( + EconomicRelease.source != "jin10", EconomicRelease.schedule_status == "scheduled", EconomicRelease.scheduled_at <= now, EconomicRelease.scheduled_at >= now - timedelta(days=HISTORY_DAYS), EconomicRelease.next_check_at <= now, ).order_by(EconomicRelease.scheduled_at.desc()).limit(8))) diff --git a/backend/src/alphapulse/core/official_sources.py b/backend/src/alphapulse/core/official_sources.py index 9e7d40c..096be3c 100644 --- a/backend/src/alphapulse/core/official_sources.py +++ b/backend/src/alphapulse/core/official_sources.py @@ -19,7 +19,7 @@ FEEDS = { "insee": ("https://www.insee.fr/fr/flux/3",), } REGIONS = {"wscn_news": "CN", "wscn_live": "CN", "cls_depth": "CN", "cls_telegraph": "CN", "coindesk": "GLOBAL", - "bls": "US", "bea": "US", "fed": "US", "umich": "US", "dol": "US", "eia": "US", "adp": "US", "ecb": "EA", "eurostat": "EA", "boe": "GB", "ons": "GB", "destatis": "DE", "insee": "FR"} + "bls": "US", "bea": "US", "fed": "US", "umich": "US", "dol": "US", "eia": "US", "adp": "US", "ecb": "EA", "eurostat": "EA", "boe": "GB", "ons": "GB", "destatis": "DE", "insee": "FR", "jin10": "GLOBAL"} HOSTS = {"ecb": {"www.ecb.europa.eu"}, "eurostat": {"ec.europa.eu"}, "boe": {"www.bankofengland.co.uk"}, "ons": {"www.ons.gov.uk"}, "destatis": {"www.destatis.de"}, "insee": {"www.insee.fr"}} LANGUAGES = {"destatis": "de", "insee": "fr"} diff --git a/backend/src/alphapulse/models/__init__.py b/backend/src/alphapulse/models/__init__.py index f149a1d..4cc4fb7 100644 --- a/backend/src/alphapulse/models/__init__.py +++ b/backend/src/alphapulse/models/__init__.py @@ -1,4 +1,4 @@ -from alphapulse.models.jin10 import Jin10Connection +from alphapulse.models.jin10 import Jin10Connection, Jin10SyncDay from alphapulse.models.risk import RiskSettings, RiskEvent, RiskAnalysis, RiskSnapshot from alphapulse.core.db import Base from alphapulse.models.ai import AiProvider, ChatMessage, ChatSession, ContextVersion, SkillNode, SkillVersion @@ -9,6 +9,7 @@ from alphapulse.models.trading import NewsSignal, TradingAccount, TradingAnalysi __all__ = [ "Jin10Connection", + "Jin10SyncDay", "RiskSettings", "RiskEvent", "RiskAnalysis", "RiskSnapshot", "AiProvider", "Base", diff --git a/backend/src/alphapulse/models/jin10.py b/backend/src/alphapulse/models/jin10.py index 79ea554..e79df0e 100644 --- a/backend/src/alphapulse/models/jin10.py +++ b/backend/src/alphapulse/models/jin10.py @@ -1,7 +1,8 @@ """Encrypted connection and short-lived, session-owned QR login state.""" -from datetime import datetime +from datetime import date, datetime -from sqlalchemy import DateTime, LargeBinary, String, Text +from sqlalchemy import Date, DateTime, LargeBinary, String, Text +from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column from alphapulse.core.db import Base @@ -19,3 +20,12 @@ class Jin10Connection(Base): expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) next_poll_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) error: Mapped[str | None] = mapped_column(Text) + + +class Jin10SyncDay(Base): + """A day is covered only after all three authenticated responses were validated.""" + __tablename__ = "jin10_sync_days" + + day: Mapped[date] = mapped_column(Date, primary_key=True) + captured_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) + counts: Mapped[dict] = mapped_column(JSONB) diff --git a/backend/tests/fixtures/calendar/jin10.json b/backend/tests/fixtures/calendar/jin10.json new file mode 100644 index 0000000..c1cfda5 --- /dev/null +++ b/backend/tests/fixtures/calendar/jin10.json @@ -0,0 +1,5346 @@ +{ + "2025-01-08": { + "date": "2025-01-08", + "list": [ + { + "data_id": 1091431, + "indicator_id": 78, + "time_period": "12月", + "full_time_period": "2024年 12月", + "previous": "14.6", + "consensus": "14", + "actual": "12.2", + "revised": null, + "pub_time": "2025-01-08 21:15", + "actual_time": "2025-01-08 21:15", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "ADP就业人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": [ + { + "end": "2025-01-08 21:15:00", + "link": "https://flash.jin10.com/detail/20250108120514620800", + "tags": [], + "type": "url", + "start": "2025-01-08 00:00:00", + "title": "15家机构前瞻美国12月ADP就业人数", + "is_free": 0 + } + ], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1091431", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12432", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736342100, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1091431&category=cj" + }, + { + "data_id": 1093217, + "indicator_id": 111, + "time_period": "至1月3日", + "full_time_period": null, + "previous": "-117.8", + "consensus": "-18.4", + "actual": "-95.9", + "revised": null, + "pub_time": "2025-01-08 23:30", + "actual_time": "2025-01-08 23:30", + "determine": 0, + "measure": "EPS", + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1093217", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736350200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1093217&category=cj" + }, + { + "data_id": 1093221, + "indicator_id": 953, + "time_period": "至1月3日", + "full_time_period": null, + "previous": "-14.2", + "consensus": null, + "actual": "-250.2", + "revised": null, + "pub_time": "2025-01-08 23:30", + "actual_time": "2025-01-08 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA俄克拉荷马州库欣原油库存", + "country": "美国", + "star": 3, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1093221", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736350200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1093221&category=cj" + }, + { + "data_id": 1091456, + "indicator_id": 79, + "time_period": "至1月4日", + "full_time_period": null, + "previous": "21.1", + "consensus": "21.8", + "actual": "20.1", + "revised": null, + "pub_time": "2025-01-08 21:30", + "actual_time": "2025-01-08 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周初请失业金人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1091456", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12396", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736343000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1091456&category=cj" + }, + { + "data_id": 1091457, + "indicator_id": 96, + "time_period": "至12月28日", + "full_time_period": "2024年 12月28日", + "previous": "184.4", + "consensus": "186.7", + "actual": "186.7", + "revised": "183.4", + "pub_time": "2025-01-08 21:30", + "actual_time": "2025-01-08 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周续请失业金人数", + "country": "美国", + "star": 2, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736343000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1091457&category=cj" + }, + { + "data_id": 1093220, + "indicator_id": 114, + "time_period": "至1月3日", + "full_time_period": null, + "previous": "771.7", + "consensus": "150.4", + "actual": "633", + "revised": null, + "pub_time": "2025-01-08 23:30", + "actual_time": "2025-01-08 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736350200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1093220&category=cj" + }, + { + "data_id": 1093219, + "indicator_id": 113, + "time_period": "至1月3日", + "full_time_period": null, + "previous": "640.6", + "consensus": "59.7", + "actual": "607.1", + "revised": null, + "pub_time": "2025-01-08 23:30", + "actual_time": "2025-01-08 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1736350200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1093219&category=cj" + } + ] + }, + "2026-10-02": { + "date": "2026-10-02", + "list": [ + { + "data_id": 1145165, + "indicator_id": 76, + "time_period": "9月", + "full_time_period": null, + "previous": "4.10", + "consensus": "4.1", + "actual": "4.2", + "revised": null, + "pub_time": "2026-10-02 20:30", + "actual_time": "2026-10-02 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "失业率", + "country": "美国", + "star": 5, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145165", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790944200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145165&category=cj" + }, + { + "data_id": 1145168, + "indicator_id": 75, + "time_period": "9月", + "full_time_period": null, + "previous": "16.2", + "consensus": "9", + "actual": "2.9", + "revised": "13.3", + "pub_time": "2026-10-02 20:30", + "actual_time": "2026-10-02 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "季调后非农就业人口", + "country": "美国", + "star": 5, + "unit": "万人", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145168", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12469", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790944200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145168&category=cj" + }, + { + "data_id": 1145164, + "indicator_id": 930, + "time_period": "9月", + "full_time_period": null, + "previous": "3.20", + "consensus": "3.6", + "actual": "3.8", + "revised": null, + "pub_time": "2026-10-02 17:00", + "actual_time": "2026-10-02 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145164", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1790931600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145164&category=cj" + }, + { + "data_id": 1145163, + "indicator_id": 413, + "time_period": "9月", + "full_time_period": null, + "previous": "0.40", + "consensus": "0.5", + "actual": "0.6", + "revised": null, + "pub_time": "2026-10-02 17:00", + "actual_time": "2026-10-02 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145163", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1790931600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145163&category=cj" + }, + { + "data_id": 1187043, + "indicator_id": 759, + "time_period": "9月", + "full_time_period": null, + "previous": "2.1", + "consensus": null, + "actual": "2.2", + "revised": null, + "pub_time": "2026-10-02 17:00", + "actual_time": "2026-10-02 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心CPI年率初值", + "country": "欧元区", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1790931600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1187043&category=cj" + } + ] + }, + "2026-10-14": { + "date": "2026-10-14", + "list": [ + { + "data_id": 1188114, + "indicator_id": 543, + "time_period": "9月", + "full_time_period": null, + "previous": "1190.9", + "consensus": "1181", + "actual": "1335.3", + "revised": null, + "pub_time": "2026-10-14 10:31", + "actual_time": "2026-10-14 10:31", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "以美元计算贸易帐", + "country": "中国", + "star": 3, + "unit": "亿美元", + "affect": 0, + "show_affect": 0, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1188114", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12652", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/中国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/中国@2x.png", + "time_status": null, + "pub_time_unix": 1791945117, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188114&category=cj" + }, + { + "data_id": 1145278, + "indicator_id": 234, + "time_period": "9月", + "full_time_period": null, + "previous": "3.40", + "consensus": "3.6", + "actual": "3.7", + "revised": null, + "pub_time": "2026-10-14 20:30", + "actual_time": "2026-10-14 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "未季调CPI年率", + "country": "美国", + "star": 5, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145278", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1791981000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145278&category=cj" + }, + { + "data_id": 1145276, + "indicator_id": 230, + "time_period": "9月", + "full_time_period": null, + "previous": "0.40", + "consensus": "0.60", + "actual": "0.6", + "revised": null, + "pub_time": "2026-10-14 20:30", + "actual_time": "2026-10-14 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "季调后CPI月率", + "country": "美国", + "star": 4, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145276", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1791981000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145276&category=cj" + }, + { + "data_id": 1145277, + "indicator_id": 233, + "time_period": "9月", + "full_time_period": null, + "previous": "0.30", + "consensus": "0.20", + "actual": "0.3", + "revised": null, + "pub_time": "2026-10-14 20:30", + "actual_time": "2026-10-14 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "季调后核心CPI月率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145277", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1791981000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145277&category=cj" + }, + { + "data_id": 1145279, + "indicator_id": 232, + "time_period": "9月", + "full_time_period": null, + "previous": "2.40", + "consensus": "2.50", + "actual": "2.6", + "revised": null, + "pub_time": "2026-10-14 20:30", + "actual_time": "2026-10-14 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "未季调核心CPI年率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145279", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1791981000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145279&category=cj" + } + ] + }, + "2026-10-28": { + "date": "2026-10-28", + "list": [ + { + "data_id": 1145430, + "indicator_id": 111, + "time_period": "至10月23日", + "full_time_period": null, + "previous": "-306", + "consensus": "1.5", + "actual": "-189.2", + "revised": null, + "pub_time": "2026-10-28 22:30", + "actual_time": "2026-10-28 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145430", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793197800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145430&category=cj" + }, + { + "data_id": 1145432, + "indicator_id": 114, + "time_period": "至10月23日", + "full_time_period": null, + "previous": "-295.2", + "consensus": "-128.8", + "actual": "387", + "revised": null, + "pub_time": "2026-10-28 22:30", + "actual_time": "2026-10-28 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793197800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145432&category=cj" + }, + { + "data_id": 1145433, + "indicator_id": 113, + "time_period": "至10月23日", + "full_time_period": null, + "previous": "167.5", + "consensus": "-88.8", + "actual": "-146.2", + "revised": null, + "pub_time": "2026-10-28 22:30", + "actual_time": "2026-10-28 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793197800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145433&category=cj" + } + ] + }, + "2026-10-29": { + "date": "2026-10-29", + "list": [ + { + "data_id": 1145437, + "indicator_id": 77, + "time_period": "至10月28日", + "full_time_period": null, + "previous": "4.00", + "consensus": "4.00", + "actual": "4", + "revised": null, + "pub_time": "2026-10-29 02:00", + "actual_time": "2026-10-29 02:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "美联储利率决定(上限)", + "country": "美国", + "star": 5, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145437", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12632", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793210400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145437&category=cj" + }, + { + "data_id": 1145447, + "indicator_id": 79, + "time_period": "至10月24日", + "full_time_period": null, + "previous": "19.4", + "consensus": "19.9", + "actual": "19.4", + "revised": "19.5", + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周初请失业金人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145447", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12396", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145447&category=cj" + }, + { + "data_id": 1145458, + "indicator_id": 211, + "time_period": "9月", + "full_time_period": null, + "previous": "3.00", + "consensus": "3.10", + "actual": "3.1", + "revised": null, + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心PCE物价指数年率", + "country": "美国", + "star": 4, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145458", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145458&category=cj" + }, + { + "data_id": 1145445, + "indicator_id": 216, + "time_period": "第三季度", + "full_time_period": null, + "previous": "2.20", + "consensus": "3.2", + "actual": "3.4", + "revised": null, + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "实际GDP年化季率初值", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145445", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145445&category=cj" + }, + { + "data_id": 1145453, + "indicator_id": 96, + "time_period": "至10月17日", + "full_time_period": null, + "previous": "172.8", + "consensus": "172", + "actual": "170.1", + "revised": "172.2", + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周续请失业金人数", + "country": "美国", + "star": 2, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145453&category=cj" + }, + { + "data_id": 1145459, + "indicator_id": 395, + "time_period": "至10月29日", + "full_time_period": null, + "previous": "2.50", + "consensus": "2.50", + "actual": "2.5", + "revised": null, + "pub_time": "2026-10-29 21:15", + "actual_time": "2026-10-29 21:15", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "欧洲央行存款机制利率", + "country": "欧元区", + "star": 4, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145459", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793279700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145459&category=cj" + }, + { + "data_id": 1145457, + "indicator_id": 214, + "time_period": "9月", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.30", + "actual": "0.3", + "revised": "0.3", + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心PCE物价指数月率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145457", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145457&category=cj" + }, + { + "data_id": 1145456, + "indicator_id": 212, + "time_period": "9月", + "full_time_period": null, + "previous": "3.40", + "consensus": "3.60", + "actual": "3.6", + "revised": null, + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PCE物价指数年率", + "country": "美国", + "star": 2, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145456&category=cj" + }, + { + "data_id": 1145455, + "indicator_id": 213, + "time_period": "9月", + "full_time_period": null, + "previous": "0.30", + "consensus": "0.50", + "actual": "0.5", + "revised": null, + "pub_time": "2026-10-29 20:30", + "actual_time": "2026-10-29 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PCE物价指数月率", + "country": "美国", + "star": 2, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793277000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145455&category=cj" + }, + { + "data_id": 1145460, + "indicator_id": 753, + "time_period": "至10月29日", + "full_time_period": null, + "previous": "2.65", + "consensus": "2.65", + "actual": "2.65", + "revised": null, + "pub_time": "2026-10-29 21:15", + "actual_time": "2026-10-29 21:15", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "欧洲央行主要再融资利率", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145460", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793279700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145460&category=cj" + }, + { + "data_id": 1145461, + "indicator_id": 419, + "time_period": "至10月29日", + "full_time_period": null, + "previous": "2.90", + "consensus": "2.90", + "actual": "2.9", + "revised": null, + "pub_time": "2026-10-29 21:15", + "actual_time": "2026-10-29 21:15", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "欧洲央行边际贷款利率", + "country": "欧元区", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793279700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145461&category=cj" + } + ] + }, + "2026-11-04": { + "date": "2026-11-04", + "list": [ + { + "data_id": 1178816, + "indicator_id": 78, + "time_period": "10月", + "full_time_period": null, + "previous": "9", + "consensus": "7.5", + "actual": null, + "revised": null, + "pub_time": "2026-11-04 21:15", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "ADP就业人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178816", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12432", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793798100, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178816&category=cj" + }, + { + "data_id": 1178813, + "indicator_id": 930, + "time_period": "10月", + "full_time_period": null, + "previous": "3.80", + "consensus": "3.90", + "actual": null, + "revised": null, + "pub_time": "2026-11-04 18:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178813", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793786400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178813&category=cj" + }, + { + "data_id": 1178815, + "indicator_id": 413, + "time_period": "10月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.30", + "actual": null, + "revised": null, + "pub_time": "2026-11-04 18:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178815", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793786400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178815&category=cj" + }, + { + "data_id": 1179887, + "indicator_id": 759, + "time_period": "10月", + "full_time_period": null, + "previous": "2.2", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-04 18:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心CPI年率初值", + "country": "欧元区", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793786400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179887&category=cj" + }, + { + "data_id": 1178824, + "indicator_id": 111, + "time_period": "至10月30日", + "full_time_period": null, + "previous": "-189.2", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-04 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178824", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793806200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178824&category=cj" + }, + { + "data_id": 1178826, + "indicator_id": 114, + "time_period": "至10月30日", + "full_time_period": null, + "previous": "387", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-04 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793806200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178826&category=cj" + }, + { + "data_id": 1178827, + "indicator_id": 113, + "time_period": "至10月30日", + "full_time_period": null, + "previous": "-146.2", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-04 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793806200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178827&category=cj" + } + ] + }, + "event:2025-01-08": { + "date": "2025-01-08", + "list": [ + { + "id": 1091279, + "event_time": "2025-01-08 00:00", + "country": "美国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "美国消费性电子展(CES)举办。", + "determine": 1, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": null, + "event_time_unix": 1736265600, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": "待定" + }, + { + "id": 1093343, + "event_time": "2025-01-08 00:00", + "country": "美国", + "region": null, + "star": 3, + "people": "特朗普", + "category": "cj", + "event_content": "特朗普在海湖庄园举行新闻发布会。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [ + { + "link": "https://xnews.jin10.com/details/158277", + "tags": [], + "type": "url", + "title": "特朗普提出多项惊人战略", + "is_free": 0, + "dateRange": [] + } + ], + "mini_app": null, + "event_time_unix": 1736265600, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null + }, + { + "id": 1093333, + "event_time": "2025-01-08 10:00", + "country": "中国", + "region": null, + "star": 3, + "people": null, + "category": "cj", + "event_content": "国新办举行国务院政策例行吹风会,介绍加力扩围实施“两新”政策有关情况。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [ + { + "link": "http://www.china.com.cn/app/template/amucsite/web/webLive.html#3818", + "tags": [], + "type": "url", + "title": "网上直播", + "is_free": 0, + "dateRange": [] + }, + { + "link": "https://zfxxgk.ndrc.gov.cn/web/iteminfo.jsp?id=20470", + "tags": [], + "type": "url", + "title": "2025年以旧换新“二十七条”公布", + "is_free": 0 + } + ], + "mini_app": null, + "event_time_unix": 1736301600, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/中国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/中国@2x.png", + "time_status": null + }, + { + "id": 1093334, + "event_time": "2025-01-08 14:00", + "country": "中国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "国新办就2025年春运形势和工作安排举行发布会。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [ + { + "link": "http://www.china.com.cn/app/template/amucsite/web/webLive.html#3817", + "tags": [], + "type": "url", + "title": "网上直播", + "is_free": 0, + "dateRange": [] + } + ], + "mini_app": null, + "event_time_unix": 1736316000, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/中国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/中国@2x.png", + "time_status": null + }, + { + "id": 1091287, + "event_time": "2025-01-08 18:00", + "country": "英国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "英国央行副行长伍兹和英国央行官员David Bailey参加英国金融服务竞争力和成长目标听证会。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&data_type=1&id=63&type=3", + "icon": "https://cdn-news.jin10.com/assets/common/7cf84551-8bad-4619-acfd-a14210fa04d9.png/lite", + "name": "伍兹", + "star": 2, + "thumb": "https://cdn-news.jin10.com/assets/common/0b834aea-8a35-4fb9-bfad-02e424cdea42.jpg/lite", + "title": "英国央行副行长伍兹", + "status": 1, + "secType": 3, + "objectID": 63, + "data_type": 1, + "show_name": "伍兹", + "sub_title": "" + } + ], + "event_time_unix": 1736330400, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null + }, + { + "id": 1093267, + "event_time": "2025-01-08 21:00", + "country": "法国", + "region": null, + "star": 3, + "people": null, + "category": "cj", + "event_content": "美联储理事沃勒在经合组织的会议上就经济前景发表讲话。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&data_type=1&id=14&type=3", + "icon": "https://cdn-news.jin10.com/assets/common/7cf84551-8bad-4619-acfd-a14210fa04d9.png/lite", + "name": "沃勒", + "star": 3, + "thumb": "https://cdn-news.jin10.com/assets/common/babda962-a446-4c78-8278-1b3ec5e6866a.jpg/lite", + "title": "美联储理事沃勒", + "status": 1, + "secType": 3, + "objectID": 14, + "data_type": 1, + "show_name": "沃勒", + "sub_title": "" + } + ], + "event_time_unix": 1736341200, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null + } + ] + }, + "event:2026-11-04": { + "date": "2026-11-04", + "list": [ + { + "id": 1189563, + "event_time": "2026-11-04 00:00", + "country": "中国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "第二十四届中国半导体封装测试技术与市场年会于11月4日-6日在南京丰大国际大酒店举行。", + "determine": 1, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [ + { + "link": "https://flash.jin10.com/detail/20261027141930569800", + "tags": [], + "type": "url", + "title": "半导体封测年会将于11月4日至6日举行 多家上市公司参会", + "is_free": 0, + "dateRange": [] + } + ], + "mini_app": null, + "event_time_unix": 1793721600, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/中国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/中国@2x.png", + "time_status": "待定" + }, + { + "id": 1189987, + "event_time": "2026-11-04 01:00", + "country": "德国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "欧洲央行管委内格尔发表讲话。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&data_type=1&auth=&id=52&type=3", + "auth": "", + "desc": null, + "icon": "", + "name": "内格尔", + "star": 2, + "thumb": "https://img.jin10.com/mp/26/10/71FlrQVnyNYqgyhvgyu0O.png/lite", + "title": "欧洲央行管委兼德国央行行长内格尔", + "status": 1, + "secType": 3, + "objectID": 52, + "thumb_sm": "https://img.jin10.com/mp/26/10/okU6wFgrBT4JqrSA_FC2_.png/lite", + "data_type": 1, + "show_name": "内格尔", + "sub_title": "" + } + ], + "event_time_unix": 1793725200, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null + }, + { + "id": 1189616, + "event_time": "2026-11-04 09:20", + "country": "中国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "今日有1510亿元7天期逆回购到期。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&category=central_banks&id=550001&type=4", + "desc": null, + "icon": "", + "thumb": "https://img.jin10.com/mp/25/07/1l0eNzhVAWfOGgxVlc7OV.jpg", + "title": "中国央行数据", + "status": 1, + "secType": 4, + "category": "central_banks", + "objectID": 550001, + "thumb_sm": "https://img.jin10.com/mp/25/07/1l0eNzhVAWfOGgxVlc7OV.jpg/lite", + "show_name": "中国央行数据" + } + ], + "event_time_unix": 1793755200, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/中国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/中国@2x.png", + "time_status": null + }, + { + "id": 1189962, + "event_time": "2026-11-04 10:00", + "country": "中国", + "region": null, + "star": 1, + "people": null, + "category": "cj", + "event_content": "国务院台湾事务办公室举行新闻发布会。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [ + { + "link": "http://www.china.com.cn/zhibo/content_118754833.htm", + "tags": [], + "type": "url", + "title": "网上直播", + "is_free": 0, + "dateRange": [] + } + ], + "mini_app": null, + "event_time_unix": 1793757600, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/中国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/中国@2x.png", + "time_status": null + }, + { + "id": 1148505, + "event_time": "2026-11-04 16:30", + "country": "瑞典", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "瑞典央行公布利率决议。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&data_type=3&desc=&id=140&type=3", + "desc": "", + "icon": "https://img.jin10.com/mp/25/07/aAntVLG3bmK3mD6PVoAx6.png", + "name": "瑞典央行", + "star": 1, + "thumb": "https://img.jin10.com/mp/25/10/ZxGF7DP_9Qf_5sjJgzlvZ.jpg", + "title": "瑞典央行", + "status": 1, + "secType": 3, + "objectID": 140, + "thumb_sm": "https://img.jin10.com/mp/25/10/QHrzjwi6IhsvcCOR7jw5g.png/lite", + "data_type": 3, + "show_name": "瑞典央行", + "sub_title": "" + } + ], + "event_time_unix": 1793781000, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/瑞典.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/瑞典@2x.png", + "time_status": null + }, + { + "id": 1189619, + "event_time": "2026-11-04 18:00", + "country": "瑞典", + "region": null, + "star": 1, + "people": null, + "category": "cj", + "event_content": "瑞典央行行长特登召开货币政策新闻发布会。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&data_type=1&auth=&id=72&type=3", + "auth": "", + "desc": null, + "icon": "", + "name": "特登", + "star": 2, + "thumb": "https://img.jin10.com/mp/26/10/9N0jhOrBw5xLd8v9jf-F9.png/lite", + "title": "瑞典央行行长特登", + "status": 1, + "secType": 3, + "objectID": 72, + "thumb_sm": "https://img.jin10.com/mp/26/10/9hxyx_wZVqSkKjA2Ve1YC.png/lite", + "data_type": 1, + "show_name": "特登", + "sub_title": "" + } + ], + "event_time_unix": 1793786400, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/瑞典.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/瑞典@2x.png", + "time_status": null + }, + { + "id": 1189620, + "event_time": "2026-11-04 21:20", + "country": "德国", + "region": null, + "star": 2, + "people": null, + "category": "cj", + "event_content": "欧洲央行首席经济学家连恩发表讲话。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&data_type=1&auth=&id=38&type=3", + "auth": "", + "desc": null, + "icon": "", + "name": "连恩", + "star": 2, + "thumb": "https://img.jin10.com/mp/26/10/3r1YYvdwOu6boCQ47nP4F.png/lite", + "title": "欧洲央行首席经济学家连恩", + "status": 1, + "secType": 3, + "objectID": 38, + "thumb_sm": "https://img.jin10.com/mp/26/10/5pRQ2ZskCsiqznBYXBdf8.png/lite", + "data_type": 1, + "show_name": "连恩", + "sub_title": "" + } + ], + "event_time_unix": 1793798400, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null + }, + { + "id": 1189944, + "event_time": "2026-11-04 21:30", + "country": "美国", + "region": null, + "star": 3, + "people": null, + "category": "cj", + "event_content": "美国财政部发布季度再融资公告。", + "determine": 0, + "note": null, + "relate": null, + "emergencies": 0, + "vip_resource": [], + "mini_app": null, + "event_time_unix": 1793799000, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null + } + ] + }, + "holiday:2026-11-03": { + "date": "2026-11-03", + "list": "eJyVz8FKw0AQBuB3mfM2m01MrXvyPUTCslmT1OxuSTYQESH2ooIXERoPPZpbwWsovk2TxceQpAXPnv+Z75+5uoc0AkrIWbBYBAgiZgRQ8FxvPiNk5vqAgOtSmfwOKNimtdsdIBAVT5iKRaiYHOf7btvvdz9fj8Nz1+8/7ce7fakBwSm2m6fhdWObdtSYEbGeOL4EBLkoTKj0VNt/vw3duu9q27SHej2lkuW3QFWZZcfrwlKlFVByfuHP/cBzXQQ3GYvDMs+AQmLMqqAY80g5y1QR1+FaYlYUwhQ4lTHmWkqtCjzu4OM/zkrF8KeEXvVvKIlO1qVXTdzD9S9e+4bH" + }, + "holiday:2026-11-04": { + "date": "2026-11-04", + "list": "eJyLjgUAARUAuQ==" + }, + "2026-08-28": { + "date": "2026-08-28", + "list": [ + { + "data_id": 1184153, + "indicator_id": 8112, + "time_period": "8月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.70", + "actual": "0.7", + "revised": null, + "pub_time": "2026-08-28 14:45", + "actual_time": "2026-08-28 14:46", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184153", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1787899500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184153&category=cj" + }, + { + "data_id": 1184158, + "indicator_id": 829, + "time_period": "第二季度", + "full_time_period": null, + "previous": "0.70", + "consensus": "0.70", + "actual": "0.5", + "revised": null, + "pub_time": "2026-08-28 14:45", + "actual_time": "2026-08-28 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP年率终值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184158", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1787899500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184158&category=cj" + }, + { + "data_id": 1184154, + "indicator_id": 8111, + "time_period": "8月", + "full_time_period": null, + "previous": "2.10", + "consensus": "2.40", + "actual": "2.4", + "revised": null, + "pub_time": "2026-08-28 14:45", + "actual_time": "2026-08-28 14:46", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1787899500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184154&category=cj" + }, + { + "data_id": 1184157, + "indicator_id": 830, + "time_period": "第二季度", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.20", + "actual": "0", + "revised": "-0.2", + "pub_time": "2026-08-28 14:45", + "actual_time": "2026-08-28 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP季率终值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1787899500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184157&category=cj" + }, + { + "data_id": 1184152, + "indicator_id": 8113, + "time_period": "8月", + "full_time_period": null, + "previous": "2.40", + "consensus": "2.60", + "actual": "2.7", + "revised": null, + "pub_time": "2026-08-28 14:45", + "actual_time": "2026-08-28 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率初值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1787899500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184152&category=cj" + }, + { + "data_id": 1184151, + "indicator_id": 8114, + "time_period": "8月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.70", + "actual": "0.8", + "revised": null, + "pub_time": "2026-08-28 14:45", + "actual_time": "2026-08-28 14:47", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率初值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1787899500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184151&category=cj" + }, + { + "data_id": 1184177, + "indicator_id": 208, + "time_period": "8月", + "full_time_period": null, + "previous": "51", + "consensus": "51", + "actual": "51.7", + "revised": null, + "pub_time": "2026-08-28 22:00", + "actual_time": "2026-08-28 22:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "密歇根大学消费者信心指数终值", + "country": "美国", + "star": 4, + "unit": null, + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184177", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12283", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1787925600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184177&category=cj" + } + ] + }, + "2026-11-25": { + "date": "2026-11-25", + "list": [ + { + "data_id": 1189766, + "indicator_id": 270, + "time_period": "第三季度", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 15:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "季调后GDP季率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1795590000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1189766&category=cj" + }, + { + "data_id": 1189768, + "indicator_id": 842, + "time_period": "第三季度", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 15:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "工作日调整后GDP年率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1795590000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1189768&category=cj" + }, + { + "data_id": 1179203, + "indicator_id": 144, + "time_period": "第三季度", + "full_time_period": null, + "previous": "3.40", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "实际GDP年化季率修正值", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1179203", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179203&category=cj" + }, + { + "data_id": 1179207, + "indicator_id": 79, + "time_period": "至11月21日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周初请失业金人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1179207", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12396", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179207&category=cj" + }, + { + "data_id": 1179202, + "indicator_id": 211, + "time_period": "10月", + "full_time_period": null, + "previous": "3.10", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心PCE物价指数年率", + "country": "美国", + "star": 4, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1179202", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179202&category=cj" + }, + { + "data_id": 1179201, + "indicator_id": 214, + "time_period": "10月", + "full_time_period": null, + "previous": "0.30", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心PCE物价指数月率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1179201", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179201&category=cj" + }, + { + "data_id": 1179200, + "indicator_id": 212, + "time_period": "10月", + "full_time_period": null, + "previous": "3.60", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PCE物价指数年率", + "country": "美国", + "star": 2, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179200&category=cj" + }, + { + "data_id": 1179199, + "indicator_id": 213, + "time_period": "10月", + "full_time_period": null, + "previous": "0.50", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PCE物价指数月率", + "country": "美国", + "star": 2, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179199&category=cj" + }, + { + "data_id": 1179209, + "indicator_id": 96, + "time_period": "至11月14日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周续请失业金人数", + "country": "美国", + "star": 2, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795613400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179209&category=cj" + }, + { + "data_id": 1179215, + "indicator_id": 111, + "time_period": "至11月20日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1179215", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795620600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179215&category=cj" + }, + { + "data_id": 1179217, + "indicator_id": 114, + "time_period": "至11月20日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795620600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179217&category=cj" + }, + { + "data_id": 1179218, + "indicator_id": 113, + "time_period": "至11月20日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-25 23:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1795620600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179218&category=cj" + } + ] + }, + "2026-09-30": { + "date": "2026-09-30", + "list": [ + { + "data_id": 1184886, + "indicator_id": 385, + "time_period": "第二季度", + "full_time_period": null, + "previous": "1.20", + "consensus": "1.2", + "actual": "1.4", + "revised": null, + "pub_time": "2026-09-30 14:00", + "actual_time": "2026-09-30 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP年率终值", + "country": "英国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184886", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1790748000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184886&category=cj" + }, + { + "data_id": 1184885, + "indicator_id": 658, + "time_period": "第二季度", + "full_time_period": null, + "previous": "0.40", + "consensus": "0.40", + "actual": "0.5", + "revised": null, + "pub_time": "2026-09-30 14:00", + "actual_time": "2026-09-30 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP季率终值", + "country": "英国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1790748000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184885&category=cj" + }, + { + "data_id": 1184892, + "indicator_id": 8112, + "time_period": "9月", + "full_time_period": null, + "previous": "0.70", + "consensus": "-0.6", + "actual": "-0.3", + "revised": null, + "pub_time": "2026-09-30 14:45", + "actual_time": "2026-09-30 14:46", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184892", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1790750700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184892&category=cj" + }, + { + "data_id": 1184893, + "indicator_id": 8111, + "time_period": "9月", + "full_time_period": null, + "previous": "2.40", + "consensus": "2.8", + "actual": "3", + "revised": null, + "pub_time": "2026-09-30 14:45", + "actual_time": "2026-09-30 14:46", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1790750700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184893&category=cj" + }, + { + "data_id": 1184891, + "indicator_id": 8113, + "time_period": "9月", + "full_time_period": null, + "previous": "2.60", + "consensus": "3.1", + "actual": "3.4", + "revised": null, + "pub_time": "2026-09-30 14:45", + "actual_time": "2026-09-30 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率初值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1790750700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184891&category=cj" + }, + { + "data_id": 1184890, + "indicator_id": 8114, + "time_period": "9月", + "full_time_period": null, + "previous": "0.70", + "consensus": "-0.5", + "actual": "-0.4", + "revised": null, + "pub_time": "2026-09-30 14:45", + "actual_time": "2026-09-30 14:46", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率初值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1790750700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184890&category=cj" + }, + { + "data_id": 1184909, + "indicator_id": 847, + "time_period": "9月", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.5", + "actual": "0.6", + "revised": null, + "pub_time": "2026-09-30 20:00", + "actual_time": "2026-09-30 20:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "德国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184909", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1790769600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184909&category=cj" + }, + { + "data_id": 1184910, + "indicator_id": 846, + "time_period": "9月", + "full_time_period": null, + "previous": "2.90", + "consensus": "3.1", + "actual": "3.3", + "revised": null, + "pub_time": "2026-09-30 20:00", + "actual_time": "2026-09-30 20:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1790769600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184910&category=cj" + }, + { + "data_id": 1184912, + "indicator_id": 839, + "time_period": "9月", + "full_time_period": null, + "previous": "2.90", + "consensus": "3.2", + "actual": "3.3", + "revised": null, + "pub_time": "2026-09-30 20:00", + "actual_time": "2026-09-30 20:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1790769600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184912&category=cj" + }, + { + "data_id": 1184911, + "indicator_id": 840, + "time_period": "9月", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.5", + "actual": "0.6", + "revised": null, + "pub_time": "2026-09-30 20:00", + "actual_time": "2026-09-30 20:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1790769600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184911&category=cj" + }, + { + "data_id": 1184922, + "indicator_id": 872, + "time_period": "第二季度", + "full_time_period": null, + "previous": "1.50", + "consensus": "1.5", + "actual": "2.2", + "revised": null, + "pub_time": "2026-09-30 20:30", + "actual_time": "2026-09-30 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "实际GDP年化季率终值", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184922", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790771400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184922&category=cj" + }, + { + "data_id": 1184913, + "indicator_id": 78, + "time_period": "9月", + "full_time_period": null, + "previous": "3.8", + "consensus": "7", + "actual": "9", + "revised": "3.6", + "pub_time": "2026-09-30 20:15", + "actual_time": "2026-09-30 20:15", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "ADP就业人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184913", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12432", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790770500, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184913&category=cj" + }, + { + "data_id": 1184921, + "indicator_id": 211, + "time_period": "8月", + "full_time_period": null, + "previous": "3.30", + "consensus": "3.3", + "actual": "3", + "revised": "3", + "pub_time": "2026-09-30 20:30", + "actual_time": "2026-09-30 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心PCE物价指数年率", + "country": "美国", + "star": 4, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184921", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790771400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184921&category=cj" + }, + { + "data_id": 1184919, + "indicator_id": 214, + "time_period": "8月", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.30", + "actual": "0.2", + "revised": "0.1", + "pub_time": "2026-09-30 20:30", + "actual_time": "2026-09-30 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心PCE物价指数月率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184919", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790771400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184919&category=cj" + }, + { + "data_id": 1184918, + "indicator_id": 212, + "time_period": "8月", + "full_time_period": null, + "previous": "3.70", + "consensus": "3.70", + "actual": "3.4", + "revised": "3.4", + "pub_time": "2026-09-30 20:30", + "actual_time": "2026-09-30 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PCE物价指数年率", + "country": "美国", + "star": 2, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790771400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184918&category=cj" + }, + { + "data_id": 1184917, + "indicator_id": 213, + "time_period": "8月", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.40", + "actual": "0.3", + "revised": "0.1", + "pub_time": "2026-09-30 20:30", + "actual_time": "2026-09-30 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PCE物价指数月率", + "country": "美国", + "star": 2, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790771400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184917&category=cj" + }, + { + "data_id": 1184927, + "indicator_id": 111, + "time_period": "至9月25日", + "full_time_period": null, + "previous": "296.9", + "consensus": "-26.4", + "actual": "92.2", + "revised": null, + "pub_time": "2026-09-30 22:30", + "actual_time": "2026-09-30 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1184927", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790778600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184927&category=cj" + }, + { + "data_id": 1184929, + "indicator_id": 114, + "time_period": "至9月25日", + "full_time_period": null, + "previous": "-168.6", + "consensus": "-48.5", + "actual": "-168.4", + "revised": null, + "pub_time": "2026-09-30 22:30", + "actual_time": "2026-09-30 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790778600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184929&category=cj" + }, + { + "data_id": 1184930, + "indicator_id": 113, + "time_period": "至9月25日", + "full_time_period": null, + "previous": "-42.8", + "consensus": "-19", + "actual": "-225.1", + "revised": null, + "pub_time": "2026-09-30 22:30", + "actual_time": "2026-09-30 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1790778600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184930&category=cj" + } + ] + }, + "2026-10-23": { + "date": "2026-10-23", + "list": [ + { + "data_id": 1186899, + "indicator_id": 208, + "time_period": "10月", + "full_time_period": null, + "previous": "46.3", + "consensus": "46.3", + "actual": "48", + "revised": null, + "pub_time": "2026-10-23 22:00", + "actual_time": "2026-10-23 22:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "密歇根大学消费者信心指数终值", + "country": "美国", + "star": 4, + "unit": null, + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1186899", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12283", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792764000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186899&category=cj" + } + ] + }, + "2026-10-09": { + "date": "2026-10-09", + "list": [ + { + "data_id": 1186757, + "indicator_id": 198, + "time_period": "10月", + "full_time_period": null, + "previous": "48.1", + "consensus": "47.8", + "actual": "46.3", + "revised": null, + "pub_time": "2026-10-09 22:00", + "actual_time": "2026-10-09 22:00", + "determine": 0, + "measure": null, + "max_label": { + "type": "min", + "label": "历史最低" + }, + "ai_interpretation": null, + "indicator_name": "密歇根大学消费者信心指数初值", + "country": "美国", + "star": 3, + "unit": null, + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1186757", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12283", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1791554400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186757&category=cj" + } + ] + }, + "2026-08-25": { + "date": "2026-08-25", + "list": [ + { + "data_id": 1184060, + "indicator_id": 270, + "time_period": "第二季度", + "full_time_period": null, + "previous": "0.20", + "consensus": "0.2", + "actual": "0.3", + "revised": null, + "pub_time": "2026-08-25 14:00", + "actual_time": "2026-08-25 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "季调后GDP季率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1787637600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184060&category=cj" + }, + { + "data_id": 1184062, + "indicator_id": 842, + "time_period": "第二季度", + "full_time_period": null, + "previous": "0.90", + "consensus": "0.9", + "actual": "1", + "revised": null, + "pub_time": "2026-08-25 14:00", + "actual_time": "2026-08-25 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "工作日调整后GDP年率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1787637600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1184062&category=cj" + } + ] + }, + "2026-11-12": { + "date": "2026-11-12", + "list": [ + { + "data_id": 1178947, + "indicator_id": 390, + "time_period": "第三季度", + "full_time_period": null, + "previous": "1.40", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-12 15:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP年率初值", + "country": "英国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178947", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1794466800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178947&category=cj" + }, + { + "data_id": 1178946, + "indicator_id": 391, + "time_period": "第三季度", + "full_time_period": null, + "previous": "0.50", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-12 15:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP季率初值", + "country": "英国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1794466800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178946&category=cj" + }, + { + "data_id": 1178962, + "indicator_id": 79, + "time_period": "至11月7日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-12 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周初请失业金人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178962", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12396", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794490200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178962&category=cj" + }, + { + "data_id": 1178964, + "indicator_id": 96, + "time_period": "至10月31日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-12 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周续请失业金人数", + "country": "美国", + "star": 2, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794490200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178964&category=cj" + } + ] + }, + "2026-10-20": { + "date": "2026-10-20", + "list": [ + { + "data_id": 1186848, + "indicator_id": 649, + "time_period": "8月", + "full_time_period": null, + "previous": "4.90", + "consensus": "5", + "actual": "4.7", + "revised": null, + "pub_time": "2026-10-20 14:00", + "actual_time": "2026-10-20 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "三个月ILO失业率", + "country": "英国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1186848", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1792476000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186848&category=cj" + } + ] + }, + "2026-11-13": { + "date": "2026-11-13", + "list": [ + { + "data_id": 1189723, + "indicator_id": 568, + "time_period": "10月", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率终值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1189723", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1794555900, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1189723&category=cj" + }, + { + "data_id": 1189724, + "indicator_id": 567, + "time_period": "10月", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率终值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1794555900, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1189724&category=cj" + }, + { + "data_id": 1189722, + "indicator_id": 569, + "time_period": "10月", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率终值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1794555900, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1189722&category=cj" + }, + { + "data_id": 1189721, + "indicator_id": 570, + "time_period": "10月", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率终值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1794555900, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1189721&category=cj" + }, + { + "data_id": 1178968, + "indicator_id": 111, + "time_period": "至11月6日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 01:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178968", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794502800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178968&category=cj" + }, + { + "data_id": 1178970, + "indicator_id": 114, + "time_period": "至11月6日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 01:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794502800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178970&category=cj" + }, + { + "data_id": 1178971, + "indicator_id": 113, + "time_period": "至11月6日", + "full_time_period": null, + "previous": null, + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 01:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794502800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178971&category=cj" + }, + { + "data_id": 1179000, + "indicator_id": 178, + "time_period": "10月", + "full_time_period": null, + "previous": "6.30", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PPI年率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1179000", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794576600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1179000&category=cj" + }, + { + "data_id": 1178998, + "indicator_id": 177, + "time_period": "10月", + "full_time_period": null, + "previous": "1.30", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-13 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PPI月率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178998", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12168", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1794576600, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178998&category=cj" + } + ] + }, + "2026-11-05": { + "date": "2026-11-05", + "list": [ + { + "data_id": 1178852, + "indicator_id": 638, + "time_period": "至11月5日", + "full_time_period": null, + "previous": "3.75", + "consensus": "4.00", + "actual": null, + "revised": null, + "pub_time": "2026-11-05 20:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "央行利率决定", + "country": "英国", + "star": 4, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178852", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12632", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1793880000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178852&category=cj" + }, + { + "data_id": 1178855, + "indicator_id": 79, + "time_period": "至10月31日", + "full_time_period": null, + "previous": "19.4", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-05 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周初请失业金人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1178855", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12396", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793885400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178855&category=cj" + }, + { + "data_id": 1178857, + "indicator_id": 96, + "time_period": "至10月24日", + "full_time_period": null, + "previous": "170.1", + "consensus": null, + "actual": null, + "revised": null, + "pub_time": "2026-11-05 21:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周续请失业金人数", + "country": "美国", + "star": 2, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1793885400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1178857&category=cj" + } + ] + }, + "2026-10-21": { + "date": "2026-10-21", + "list": [ + { + "data_id": 1145355, + "indicator_id": 348, + "time_period": "9月", + "full_time_period": null, + "previous": "0.50", + "consensus": "0.3", + "actual": "0.4", + "revised": null, + "pub_time": "2026-10-21 14:00", + "actual_time": "2026-10-21 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率", + "country": "英国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145355", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1792562400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145355&category=cj" + }, + { + "data_id": 1145356, + "indicator_id": 344, + "time_period": "9月", + "full_time_period": null, + "previous": "3.10", + "consensus": "3.4", + "actual": "3.5", + "revised": null, + "pub_time": "2026-10-21 14:00", + "actual_time": "2026-10-21 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率", + "country": "英国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1792562400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145356&category=cj" + }, + { + "data_id": 1145357, + "indicator_id": 349, + "time_period": "9月", + "full_time_period": null, + "previous": "2.60", + "consensus": "2.7", + "actual": "2.8", + "revised": null, + "pub_time": "2026-10-21 14:00", + "actual_time": "2026-10-21 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心CPI年率", + "country": "英国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/英国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/英国@2x.png", + "time_status": null, + "pub_time_unix": 1792562400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145357&category=cj" + }, + { + "data_id": 1145366, + "indicator_id": 111, + "time_period": "至10月16日", + "full_time_period": null, + "previous": "-855.7", + "consensus": "-82", + "actual": "-306", + "revised": null, + "pub_time": "2026-10-21 22:30", + "actual_time": "2026-10-21 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145366", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792593000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145366&category=cj" + }, + { + "data_id": 1145368, + "indicator_id": 114, + "time_period": "至10月16日", + "full_time_period": null, + "previous": "351.8", + "consensus": "-44.3", + "actual": "-295.2", + "revised": null, + "pub_time": "2026-10-21 22:30", + "actual_time": "2026-10-21 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792593000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145368&category=cj" + }, + { + "data_id": 1145369, + "indicator_id": 113, + "time_period": "至10月16日", + "full_time_period": null, + "previous": "-252.7", + "consensus": "-116.2", + "actual": "167.5", + "revised": null, + "pub_time": "2026-10-21 22:30", + "actual_time": "2026-10-21 22:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792593000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145369&category=cj" + } + ] + }, + "2026-10-13": { + "date": "2026-10-13", + "list": [ + { + "data_id": 1145265, + "indicator_id": 845, + "time_period": "9月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.60", + "actual": "0.6", + "revised": null, + "pub_time": "2026-10-13 14:00", + "actual_time": "2026-10-13 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率终值", + "country": "德国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145265", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1791871200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145265&category=cj" + }, + { + "data_id": 1145266, + "indicator_id": 844, + "time_period": "9月", + "full_time_period": null, + "previous": "3.30", + "consensus": "3.30", + "actual": "3.3", + "revised": null, + "pub_time": "2026-10-13 14:00", + "actual_time": "2026-10-13 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1791871200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145266&category=cj" + }, + { + "data_id": 1145268, + "indicator_id": 837, + "time_period": "9月", + "full_time_period": null, + "previous": "3.30", + "consensus": "3.30", + "actual": "3.3", + "revised": null, + "pub_time": "2026-10-13 14:00", + "actual_time": "2026-10-13 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1791871200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145268&category=cj" + }, + { + "data_id": 1145267, + "indicator_id": 838, + "time_period": "9月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.60", + "actual": "0.6", + "revised": null, + "pub_time": "2026-10-13 14:00", + "actual_time": "2026-10-13 14:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率终值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1791871200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145267&category=cj" + } + ] + }, + "2026-10-30": { + "date": "2026-10-30", + "list": [ + { + "data_id": 1186984, + "indicator_id": 831, + "time_period": "第三季度", + "full_time_period": null, + "previous": "0.50", + "consensus": "0.3", + "actual": "-0.1", + "revised": null, + "pub_time": "2026-10-30 14:30", + "actual_time": "2026-10-30 14:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP年率初值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1186984", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1793341800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186984&category=cj" + }, + { + "data_id": 1186989, + "indicator_id": 8112, + "time_period": "10月", + "full_time_period": null, + "previous": "-0.30", + "consensus": "0.2", + "actual": "0.4", + "revised": null, + "pub_time": "2026-10-30 15:45", + "actual_time": "2026-10-30 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1186989", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1793346300, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186989&category=cj" + }, + { + "data_id": 1186990, + "indicator_id": 8111, + "time_period": "10月", + "full_time_period": null, + "previous": "3.10", + "consensus": "3.2", + "actual": "3.4", + "revised": null, + "pub_time": "2026-10-30 15:45", + "actual_time": "2026-10-30 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1793346300, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186990&category=cj" + }, + { + "data_id": 1186988, + "indicator_id": 8113, + "time_period": "10月", + "full_time_period": null, + "previous": "3.40", + "consensus": "3.6", + "actual": "3.8", + "revised": null, + "pub_time": "2026-10-30 15:45", + "actual_time": "2026-10-30 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率初值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1793346300, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186988&category=cj" + }, + { + "data_id": 1186987, + "indicator_id": 8114, + "time_period": "10月", + "full_time_period": null, + "previous": "-0.30", + "consensus": "0.3", + "actual": "0.4", + "revised": null, + "pub_time": "2026-10-30 15:45", + "actual_time": "2026-10-30 15:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率初值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1793346300, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186987&category=cj" + }, + { + "data_id": 1188822, + "indicator_id": 246, + "time_period": "第三季度", + "full_time_period": null, + "previous": "1", + "consensus": "1.2", + "actual": "1.3", + "revised": null, + "pub_time": "2026-10-30 17:00", + "actual_time": "2026-10-30 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "未季调GDP年率初值", + "country": "德国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1188822", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12184", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793350800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188822&category=cj" + }, + { + "data_id": 1188821, + "indicator_id": 245, + "time_period": "第三季度", + "full_time_period": null, + "previous": "0.3", + "consensus": "0.1", + "actual": "0.2", + "revised": null, + "pub_time": "2026-10-30 17:00", + "actual_time": "2026-10-30 17:00", + "determine": 0, + "measure": "EPS", + "max_label": null, + "ai_interpretation": null, + "indicator_name": "季调后GDP季率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793350800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188821&category=cj" + }, + { + "data_id": 1188823, + "indicator_id": 843, + "time_period": "第三季度", + "full_time_period": null, + "previous": "1", + "consensus": "1.1", + "actual": "1.2", + "revised": null, + "pub_time": "2026-10-30 17:00", + "actual_time": "2026-10-30 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "工作日调整后GDP年率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793350800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188823&category=cj" + }, + { + "data_id": 1145479, + "indicator_id": 762, + "time_period": "第三季度", + "full_time_period": null, + "previous": "1.20", + "consensus": "1.1", + "actual": "1.3", + "revised": null, + "pub_time": "2026-10-30 18:00", + "actual_time": "2026-10-30 18:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP年率初值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145479", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793354400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145479&category=cj" + }, + { + "data_id": 1145478, + "indicator_id": 763, + "time_period": "第三季度", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.20", + "actual": "0.4", + "revised": null, + "pub_time": "2026-10-30 18:00", + "actual_time": "2026-10-30 18:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP季率初值", + "country": "欧元区", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793354400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145478&category=cj" + }, + { + "data_id": 1145481, + "indicator_id": 847, + "time_period": "10月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.1", + "actual": "-0.2", + "revised": null, + "pub_time": "2026-10-30 21:00", + "actual_time": "2026-10-30 21:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率初值", + "country": "德国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145481", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793365200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145481&category=cj" + }, + { + "data_id": 1145482, + "indicator_id": 846, + "time_period": "10月", + "full_time_period": null, + "previous": "3.30", + "consensus": "3", + "actual": "2.8", + "revised": null, + "pub_time": "2026-10-30 21:00", + "actual_time": "2026-10-30 21:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793365200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145482&category=cj" + }, + { + "data_id": 1145484, + "indicator_id": 839, + "time_period": "10月", + "full_time_period": null, + "previous": "3.30", + "consensus": "3.1", + "actual": "2.8", + "revised": null, + "pub_time": "2026-10-30 21:00", + "actual_time": "2026-10-30 21:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793365200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145484&category=cj" + }, + { + "data_id": 1145483, + "indicator_id": 840, + "time_period": "10月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.1", + "actual": "-0.1", + "revised": null, + "pub_time": "2026-10-30 21:00", + "actual_time": "2026-10-30 21:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率初值", + "country": "德国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/德国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/德国@2x.png", + "time_status": null, + "pub_time_unix": 1793365200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145483&category=cj" + }, + { + "data_id": 1186983, + "indicator_id": 832, + "time_period": "第三季度", + "full_time_period": null, + "previous": "0.00", + "consensus": "0.1", + "actual": "-0.1", + "revised": null, + "pub_time": "2026-10-30 14:30", + "actual_time": "2026-10-30 14:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "GDP季率初值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1793341800, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186983&category=cj" + }, + { + "data_id": 1145480, + "indicator_id": 396, + "time_period": "9月", + "full_time_period": null, + "previous": "6.40", + "consensus": "6.40", + "actual": "6.5", + "revised": "6.5", + "pub_time": "2026-10-30 18:00", + "actual_time": "2026-10-30 18:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "失业率", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145480", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1793354400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145480&category=cj" + } + ] + }, + "2026-10-15": { + "date": "2026-10-15", + "list": [ + { + "data_id": 1186797, + "indicator_id": 568, + "time_period": "9月", + "full_time_period": null, + "previous": "-0.30", + "consensus": "-0.3", + "actual": "-0.3", + "revised": null, + "pub_time": "2026-10-15 14:45", + "actual_time": "2026-10-15 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率终值", + "country": "法国", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1186797", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1792046700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186797&category=cj" + }, + { + "data_id": 1186798, + "indicator_id": 567, + "time_period": "9月", + "full_time_period": null, + "previous": "3.00", + "consensus": "3", + "actual": "3.1", + "revised": null, + "pub_time": "2026-10-15 14:45", + "actual_time": "2026-10-15 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率终值", + "country": "法国", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1792046700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186798&category=cj" + }, + { + "data_id": 1186796, + "indicator_id": 569, + "time_period": "9月", + "full_time_period": null, + "previous": "3.40", + "consensus": "3.4", + "actual": "3.4", + "revised": null, + "pub_time": "2026-10-15 14:45", + "actual_time": "2026-10-15 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI年率终值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1792046700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186796&category=cj" + }, + { + "data_id": 1186795, + "indicator_id": 570, + "time_period": "9月", + "full_time_period": null, + "previous": "-0.40", + "consensus": "-0.4", + "actual": "-0.3", + "revised": null, + "pub_time": "2026-10-15 14:45", + "actual_time": "2026-10-15 14:45", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "调和CPI月率终值", + "country": "法国", + "star": 1, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/法国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/法国@2x.png", + "time_status": null, + "pub_time_unix": 1792046700, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1186795&category=cj" + }, + { + "data_id": 1145303, + "indicator_id": 79, + "time_period": "至10月10日", + "full_time_period": null, + "previous": "19.7", + "consensus": "20", + "actual": "19.9", + "revised": "19.8", + "pub_time": "2026-10-15 20:30", + "actual_time": "2026-10-15 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周初请失业金人数", + "country": "美国", + "star": 4, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145303", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12396", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792067400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145303&category=cj" + }, + { + "data_id": 1145308, + "indicator_id": 178, + "time_period": "9月", + "full_time_period": null, + "previous": "5.40", + "consensus": "5.3", + "actual": "6.3", + "revised": "5.6", + "pub_time": "2026-10-15 20:30", + "actual_time": "2026-10-15 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PPI年率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145308", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792067400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145308&category=cj" + }, + { + "data_id": 1145306, + "indicator_id": 177, + "time_period": "9月", + "full_time_period": null, + "previous": "0.40", + "consensus": "0.50", + "actual": "1.3", + "revised": "0.5", + "pub_time": "2026-10-15 20:30", + "actual_time": "2026-10-15 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "PPI月率", + "country": "美国", + "star": 3, + "unit": "%", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1145306", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12168", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792067400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145306&category=cj" + }, + { + "data_id": 1145305, + "indicator_id": 96, + "time_period": "至10月3日", + "full_time_period": null, + "previous": "171.6", + "consensus": "171", + "actual": "171", + "revised": "171.4", + "pub_time": "2026-10-15 20:30", + "actual_time": "2026-10-15 20:30", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周续请失业金人数", + "country": "美国", + "star": 2, + "unit": "万人", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792067400, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1145305&category=cj" + } + ] + }, + "holiday:2025-01-01": { + "date": "2025-01-01", + "list": "eJzdmF1P2lAcxr/LuUYpyPvVErOLXewTLAshwBAHxfCSsCxLAFd5VXQbdiIbWwJ1LBpB2ECG+GHs/1Cu/AoLPcLIdkwjaczqbV+e0+d3/nn6tM9eI78HOQyMwWQ3WXTI44p6kQMZGaN5iTEsMQakQ+5QjI2GXyEHGg134PAC6ZA37l5zsT6vk3UFJ9eP+hdiv4azCaRDN4eA28S8AMk0rlQnIq6o1xeSVdzrSIfC3kjUyYbk1cTBHvRSYi+B+fpVIiWfDbrCL5GDjQUC5KGcMdYfRw6DdcVssTA2htGhFwGXzxkLB5ADrUWjGxGHXu/2sMvrftbALLtDQb0rEvFGI3p/0Kd3h4LBEBvRT+7RExvLG6wP/VFxGuN3Flrz3Gg9MsZluTe6OZ4248I8cedsfMBNkH58j7OJ60Hmyerj60H2b7wKYDHPi7089DbFgTDKnsNOFZ+d40pV6hzBbmbUF4D7BrXGzTrlFCQrD4a/mTEqzvO42qDzHwv70PwgnSYh05vtwh3h3/dUEzPqUCVaVKr2FSWqUr5FpyoOBFwStEWVmFGHKtH6l6qdsRhMC2eFlPsEpS2SEqtPF04JqfNjnN6D1uerRJKsReLiKpGE2j5upeFdQUp9xYX0fICQbSQx8rACxM5YVhQDBA/bUDuCTEPsl2kxnspCc6Aw8P/lK3LemDqc5xWptJXrBwy7t7wu2yXgmsDlR0JRW/FCLKlDmGhR2dqUJ7ldorOFbmf8a0dbVIkZleZW1qJStSuGNn5bJENP68ytNnBNjYGd+lGJ7VSOhtfKKOPl67hyTGsavYrYP9Zk8MqWVMIra1HZGhW/TaT65ah4Msoe0Opx4xiG3XG6oK3pnVlSqc5N5aiETYwSYbF3ckvkFpv4fKjF6SWW1MFLtKhsLfaF2eKz77jbgmIOCjz5ZTEpuIcXsMWRXquVYb4n1DbrwqjHwgHunarB83pQgG1+slEFHnb3ph8d7TF/8mBQG4xWs2IBzn3B+UuoHdFCo1YWBwLUytrK5JkllTrwVI5OWLkG7zel+iVwTVp0TM9pi/DssVUqFVM5OmHFuJB+5mCbpyZzG7Y4bbElZlSqE7LWjOrz3w7EZrg=" + }, + "2026-10-16": { + "date": "2026-10-16", + "list": [ + { + "data_id": 1188253, + "indicator_id": 111, + "time_period": "至10月9日", + "full_time_period": null, + "previous": "-318.6", + "consensus": "-28.8", + "actual": "-855.7", + "revised": null, + "pub_time": "2026-10-16 00:00", + "actual_time": "2026-10-16 00:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA原油库存", + "country": "美国", + "star": 4, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1188253", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12315", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792080000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188253&category=cj" + }, + { + "data_id": 1188255, + "indicator_id": 114, + "time_period": "至10月9日", + "full_time_period": null, + "previous": "38.2", + "consensus": "-80", + "actual": "351.8", + "revised": null, + "pub_time": "2026-10-16 00:00", + "actual_time": "2026-10-16 00:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA汽油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792080000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188255&category=cj" + }, + { + "data_id": 1188256, + "indicator_id": 113, + "time_period": "至10月9日", + "full_time_period": null, + "previous": "-4.2", + "consensus": "-156", + "actual": "-252.7", + "revised": null, + "pub_time": "2026-10-16 00:00", + "actual_time": "2026-10-16 00:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "当周EIA精炼油库存", + "country": "美国", + "star": 1, + "unit": "万桶", + "affect": 1, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/美国.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/美国@2x.png", + "time_status": null, + "pub_time_unix": 1792080000, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188256&category=cj" + }, + { + "data_id": 1188272, + "indicator_id": 764, + "time_period": "9月", + "full_time_period": null, + "previous": "3.80", + "consensus": "3.80", + "actual": "3.8", + "revised": null, + "pub_time": "2026-10-16 17:00", + "actual_time": "2026-10-16 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI年率终值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1188272", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1792141200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188272&category=cj" + }, + { + "data_id": 1188273, + "indicator_id": 8125, + "time_period": "9月", + "full_time_period": null, + "previous": "0.60", + "consensus": "0.60", + "actual": "0.6", + "revised": null, + "pub_time": "2026-10-16 17:00", + "actual_time": "2026-10-16 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "CPI月率终值", + "country": "欧元区", + "star": 3, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [ + { + "url": "https://miniprogram.jin10.com?path=/pages_sub/dataReview/dataReview&id=1188273", + "show_name": "数据回顾", + "icon": "" + } + ], + "video_url": "https://v.jin10.com/details.html?id=12128", + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1792141200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188273&category=cj" + }, + { + "data_id": 1188299, + "indicator_id": 622, + "time_period": "9月", + "full_time_period": null, + "previous": "2.2", + "consensus": "2.2", + "actual": "2.2", + "revised": null, + "pub_time": "2026-10-16 17:00", + "actual_time": "2026-10-16 17:00", + "determine": 0, + "measure": null, + "max_label": null, + "ai_interpretation": null, + "indicator_name": "核心CPI年率终值", + "country": "欧元区", + "star": 2, + "unit": "%", + "affect": 0, + "show_affect": 1, + "show": 1, + "type": 0, + "vip_resource": null, + "mini_app": [], + "video_url": null, + "flag_url": "https://cdn.jin10.com/assets/img/commons/flag/欧元区.png", + "flag_url_2x": "https://cdn.jin10.com/assets/img/commons/flag/hd/欧元区@2x.png", + "time_status": null, + "pub_time_unix": 1792141200, + "detail_url": "https://miniprogram.jin10.com?path=/pages/redirect/redirect&type=6&id=1188299&category=cj" + } + ] + } +} diff --git a/backend/tests/test_calendar.py b/backend/tests/test_calendar.py index 019c4d6..f16bf7c 100644 --- a/backend/tests/test_calendar.py +++ b/backend/tests/test_calendar.py @@ -260,15 +260,15 @@ def test_calendar_api_requires_auth_validates_range_and_exposes_unavailable_work assert (await client.get("/api/calendar/999/revisions")).status_code == 404 assert (await client.get("/api/calendar/999/history?indicator=cpi_mom")).status_code == 404 assert (await client.get("/api/calendar/999/series?indicator=cpi_mom")).status_code == 404 - session.get.return_value = SimpleNamespace(family="cpi") + session.get.return_value = SimpleNamespace(family="cpi", source="bls") assert (await client.get("/api/calendar/1/history?indicator=ppi_mom")).status_code == 400 assert (await client.get("/api/calendar/1/history?indicator=cpi_mom&limit=121")).status_code == 422 assert (await client.get("/api/calendar/1/series?indicator=ppi_mom")).status_code == 400 assert (await client.get("/api/calendar/1/series?indicator=cpi_mom&limit=121")).status_code == 422 - session.get.return_value = SimpleNamespace(family="cpi", period=None) + session.get.return_value = SimpleNamespace(family="cpi", source="bls", period=None) assert (await client.get("/api/calendar/1/series?indicator=cpi_mom")).json()["data"] == [] handler = AsyncMock(return_value=[{"actual": "0.3", "first_recorded_actual": "0.4", "revised": True}]) - monkeypatch.setattr(calendar, "indicator_history", handler) + monkeypatch.setattr(calendar, "calendar_history", handler) history = await client.get("/api/calendar/1/history?indicator=cpi_mom") assert history.status_code == 200 and history.json()["data"][0]["revised"] is True assert handler.await_args.args[2:] == ("cpi_mom", 36) diff --git a/backend/tests/test_jin10.py b/backend/tests/test_jin10.py index 56b9973..d72c57f 100644 --- a/backend/tests/test_jin10.py +++ b/backend/tests/test_jin10.py @@ -25,6 +25,7 @@ def qr_env(trading, monkeypatch): app.include_router(jin10.router) app.dependency_overrides[get_session] = lambda: session monkeypatch.setattr(jin10, "check_session", AsyncMock()) + monkeypatch.setattr(jin10.redis, "get", AsyncMock(return_value=None)) monkeypatch.setattr(jin10_auth, "generate_qr", AsyncMock(return_value="private-qr-token")) monkeypatch.setattr(jin10_auth, "poll_qr", AsyncMock(return_value=("pending", None))) def authorize(): diff --git a/backend/tests/test_jin10_calendar.py b/backend/tests/test_jin10_calendar.py new file mode 100644 index 0000000..3b19b70 --- /dev/null +++ b/backend/tests/test_jin10_calendar.py @@ -0,0 +1,207 @@ +"""Calendar tests use public fixtures and isolate all account, database and upstream I/O.""" +import asyncio +from copy import deepcopy +from datetime import UTC, date, datetime, timedelta +import json +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest + +from alphapulse.calendar.jin10 import normalize, period + +FIXTURE = json.loads((Path(__file__).parent / "fixtures/calendar/jin10.json").read_text()) + + +def entries(day): + return normalize(deepcopy(FIXTURE[day]), "data", date.fromisoformat(day)) + + +@pytest.mark.parametrize("day", [key for key in FIXTURE if ":" not in key]) +def test_real_values_and_timestamp_seconds(day): + result = entries(day) + assert len(result) == len(FIXTURE[day]["list"]) + for entry, raw in zip(result, FIXTURE[day]["list"], strict=True): + assert entry.at.timestamp() == raw["pub_time_unix"] + value = entry.values[f"jin10_{raw['indicator_id']}"] + assert value["actual"] == raw["actual"] + assert value["previous"] == raw["previous"] + assert value["previous_revised"] == raw["revised"] + assert "affect" not in value + + +def test_events_distinguish_midnight_from_undetermined_time(): + result = normalize(FIXTURE["event:2025-01-08"], "event", date(2025, 1, 8)) + ces = next(r for r in result if "CES" in r.title) + trump = next(r for r in result if "特朗普" in r.title) + assert ces.values["_jin10"]["precision"] == "tentative" + assert trump.values["_jin10"]["precision"] == "minute" + assert ces.at.hour == trump.at.hour == 16 + + +def test_daily_holiday_and_empty_day(): + entry, = normalize(FIXTURE["holiday:2026-11-03"], "holiday", date(2026, 11, 3)) + assert entry.title == "文化日" + assert entry.values["_jin10"]["exchange"] == "东京证券交易所" + assert entry.values["_jin10"]["precision"] == "day" + assert normalize(FIXTURE["holiday:2026-11-04"], "holiday", date(2026, 11, 4)) == [] + + +@pytest.mark.parametrize("change", ["date", "duplicate", "timestamp", "number", "country"]) +def test_invalid_contract_does_not_become_empty_success(change): + body = deepcopy(FIXTURE["2026-11-04"]) + if change == "date": body["date"] = "2026-11-05" + if change == "duplicate": body["list"] *= 2 + if change == "timestamp": body["list"][0]["pub_time_unix"] += 3600 + if change == "number": body["list"][0]["actual"] = 0 + if change == "country": body["list"][0]["country"] = 7 + with pytest.raises(ValueError): + normalize(body, "data", date(2026, 11, 4)) + + +@pytest.mark.parametrize("payload", [None, [], "not base64", "eJwDAAAAAAE=", "eJyLjo0FAAEVALk=garbage"]) +def test_broken_holiday_payload_is_not_accepted(payload): + with pytest.raises(ValueError): + normalize({"date": "2026-11-03", "list": payload}, "holiday", date(2026, 11, 3)) + + +def test_non_numeric_text_and_unknown_units_are_preserved(): + body = deepcopy(FIXTURE["2026-11-04"]) + body["list"] = body["list"][:1] + body["list"][0].update(actual="4.00-4.25", unit=None, consensus=None, revised=None) + entry, = normalize(body, "data", date(2026, 11, 4)) + assert entry.values["jin10_78"]["actual"] == "4.00-4.25" + assert entry.values["_jin10"]["unit"] is None + assert entry.values["jin10_78"]["forecast"] is None + + +def test_periods_cross_year_without_inventing_unrecognized_periods(): + at = datetime(2025, 1, 8, 15, tzinfo=UTC) + assert period("12月", at) == "2024-12" + assert period("至12月30日", at) == "2024-12-30" + assert period("第四季度", at) == "2024-Q4" + assert period("1-12月", at) is None + assert period("至1月9日", at) is None + + +def stored(entry, id=11, now=None): + return SimpleNamespace(id=id, source="jin10", family="jin10", title=entry.title, scheduled_at=entry.at, + schedule_url="https://rili.jin10.com/day/2026-11-04/", release_url=None, + values=deepcopy(entry.values), first_received_at=now, last_checked_at=now, + schedule_checked_at=now, error=None, revision_no=1) + + +def test_late_forecast_is_displayed_but_not_a_historical_surprise(trading): + from alphapulse.calendar.jin10_view import jin10_item + row = stored(entries("2026-11-04")[0]) + row.values["jin10_78"].update(actual="9", forecast_before_release=False, observed_at={"forecast": "2026-11-05T00:00:00+00:00"}) + item = jin10_item(row, datetime(2026, 11, 5, tzinfo=UTC)) + assert item["forecast"] == "7.5" + assert not item["forecast_before_release"] + assert item["impact_basis"] == "previous" and item["impact"] == "neutral" + + +def test_exact_merge_prefers_official_fields_and_preserves_previous_revisions(trading): + from alphapulse.calendar.jin10_view import merge_items + entry = next(e for e in entries("2026-10-29") if "jin10_79" in e.values) + extra = stored(entry) + extra.values["jin10_79"].update(observed_at={"forecast": "2026-10-28T00:00:00+00:00"}, forecast_before_release=True) + official = SimpleNamespace(source="dol", family="initial_claims", stage=None, period="2026-10-24", scheduled_at=entry.at, + first_received_at=entry.at, schedule_checked_at=entry.at, release_url="https://www.dol.gov/claims.pdf", + schedule_url="https://oui.doleta.gov/unemploy/archive.asp") + original = {"release_id": 1, "indicator": "initial_claims", "actual": "19.40", "previous": "19.5", "forecast": None, + "status": "released", "impact": "uncertain", "explanation": "official", "revision_no": 1, "scheduled_at": entry.at} + item, = merge_items([(official, original)], [extra], entry.at) + assert item["release_id"] == 1 and item["actual"] == "19.40" + assert item["forecast"] == "19.9" and item["related_release_id"] == extra.id + assert item["previous_original"] == "19.4" and item["previous_revised"] == "19.5" + assert item["field_sources"]["actual"]["source"] == "dol" + assert item["field_sources"]["forecast"]["source"] == "jin10" + assert original["forecast"] is None + official.period = "2026-10-17" + assert len(merge_items([(official, original)], [extra], entry.at)) == 2 + official.period = "2026-10-24" + extra.values["_jin10"]["unit"] = "人" + assert len(merge_items([(official, original)], [extra], entry.at)) == 2 + + +def test_cushing_is_not_total_commercial_crude(trading): + from alphapulse.calendar.jin10_view import definition + crude = next(e for e in entries("2025-01-08") if "jin10_111" in e.values) + cushing = next(e for e in entries("2025-01-08") if "jin10_953" in e.values) + assert definition(crude.values["_jin10"]) == ("eia_stocks", "eia_crude", None) + assert definition(cushing.values["_jin10"]) is None + + +def test_repeated_storage_preserves_observation_and_first_received_time(trading): + from alphapulse.calendar.jin10_store import save_entry + now = datetime(2026, 11, 3, tzinfo=UTC) + entry = entries("2026-11-04")[0] + row = stored(entry) + row.values["jin10_78"].update(observed_at={"previous": now.isoformat(), "forecast": now.isoformat()}, forecast_before_release=True) + old = deepcopy(row.values) + session = SimpleNamespace(scalar=AsyncMock(return_value=row), add=lambda r: pytest.fail("Unexpected revision")) + asyncio.run(save_entry(session, entry, 1, now + timedelta(hours=1))) + assert row.values == old and row.first_received_at is None + + +def test_reconnect_rejects_obsolete_inflight_capture(trading, monkeypatch): + from alphapulse.calendar import jin10_worker as worker + now = datetime.now(UTC) + class Session: + async def __aenter__(self): return self + async def __aexit__(self, *args): pass + scalar = AsyncMock(return_value=SimpleNamespace(token_encrypted=b"new", connected_at=now)) + commit = AsyncMock() + session = Session() + monkeypatch.setattr(worker, "SessionLocal", lambda: session) + monkeypatch.setattr(worker, "save_raw", AsyncMock()) + response = {"data": FIXTURE["2026-11-04"], "event": FIXTURE["event:2026-11-04"], "holiday": FIXTURE["holiday:2026-11-04"]} + with pytest.raises(worker.ConnectionChanged): + asyncio.run(worker.persist_day(date(2026, 11, 4), response, b"old", now, now)) + worker.save_raw.assert_not_awaited() + session.commit.assert_not_awaited() + + +def test_every_verified_mapping_has_a_public_fixture_with_the_exact_definition(trading): + from alphapulse.calendar.jin10_view import MAPPINGS, definition + seen = set() + for day in (key for key in FIXTURE if ":" not in key): + for entry in entries(day): + meta = entry.values["_jin10"] + if definition(meta): + seen.add((meta["country_code"], int(meta["indicator"].removeprefix("jin10_")), meta["name"])) + expected = {(country, id_, name) for country, mapping in MAPPINGS.items() for id_, name in mapping} + assert expected <= seen + + +def test_holidays_preserve_distinct_exchanges_within_the_same_country(): + rows = normalize(FIXTURE["holiday:2025-01-01"], "holiday", date(2025, 1, 1)) + assert len(rows) == 17 + us = [row for row in rows if row.values["_jin10"]["country_code"] == "US"] + assert len(us) > 1 and len({row.identity for row in us}) == len(us) + + +def test_connection_status_never_accepts_a_previous_connection_heartbeat(trading): + from alphapulse.api.jin10 import public_status + now = datetime.now(UTC) + row = SimpleNamespace(token_encrypted=b"synthetic", connected_at=now, error=None) + sync = {"at": now.isoformat(), "state": "ready", "connected_at": now.isoformat(), "last_success_at": now.isoformat()} + assert public_status(row, sync)["sync_state"] == "ready" + sync["connected_at"] = (now - timedelta(seconds=1)).isoformat() + assert public_status(row, sync)["sync_state"] == "awaiting_validation" + row.token_encrypted = None + sync["connected_at"] = now.isoformat() + assert public_status(row, sync)["sync_state"] == "disconnected" + + +def test_account_login_does_not_prove_calendar_access(): + from alphapulse.calendar.jin10_browser import validate_access + from alphapulse.calendar.jin10_auth import Jin10Error + response = {"data": FIXTURE["2025-01-08"], "event": FIXTURE["event:2025-01-08"], + "holiday": {"date": "2025-01-08", "list": FIXTURE["holiday:2026-11-04"]["list"]}} + validate_access(response) + response["data"] = {"date": "2025-01-08", "list": []} + with pytest.raises(Jin10Error, match="读取权限"): + validate_access(response) diff --git a/backend/uv.lock b/backend/uv.lock index 19f0cb6..f9c6771 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -178,6 +178,7 @@ dependencies = [ { name = "httpx" }, { name = "openai" }, { name = "pillow" }, + { name = "playwright" }, { name = "pydantic-settings" }, { name = "pyjwt" }, { name = "pypdf" }, @@ -205,6 +206,7 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, { name = "openai", specifier = ">=3.30.0" }, { name = "pillow", specifier = ">=13.0.0" }, + { name = "playwright", specifier = ">=1.64.1,<2" }, { name = "pydantic-settings", specifier = ">=2.15.0" }, { name = "pyjwt", specifier = ">=2.15.1" }, { name = "pypdf", specifier = ">=6.21.2" }, @@ -1686,6 +1688,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/15/1633010b26e88e872c93b67c0b6c5e174fb74cb6fb5c1472b4d51d4a8f22/platformdirs-4.13.0-py3-none-any.whl", hash = "sha256:3dbcf4cd708f21cf876c4eaa90e58412bc4f033d87143f41b1493ff77c25b7e1", size = 32724, upload-time = "2026-10-11T02:05:22.776Z" }, ] +[[package]] +name = "playwright" +version = "1.64.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet" }, + { name = "pyee" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/c8/cc677856d89a0c077673b40ab915ed5597bf1347b205cabe70121c431ca9/playwright-1.64.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:29453ff76e25c17d0b45658cacd6b92e9eec46e8db21e134fb3b7143df50f013", size = 44508971, upload-time = "2026-10-22T16:29:58.12Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/572bbbe60a5787ca61a84bad934c3ce19c08c2bc9c0e9781bf0d99cad1a6/playwright-1.64.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:59f7b041284ce8d1b72098ae50ca48a2d83692eec25154ab5ba79d4b6b4fe8a9", size = 43155232, upload-time = "2026-10-22T16:30:02.415Z" }, + { url = "https://files.pythonhosted.org/packages/44/fa/468f313a56d33a6420705147c8d47bef558f087856edf38c5b13eef8aa6c/playwright-1.64.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:6a6f99adad1ae40e6ee62efe89785db861ce7ddd57651521cb31894183cc4ae3", size = 44508977, upload-time = "2026-10-22T16:30:07.632Z" }, + { url = "https://files.pythonhosted.org/packages/15/b9/e481a34572933b5cf179d5637c98da488c732ee339dff5b73a0624d2e42b/playwright-1.64.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:577b8d88778c245cfbd145ae17cf53044f084b1b46c55c69081d4069d56057e2", size = 48471931, upload-time = "2026-10-22T16:30:12.831Z" }, + { url = "https://files.pythonhosted.org/packages/0c/11/545f25e47ec54e1e8a2892ef9994fc2b298f909ab37b88578e3a1f87ac2e/playwright-1.64.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a29fda44725e11b0b625c4bff6458a1f6dbee97b88c9788d12e18a8344458c2d", size = 48150994, upload-time = "2026-10-22T16:30:18.297Z" }, + { url = "https://files.pythonhosted.org/packages/33/87/8ff7af99387ee72ae9b46a66ece7a0c0aa63f73320ecf6dd3dd087fa840a/playwright-1.64.1-py3-none-win32.whl", hash = "sha256:d3c48b31da426c2f3562d0919cb9ea4e5415f6dbd29d7211fa4d66cca24fbd2f", size = 38801828, upload-time = "2026-10-22T16:30:23.154Z" }, + { url = "https://files.pythonhosted.org/packages/96/0a/64db6851aa1852a4f76be28eea71d4e2bf9f3ce493ee2e66939426729046/playwright-1.64.1-py3-none-win_amd64.whl", hash = "sha256:5e240a79464434c9260957b9ef59a5808e02e96e3fcabdc717fd7485260b9730", size = 38801836, upload-time = "2026-10-22T16:30:28.092Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4b/d7ddde9467a5fcfe6a0d3c954732332e98a4f4a9025fd42c8d091c16e5f9/playwright-1.64.1-py3-none-win_arm64.whl", hash = "sha256:983ccd085453ea5a7588193657fc349a6ffbf7bf7ba663fd58dc81e4aa3af9d9", size = 34693875, upload-time = "2026-10-22T16:30:32.724Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -1932,6 +1953,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/0e/9ee7bc0b48ec45d93b302fa2d787830dca4dc454d31a237faa5815995988/PyDispatcher-2.0.7-py3-none-any.whl", hash = "sha256:96543bea04115ffde08f851e1d45cacbfd1ee866ac42127d9b476dc5aefa7de0", size = 12040, upload-time = "2023-02-17T20:11:11.991Z" }, ] +[[package]] +name = "pyee" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/f1/fdedc2c75c3e31a330659c85e5793bb18b3397981fbf0844c6dee5b18926/pyee-14.0.0.tar.gz", hash = "sha256:76dd0f4314ecd27f02dc73589dea7fd3853f9b6176d8ef9b122860657e3602de", size = 98760, upload-time = "2026-08-13T04:26:11.021Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/12/5347938b1f9a6453f0dbdfcc3e2388a1320ef9b9ec17fbefbc4ab647ea98/pyee-14.0.0-py3-none-any.whl", hash = "sha256:3ac2d3229a9677f7de2c33d7f52fe25b638a46b19c413fea2edc8c6d0a644e4d", size = 15553, upload-time = "2026-08-13T04:26:09.916Z" }, +] + [[package]] name = "pygments" version = "2.21.0" diff --git a/deploy/Dockerfile.backend b/deploy/Dockerfile.backend index ec50330..d862556 100644 --- a/deploy/Dockerfile.backend +++ b/deploy/Dockerfile.backend @@ -11,6 +11,8 @@ FROM base AS check RUN uv sync --locked && uv run pytest && uvx ruff==0.14.14 check --select F,E9,B src tests FROM base AS runtime +ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright +RUN playwright install --with-deps --only-shell chromium ARG APP_VERSION=source ARG APP_COMMIT=unknown ENV APP_VERSION=$APP_VERSION APP_COMMIT=$APP_COMMIT diff --git a/deploy/README.md b/deploy/README.md index ec5502c..a7c2c44 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -53,3 +53,15 @@ The additive `c4d7e9f021ab` migration must be reviewed and separately authorized Risk grades are zero (no explicit risk found) through five stars. Pending, failed and insufficient information are distinct states. Article evidence, model identity and timestamps remain available in event history. The rolling dashboard windows use publication/progress timestamps; retrospective analysis is not a historical forecast. Model/market outages retain the prior outlook with an outdated marker. No event classification changes positions or order execution. Candidate retrieval retains the article's previous event and ranks other recent events by headline relevance and severity. Each dashboard window selects up to 100 events independently. Context packing includes source text and the tool schema; oversized articles fail individually while other articles stay queued. News details refresh pending analysis every 15 seconds while visible and stop after completion or leaving the page. Deleting an enabled news provider returns 409; pausing analysis allows deletion and clears the saved provider reference. + +## Global economic calendar + +The independent `jin10-calendar` service runs Chromium and Jin10's official browser SDK. It collects the macroeconomic calendar only: economic data, important events and exchange holidays. The backend runtime image installs the matching Playwright headless Chromium and OS libraries; no browser is started in the API, official calendar, news, model or trading workers. + +After the separately authorized migrations through `a8b1cd465ef3` and deployment, an administrator connects Jin10 in Settings using the Jin10 App QR flow. The account token is encrypted with the existing server encryption key and never returned to the browser. Disconnect retains history and invalidates in-flight writes; reconnect starts a new connection generation. Browser or authentication failures stay visible instead of being recorded as empty days. + +The collector prioritizes today, checks yesterday and the next seven days, and fills missing days from 2025-01-01 Asia/Shanghai at a limited pace. A day receives a persistent coverage checkpoint only after all three category responses pass date/structure checks. Historical dates with no checkpoint are coverage gaps, not evidence that no events occurred. A restart resumes the first gap. Failures retain raw responses without advancing coverage. No historical completeness or realtime latency is guaranteed before actual collection finishes. + +Official and Jin10 observations retain separate identities and revisions. The read API merges only verified indicator IDs, definitions, units, reference periods, release stages and exact publication times. Official nonmissing fields win independently; Jin10 fills missing fields and market forecasts with explicit provenance. Unverified definitions remain distinct records. Original and revised previous values remain separate. Forecast observations captured after publication are displayed with that limitation and excluded from historical surprise judgments. Jin10's gold/USD direction flags are not BTC/ETH signals. Tentative times and all-day holidays never display an invented exact midnight time. + +CI acceptance runs `scripts/smoke_calendar.py` against the isolated database using public fixtures and synthetic account material. It never starts a browser collector or connects a real Jin10 account. Local unit tests likewise isolate all external I/O. The live database migration, credential import/reconnection, history collection and dev deployment are separate operator actions; ordinary local validation does not authorize them. diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index d0963dd..f048ec2 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -44,6 +44,11 @@ services: container_name: AlphaPulse-dev-calendar command: [python, -m, alphapulse.calendar.worker] + jin10-calendar: + <<: *backend + container_name: AlphaPulse-dev-jin10-calendar + command: [python, -m, alphapulse.calendar.jin10_worker] + web: image: ${ALPHAPULSE_WEB_IMAGE:?Set a verified web digest} container_name: AlphaPulse-dev diff --git a/scripts/ci.py b/scripts/ci.py index b20a5e1..d65d11f 100644 --- a/scripts/ci.py +++ b/scripts/ci.py @@ -181,6 +181,8 @@ def smoke(build, label): 'import urllib.request; urllib.request.urlopen("http://localhost:8000/api/health", timeout=3)') print(run('docker', 'exec', '-i', names['api'], 'python', '-', input=(ROOT / 'scripts/smoke_risk.py').read_text()), flush=True) + print(run('docker', 'exec', '-i', names['api'], 'python', '-', + input=(ROOT / 'scripts/smoke_calendar.py').read_text()), flush=True) output = run('docker', 'exec', '-i', '-e', 'SMOKE_WEB=' + names['web'], names['api'], 'python', '-', input=(ROOT / 'scripts/smoke_app.py').read_text()) print(output, flush=True) diff --git a/scripts/deploy.py b/scripts/deploy.py index 15190f9..69f9f25 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -11,7 +11,7 @@ import time from ci import IMAGES, ROOT, branch_head, locked, require, run, save INSTANCE = Path('/docker/gitea/dev/AlphaPulse') -SERVICES = ('api', 'web', 'market', 'crawler', 'calendar', 'trader', 'news-risk') +SERVICES = ('api', 'web', 'market', 'crawler', 'calendar', 'trader', 'news-risk', 'jin10-calendar') SCHEMA = ''' import asyncio, json @@ -153,7 +153,7 @@ def deploy(build, directory=INSTANCE): command = compose(directory, build) run(*command, 'config', '--quiet') run(*command, 'up', '-d', '--no-build', '--pull', 'never', '--wait', '--wait-timeout', '120', 'api', 'web') - run(*command, 'up', '-d', '--no-build', '--pull', 'never', 'market', 'crawler', 'calendar', 'trader', 'news-risk') + run(*command, 'up', '-d', '--no-build', '--pull', 'never', 'market', 'crawler', 'calendar', 'trader', 'news-risk', 'jin10-calendar') time.sleep(5) health(command, build) if previous: diff --git a/scripts/smoke_calendar.py b/scripts/smoke_calendar.py new file mode 100644 index 0000000..a86d604 --- /dev/null +++ b/scripts/smoke_calendar.py @@ -0,0 +1,105 @@ +"""Exercise calendar persistence with synthetic credentials in the isolated CI database.""" +import asyncio +from copy import deepcopy +from datetime import UTC, date, datetime, timedelta +import json +import os +from pathlib import Path + +from sqlalchemy import func, select + +from alphapulse.calendar.jin10 import normalize +from alphapulse.calendar.jin10_history import calendar_history +from alphapulse.calendar.jin10_store import next_history_day, save_entry +from alphapulse.calendar.jin10_view import merge_items +from alphapulse.calendar.jin10_worker import ConnectionChanged, persist_day +from alphapulse.calendar.parsers import Scheduled +from alphapulse.calendar.service import release_rows, save_raw, save_result, save_schedule +from alphapulse.core.crypto import encrypt +from alphapulse.core.db import SessionLocal, engine +from alphapulse.models import EconomicRelease, EconomicRevision, RawResponse, Source +from alphapulse.models.jin10 import Jin10Connection, Jin10SyncDay + + +async def verify(): + assert os.environ["DATABASE_URL"].startswith("postgresql+asyncpg://ci:ci-only@"), "Calendar acceptance requires the isolated CI database" + fixture = json.loads((Path("tests/fixtures/calendar/jin10.json")).read_text()) + now = datetime(2026, 11, 5, tzinfo=UTC) + connected = now - timedelta(days=1) + encrypted = encrypt("isolated-ci-calendar-account") + async with SessionLocal() as session: + row = await session.get(Jin10Connection, 1) + row.token_encrypted, row.connected_at = encrypted, connected + assert (await session.get(Source, "jin10")).collector == "calendar" + await session.commit() + day = date(2026, 11, 4) + responses = {"data": fixture[str(day)], "event": fixture[f"event:{day}"], "holiday": fixture[f"holiday:{day}"]} + await persist_day(day, responses, encrypted, connected, now) + async with SessionLocal() as session: + rows = list(await session.scalars(select(EconomicRelease).where(EconomicRelease.source == "jin10"))) + assert len(rows) == len(responses["data"]["list"]) + len(responses["event"]["list"]) + adp = next(r for r in rows if "jin10_78" in r.values) + assert adp.first_received_at is None + assert not adp.values["jin10_78"]["forecast_before_release"] + assert adp.values["jin10_78"]["observed_at"]["forecast"] == now.isoformat() + assert (await session.get(Jin10SyncDay, day)).counts == {"data": len(responses["data"]["list"]), "event": 8, "holiday": 0} + before = await session.scalar(select(func.count()).select_from(EconomicRevision)) + assert await next_history_day(session, date(2025, 1, 1), day) == date(2025, 1, 1) + await persist_day(day, responses, encrypted, connected, now + timedelta(minutes=1)) + async with SessionLocal() as session: + assert await session.scalar(select(func.count()).select_from(EconomicRevision)) == before + try: + await persist_day(day, responses, b"obsolete-credential", connected, now) + raise AssertionError("Obsolete connection was accepted") + except ConnectionChanged: + pass + invalid = deepcopy(responses) + invalid["event"]["date"] = "2026-11-05" + async with SessionLocal() as session: + raw_before = await session.scalar(select(func.count()).select_from(RawResponse)) + coverage_before = (await session.get(Jin10SyncDay, day)).captured_at + try: + await persist_day(day, invalid, encrypted, connected, now + timedelta(hours=1)) + raise AssertionError("Mismatched response date was accepted") + except ValueError: + pass + async with SessionLocal() as session: + assert await session.scalar(select(func.count()).select_from(RawResponse)) == raw_before + 3 + assert (await session.get(Jin10SyncDay, day)).captured_at == coverage_before + + # Join an official weekly release to the exact provider series, including its revised previous value. + claims_day = date(2026, 10, 29) + entries = normalize(fixture[str(claims_day)], "data", claims_day) + claims = next(e for e in entries if "jin10_79" in e.values) + async with SessionLocal() as session: + raw = await save_raw(session, "jin10", "https://rili.jin10.com/day/2026-10-29/", 200, {}, b"isolated-claims", now) + for entry in entries: + await save_entry(session, entry, raw, now) + official_raw = await save_raw(session, "dol", "https://www.dol.gov/claims.pdf", 200, {}, b"isolated-official", now) + event = Scheduled("isolated-claims", "initial_claims", "Official initial claims", claims.at, "scheduled", "2026-10-24") + await save_schedule(session, "dol", "https://oui.doleta.gov/unemploy/archive.asp", [event], official_raw, now, withdraw_missing=False) + official = await session.scalar(select(EconomicRelease).where(EconomicRelease.source == "dol", EconomicRelease.external_id == event.external_id)) + save_result(session, official, {"initial_claims": {"actual": "19.40", "previous": "19.5"}}, "https://www.dol.gov/claims.pdf", official_raw, now) + await session.commit() + extra = next(r for r in await session.scalars(select(EconomicRelease).where(EconomicRelease.source == "jin10")) if "jin10_79" in r.values) + item, = merge_items([(official, release_rows(official, now)[0])], [extra], now) + assert item["actual"] == "19.40" and item["forecast"] == "19.9" + assert item["previous_original"] == "19.4" and item["previous_revised"] == "19.5" + assert item["field_sources"]["actual"]["source"] == "dol" + assert item["field_sources"]["forecast"]["source"] == "jin10" + assert item["forecast_before_release"] is False + history = await calendar_history(session, official, "initial_claims", 36) + assert len(history) == 1 and history[0]["actual"] == "19.40" and history[0]["forecast"] == "19.9" + assert history[0]["first_recorded_actual"] == "19.40" + assert history[0]["first_received_at"] == now + print("Calendar acceptance passed: three categories, exact official joins, revisions, raw evidence, late forecasts, coverage and obsolete connection rejection") + + +async def main(): + try: + await verify() + finally: + await engine.dispose() + + +asyncio.run(main()) diff --git a/scripts/tests/test_cicd.py b/scripts/tests/test_cicd.py index 5bec133..6d9ce22 100644 --- a/scripts/tests/test_cicd.py +++ b/scripts/tests/test_cicd.py @@ -82,11 +82,11 @@ class BuildTests(unittest.TestCase): class RecoveryTests(unittest.TestCase): - def test_six_service_rollback_removes_new_worker_and_checks_previous_services(self): + def test_rollback_removes_new_calendar_worker_and_checks_previous_services(self): with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) ci.save(directory / 'deployment.pending.json', {}) - services = [name for name in deploy.SERVICES if name != 'news-risk'] + services = [name for name in deploy.SERVICES if name != 'jin10-calendar'] previous = {'images': {part: {'digest': 'synthetic'} for part in ci.IMAGES}} compose = 'services:\n' + ''.join(f' {name}:\n image: synthetic\n' for name in services) with patch.object(deploy, 'run', return_value='') as run, \ @@ -94,7 +94,7 @@ class RecoveryTests(unittest.TestCase): deploy.restore(directory, {'previous': previous, 'previous_compose': compose, 'previous_running': services}) update = next(call.args for call in run.call_args_list if 'up' in call.args) self.assertIn('--remove-orphans', update) - self.assertNotIn('news-risk', update) + self.assertNotIn('jin10-calendar', update) self.assertEqual(health.call_args.args[2], services) self.assertFalse((directory / 'deployment.pending.json').exists()) diff --git a/web/src/common/apis/calendar/type.ts b/web/src/common/apis/calendar/type.ts index a2df55a..3bda3fb 100644 --- a/web/src/common/apis/calendar/type.ts +++ b/web/src/common/apis/calendar/type.ts @@ -9,10 +9,10 @@ export interface CalendarItem { period: string | null stage: string | null source: string - unit: string + unit: string | null importance: number scheduled_at: string - status: "scheduled" | "waiting" | "delayed" | "released" | "withdrawn" | "error" | "historical" | "unavailable" + status: "scheduled" | "waiting" | "delayed" | "released" | "withdrawn" | "error" | "historical" | "unavailable" | "recorded" actual: string | null previous: string | null forecast: string | null @@ -26,6 +26,18 @@ export interface CalendarItem { last_checked_at: string | null error: string | null revision_no: number + kind: "data" | "event" | "holiday" + calendar_date: string + time_precision: "minute" | "day" | "tentative" + period_label: string | null + importance_source: "project" | "jin10" + previous_original: string | null + previous_revised: string | null + forecast_before_release: boolean + forecast_observed_at: string | null + field_sources: Record + related_release_id: number | null + related_revision_no: number | null } export interface CalendarData { @@ -33,15 +45,21 @@ export interface CalendarData { countries: { code: string, name: string }[] worker: { at: string, sources: Record } | null server_time: string + jin10: { at: string, state: string, error: string | null, history_next?: string | null, last_success_at: string | null } | null + coverage: { date: string, captured_at: string, counts: Record<"data" | "event" | "holiday", number> }[] } export interface CalendarRevision { + source: string revision_no: number captured_at: string - payload: { title: string, scheduled_at: string, schedule_status: string, values: Record } + payload: { title: string, scheduled_at: string, schedule_status: string, values: Record } } export interface CalendarHistory { + period_label?: string | null + forecast_before_release?: boolean + forecast_observed_at?: string | null release_id: number scheduled_at: string title: string diff --git a/web/src/common/apis/jin10/type.ts b/web/src/common/apis/jin10/type.ts index fe62dc6..190d593 100644 --- a/web/src/common/apis/jin10/type.ts +++ b/web/src/common/apis/jin10/type.ts @@ -2,7 +2,9 @@ export interface Jin10Status { connected: boolean connected_at: string | null error: string | null - sync_state: "awaiting_validation" | "disconnected" + sync_state: "awaiting_validation" | "disconnected" | "connecting" | "syncing" | "ready" | "error" + last_success_at: string | null + history_next: string | null } export interface Jin10Qr { task_id: string diff --git a/web/src/pages/calendar/components/CalendarDetail.vue b/web/src/pages/calendar/components/CalendarDetail.vue index e1d7f48..794b232 100644 --- a/web/src/pages/calendar/components/CalendarDetail.vue +++ b/web/src/pages/calendar/components/CalendarDetail.vue @@ -1,15 +1,17 @@