92 lines
4.2 KiB
Python
92 lines
4.2 KiB
Python
import datetime as dt
|
|
|
|
from sqlalchemy import DateTime, Float, Integer, String, Text, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from .db import Base
|
|
|
|
|
|
class Article(Base):
|
|
__tablename__ = "articles"
|
|
__table_args__ = (UniqueConstraint("url", name="uq_article_url"),)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
source: Mapped[str] = mapped_column(String(128), index=True)
|
|
source_bias: Mapped[str] = mapped_column(String(64), default="")
|
|
title: Mapped[str] = mapped_column(Text)
|
|
url: Mapped[str] = mapped_column(Text, index=True)
|
|
summary: Mapped[str] = mapped_column(Text, default="")
|
|
|
|
published_at: Mapped[dt.datetime] = mapped_column(DateTime, index=True)
|
|
fetched_at: Mapped[dt.datetime] = mapped_column(DateTime, default=dt.datetime.utcnow)
|
|
|
|
location_name: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
|
country: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
lat: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
lon: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
|
|
# Rounded lat/lon grid key articles are clustered on, e.g. "31.5:34.5"
|
|
cluster_key: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
|
|
|
|
class MarketPrice(Base):
|
|
__tablename__ = "market_prices"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
symbol: Mapped[str] = mapped_column(String(32), index=True)
|
|
label: Mapped[str] = mapped_column(String(128))
|
|
category: Mapped[str] = mapped_column(String(32)) # "index" | "commodity"
|
|
price: Mapped[float] = mapped_column(Float)
|
|
currency: Mapped[str] = mapped_column(String(8), default="USD")
|
|
change_pct: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
recorded_at: Mapped[dt.datetime] = mapped_column(DateTime, index=True, default=dt.datetime.utcnow)
|
|
|
|
|
|
class MarketSpike(Base):
|
|
__tablename__ = "market_spikes"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
symbol: Mapped[str] = mapped_column(String(32), index=True)
|
|
label: Mapped[str] = mapped_column(String(128))
|
|
from_price: Mapped[float] = mapped_column(Float)
|
|
to_price: Mapped[float] = mapped_column(Float)
|
|
pct_change: Mapped[float] = mapped_column(Float)
|
|
# This instrument's recent typical (mean absolute) poll-to-poll move,
|
|
# for comparison — null if there wasn't enough price history yet to
|
|
# compute one (see market_spike_min_samples). Lets the UI show "Nx this
|
|
# instrument's normal move" instead of just the raw percentage.
|
|
baseline_volatility_pct: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
|
|
window_start: Mapped[dt.datetime] = mapped_column(DateTime)
|
|
window_end: Mapped[dt.datetime] = mapped_column(DateTime)
|
|
detected_at: Mapped[dt.datetime] = mapped_column(DateTime, index=True, default=dt.datetime.utcnow)
|
|
|
|
# JSON-encoded list of Article.id, ranked most-to-least likely relevant.
|
|
article_ids_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
# JSON-encoded [[word, count], ...] — plain word-frequency across every
|
|
# article in the window's titles, stopwords dropped. A heuristic hint at
|
|
# "likely factors," not an explanation; see textutil.py.
|
|
top_keywords_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
|
|
|
|
class ConflictEvent(Base):
|
|
__tablename__ = "conflict_events"
|
|
__table_args__ = (UniqueConstraint("source", "external_id", name="uq_conflict_event"),)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
source: Mapped[str] = mapped_column(String(32), default="acled")
|
|
external_id: Mapped[str] = mapped_column(String(64))
|
|
|
|
event_type: Mapped[str] = mapped_column(String(128))
|
|
actor1: Mapped[str] = mapped_column(String(256), default="")
|
|
actor2: Mapped[str] = mapped_column(String(256), default="")
|
|
fatalities: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
notes: Mapped[str] = mapped_column(Text, default="")
|
|
|
|
location_name: Mapped[str] = mapped_column(String(128), default="")
|
|
country: Mapped[str] = mapped_column(String(128), default="")
|
|
lat: Mapped[float] = mapped_column(Float)
|
|
lon: Mapped[float] = mapped_column(Float)
|
|
|
|
event_date: Mapped[dt.datetime] = mapped_column(DateTime, index=True)
|