Add official US and European collection and calendar storage

Collect official RSS and Atom summaries, preserve historical captures, and poll macro releases with bounded cadence and Retry-After handling. Separate news collectors from the economic calendar worker.

Track statistical periods, publication stages, first observations, revisions, and current official series. Include explicit migration files and isolated archive, parsing, and import tests; no migration or import is executed by this commit.

Keep dependency declarations and lockfile together for PDF archives and the upcoming captcha integration. Verify the staged snapshot with 90 focused tests.
This commit is contained in:
eezd committed 2026-10-26 12:03:00 +08:00
1 parent 5fa1e37fd4
commit e8ca4daa0e
76 files changed
+18923 -4

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
from datetime import datetime
from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from alphapulse.core.db import Base
class EconomicRelease(Base):
__tablename__ = "economic_releases"
__table_args__ = (UniqueConstraint("source", "external_id"),)
id: Mapped[int] = mapped_column(primary_key=True)
source: Mapped[str] = mapped_column(String(16))
external_id: Mapped[str] = mapped_column(String(160))
family: Mapped[str] = mapped_column(String(16))
title: Mapped[str] = mapped_column(Text)
period: Mapped[str | None] = mapped_column(String(10))
stage: Mapped[str | None] = mapped_column(String(16))
scheduled_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
# scheduled / withdrawn;已公布由 first_received_at 判断,不把到点当成已公布。
schedule_status: Mapped[str] = mapped_column(String(16), default="scheduled")
schedule_url: Mapped[str] = mapped_column(Text)
release_url: Mapped[str | None] = mapped_column(Text)
schedule_checked_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
first_received_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_checked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
next_check_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
error: Mapped[str | None] = mapped_column(Text)
# 按指标代码保存实际值、官方报告内前值和引用;数字以十进制字符串存储。
values: Mapped[dict] = mapped_column(JSONB, default=dict)
revision_no: Mapped[int] = mapped_column(Integer, default=1)
class EconomicRevision(Base):
__tablename__ = "economic_revisions"
__table_args__ = (UniqueConstraint("release_id", "revision_no"),)
id: Mapped[int] = mapped_column(primary_key=True)
release_id: Mapped[int] = mapped_column(ForeignKey("economic_releases.id"), index=True)
revision_no: Mapped[int] = mapped_column(Integer)
payload: Mapped[dict] = mapped_column(JSONB)
raw_response_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("raw_responses.id"))
captured_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class EconomicSeriesValue(Base):
"""当前官方序列;没有历史公布时点的版本保证,不覆盖当期公告值。"""
__tablename__ = "economic_series_values"
__table_args__ = (UniqueConstraint("source", "indicator", "period", "stage"),)
id: Mapped[int] = mapped_column(primary_key=True)
source: Mapped[str] = mapped_column(String(16))
indicator: Mapped[str] = mapped_column(String(48))
period: Mapped[str] = mapped_column(String(10))
stage: Mapped[str] = mapped_column(String(16))
value: Mapped[str] = mapped_column(String(64))
captured_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
raw_response_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("raw_responses.id"))