Skip to content

Data Model: Article Collection

Phase: 1 Date: 2026-05-28

Entity Overview

ScraperSetting (DB)
    │  configures

ScrapeJob ──────────────── DiscoverTask
    │  fetched by                │
    ▼                            ▼ (executor)
ScrapedArticle          FetchTask
    │  transformed by

ArticleScrapedEvent ──► InMemoryEventBus

    ▼  (ProcessScrapedArticleUseCase)
Article (DB)  ◄── deduplicated via UrlHash

    └── ArxivMetadata (DB, optional)

Domain Entities

ScrapeJob

Produced by a scraper's discover(). Represents one pending fetch unit.

FieldTypeNotes
urlstrArticle URL to fetch
sourcestrHuman-readable source name (e.g. "TechCrunch")
source_typestr"rss" | "arxiv" | "blog"
topic_idUUID | NoneAssociates article with a topic
prompt_overridestr | NoneCustom LLM prompt for this source
metadatadictRaw metadata: title, description, author, published, arxiv_id, etc.

ArxivMetadata

Persisted alongside Article for arXiv papers. Optional — only created when source == "arxiv".

FieldTypeNotes
article_idUUIDFK to Article
arxiv_idstr | Nonee.g. "2401.12345"
authorslist[str]Author names
pdf_availableboolWhether PDF was successfully fetched
sectionsdictPDF section map (heading → text)

Value Objects

ScrapedArticle

Result of a successful fetch(). Immutable; not persisted directly.

FieldTypeNotes
urlstrCanonical article URL
titlestrArticle title
contentstrSanitised plain text (may be truncated)
sourcestrSource name
topic_idUUID | None
published_atdatetime | None
authorslist[str]
extradictSource-specific metadata passed through to event

UrlHash

Deterministic deduplication key.

FieldTypeNotes
valuestrSHA-256 hex of normalised URL

Normalisation rules: lowercase, strip trailing slash, remove fragment (#…).


Application Events

ArticleScrapedEvent

Published to InMemoryEventBus after a successful fetch. Consumed by ArticleScrapedHandler which invokes ProcessScrapedArticleUseCase.

FieldTypeSource
urlstrScrapedArticle.url
titlestrScrapedArticle.title
contentstrScrapedArticle.content
sourcestrScrapedArticle.source
topic_idUUID | NoneScrapedArticle.topic_id
published_atdatetime | None
metadatadictScrapedArticle.extra

Infrastructure Configuration Entities

ScraperSetting (DB)

Loaded at pipeline start from DB via ScraperSettingRepository. Drives ScraperFactory.

FieldTypeNotes
sourcestrUnique source name
source_typestr"rss" | "arxiv" | "blog"
urlstrFeed URL / API endpoint / blog listing URL
keywordslist[str] | NoneKeyword filter; None = use defaults
topic_idUUID | None
prompt_overridestr | None
enabledboolIf False, skipped at pipeline start
selectorsdict | NoneCSS selectors for blog sources
fetch_pdfboolArXiv only: whether to extract PDF

Deduplication State Machine

URL encountered during run


DedupService.find_existing(url)

   ┌────┴────┐
   │         │
  None    Article found
   │         │
   ▼         ▼
 NEW    needs_analysis(article)?

        ┌────┴────┐
        │         │
       True     False
        │         │
        ▼         ▼
  DUPLICATE_   DUPLICATE
  NEEDS_       (skip)
  ANALYSIS

Outcomes (ArticleOutcome enum):

  • NEW — stored and forwarded for analysis
  • DUPLICATE_NEEDS_ANALYSIS — not re-stored; forwarded for analysis
  • DUPLICATE — silently dropped
  • FAILED — storage error; recorded in failed task log