Some checks failed
build-and-deploy / build (push) Failing after 4s
Vendor the newsPipeline scraper + summarizer into the repo and wire them into
docker-compose against the EXISTING osint-db (no second Postgres), replacing
the upstream k8s CronJobs with in-compose wall-clock loops (:00 scrape, :05
summarize).
- news/scraper: vendored Scrapy project (257 RSS feeds) + hourly loop
scheduler (run_news_scraper.py)
- news/summerizer: vendored Gemini map-reduce summarizer, cleaned:
* fix broken google-genai response handling (_extract_text, defensive)
* fix malformed INSERT/GRANT query in save_summary_to_db
* OSINT-neutral default MAP_PROMPT; futures/markets language gated behind
INCLUDE_FUTURES=0 (yfinance lazy-imported)
* env-configurable model, batch size, lookback window
+ hourly loop scheduler (run_news_summarizer.py, :05)
- alembic 003_news: idempotent articles + article_summaries tables
- API: GET /api/news and GET /api/news/summaries (+ models, schemas)
- tests/test_api_news.py: 5 DB-backed contract tests (all pass vs real PG)
- docs/news.md + .env.example updates
Both services run under the `ingest` compose profile (matching the
ingester/camera-scraper pattern) and build arm64 on the Pi via the existing
Forgejo CI workflow. telebot left out of scope (reserved env only).
57 lines
1.6 KiB
Python
57 lines
1.6 KiB
Python
"""news tables: scraped articles + LLM article_summaries
|
|
|
|
Revision ID: 003_news
|
|
Revises: 002_cameras
|
|
Create Date: 2026-08-24
|
|
"""
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa # noqa: F401
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision = '003_news'
|
|
down_revision = '002_cameras'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Idempotent DDL: the news scraper's Scrapy pipeline also issues
|
|
# `CREATE TABLE IF NOT EXISTS articles`, so either the scraper or the app
|
|
# may create these first depending on container startup order. IF NOT
|
|
# EXISTS makes both orders safe — whichever runs first wins, the other
|
|
# no-ops. Same table shapes as the upstream newsPipeline services.
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS articles (
|
|
id SERIAL PRIMARY KEY,
|
|
title TEXT,
|
|
url TEXT UNIQUE,
|
|
content TEXT,
|
|
domain TEXT,
|
|
timestamp TIMESTAMPTZ
|
|
)
|
|
"""
|
|
)
|
|
op.execute(
|
|
"CREATE INDEX IF NOT EXISTS ix_articles_timestamp ON articles (timestamp)"
|
|
)
|
|
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_summaries (
|
|
id SERIAL PRIMARY KEY,
|
|
summary_text TEXT NOT NULL,
|
|
batch_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)
|
|
"""
|
|
)
|
|
op.execute(
|
|
"CREATE INDEX IF NOT EXISTS ix_article_summaries_batch_timestamp "
|
|
"ON article_summaries (batch_timestamp)"
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute("DROP TABLE IF EXISTS article_summaries")
|
|
op.execute("DROP TABLE IF EXISTS articles")
|