Files
AlphaPulse/backend/src/alphapulse/news_research/models.py
T
eezd 4929a32a0a feat(news): index news packages and research persistent events
Extract reusable literal keywords once per source revision, assign complete news packages to long-lived events, and research all event members with immutable evidence and references.

Add additive research storage, vocabulary snapshots, the two-stage workspace, isolated acceptance coverage, and a paused rollout with an initial fixed 50-news production trial.
2026-11-04 12:09:58 +08:00

143 lines
5.2 KiB
Python

"""Additive storage: original news and legacy AI tables are never written here."""
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 VocabularyVersion(Base):
__tablename__ = "news_research_vocabulary_versions"
revision: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
entries: Mapped[list] = mapped_column(JSONB)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class ResearchSettings(Base):
__tablename__ = "news_research_settings"
id: Mapped[int] = mapped_column(primary_key=True)
config: Mapped[dict] = mapped_column(JSONB)
enabled: Mapped[bool] = mapped_column(default=False)
control_revision: Mapped[int] = mapped_column(
Integer, default=0, server_default="0"
)
start_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
class Package(Base):
__tablename__ = "news_research_packages"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
news_id: Mapped[int] = mapped_column(ForeignKey("news_items.id"), unique=True)
content_hash: Mapped[str] = mapped_column(String(64))
state: Mapped[str] = mapped_column(String(20), default="index_pending", index=True)
keywords: Mapped[list] = mapped_column(JSONB, default=list)
error: Mapped[str | None] = mapped_column(Text)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
class Attempt(Base):
__tablename__ = "news_research_attempts"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
package_id: Mapped[int] = mapped_column(
ForeignKey("news_research_packages.id"), index=True
)
content_hash: Mapped[str] = mapped_column(String(64))
stage: Mapped[str] = mapped_column(String(16))
state: Mapped[str] = mapped_column(String(16))
input: Mapped[dict] = mapped_column(JSONB)
output: Mapped[dict | None] = mapped_column(JSONB)
rules: Mapped[dict] = mapped_column(JSONB)
usage: Mapped[dict | None] = mapped_column(JSONB)
error: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
class Event(Base):
__tablename__ = "news_research_events"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
title: Mapped[str] = mapped_column(Text)
tags: Mapped[list] = mapped_column(JSONB, default=list)
revision: Mapped[int] = mapped_column(Integer, default=1)
state: Mapped[str] = mapped_column(String(16), default="pending", index=True)
result: Mapped[dict | None] = mapped_column(JSONB)
error: Mapped[str | None] = mapped_column(Text)
dirty_since: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
due_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
class Membership(Base):
__tablename__ = "news_research_memberships"
event_id: Mapped[int] = mapped_column(
ForeignKey("news_research_events.id"), primary_key=True
)
package_id: Mapped[int] = mapped_column(
ForeignKey("news_research_packages.id"), primary_key=True
)
evidence: Mapped[str] = mapped_column(Text)
reason: Mapped[str] = mapped_column(Text)
change_kind: Mapped[str] = mapped_column(String(20))
class Version(Base):
__tablename__ = "news_research_versions"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
event_id: Mapped[int] = mapped_column(
ForeignKey("news_research_events.id"), index=True
)
revision: Mapped[int] = mapped_column(Integer)
state: Mapped[str] = mapped_column(String(16))
input: Mapped[dict] = mapped_column(JSONB)
output: Mapped[dict | None] = mapped_column(JSONB)
rules: Mapped[dict] = mapped_column(JSONB)
usage: Mapped[dict | None] = mapped_column(JSONB)
evidence: Mapped[list] = mapped_column(JSONB, default=list)
error: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
class Reference(Base):
__tablename__ = "news_research_references"
__table_args__ = (UniqueConstraint("version_id", "target_version_id"),)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
version_id: Mapped[int] = mapped_column(
ForeignKey("news_research_versions.id"), index=True
)
target_version_id: Mapped[int] = mapped_column(
ForeignKey("news_research_versions.id")
)
role: Mapped[str] = mapped_column(String(20))
reason: Mapped[str] = mapped_column(Text)
TABLES = [
VocabularyVersion.__table__,
ResearchSettings.__table__,
Package.__table__,
Attempt.__table__,
Event.__table__,
Membership.__table__,
Version.__table__,
Reference.__table__,
]