Drupal studio · Diego Suarez, Madagascar
Est. 2025 · 12°16′ S

Editorial Intelligence

An external news engine for Drupal that scores every candidate article against a site's own content, and shows visitors only the ones that are relevant.

What it is

Editorial Intelligence is a Drupal module that curates external news for a site instead of adding a raw feed. It polls sources for candidate articles, scores each one by embedding similarity against the site's own content, and shows only the relevant ones: in a sidebar, in a related coverage block on an article, and, paired with the Species External Entity module, in a per-species widget on a species page.

It treats external coverage as a curated newsroom, not an unfiltered feed: nothing appears until it has been scored and found relevant. Editorial Intelligence is live today on Biodiversa.

At a glance

Modules
editorial_intelligence, the engine: storage, embeddings and scoring; editorial_intelligence_feeds and editorial_intelligence_manual, two interchangeable input adapters
Drupal core
10 or 11
Requires
The contributed external_entities module and key; editorial_intelligence_feeds also requires Feeds
Embeddings
A self-hosted Ollama server running nomic-embed-text, configured through the Drupal AI module
Works with
species_ext, for a per-species widget and a species corpus to score against
Status
Working. In production on Biodiversa.

What visitors and editors see

Visitors see curated coverage wherever a site places a block: a sidebar list, a related coverage block on an article, or, on a biodiversity site, a widget of news about the species on the page.

Editors work through a keyboard-driven review queue: an item that scores above a threshold publishes automatically, one that scores below it is dropped, and everything in between waits for a person to approve or reject it.

What it adds to a Drupal site

Entity storage

Every candidate is stored as an external entity (editorial_news), not a node, so the engine adds nothing to Drupal's node table.

Corpora

Scoring works against named corpora rather than one global feed. Shipped corpora are news_article, the site's own content, and species, for sites running species_ext. Each candidate keeps the corpus it was scored against, so several corpora coexist without overwriting each other, and adding a new one is two Drush calls, not new code.

Display components

The block plugin EditorialNewsRelatedBlock is reusable per corpus. It ships with three placements built on the same pattern:

  • Editorial news: sidebar, a site-wide list, configured by tag and a result limit
  • Editorial news: related, placed on an article, matched by content type and corpus, with an include_queued option to show unapproved candidates too
  • Editorial news: about this species, placed on a species page, matched against the species corpus, with its own minimum-match and scoring settings

Input sources

The engine has no knowledge of where a candidate comes from. Each way one can arrive is a sibling submodule: editorial_intelligence_feeds polls RSS and Atom feeds through Drupal Feeds, and editorial_intelligence_manual lets an editor paste a URL, preview its Open Graph metadata, and add it by hand. A new source is a third submodule of the same shape, not a change to the engine.

Drush commands

Ingestion and scoring: ei:seed-sources, ei:add-source, ei:embed-onsite, ei:rescore-all (its --rerank option reuses stored embeddings instead of calling Ollama again), ei:apply-thresholds. Species and taxonomy: ei:embed-taxa, ei:embed-siblings. AI captions: ei:generate-taglines, ei:explain-matches. Diagnostics: ei:diag, ei:purge.

Data sources and licences

  • RSS and Atom feeds: polled on a nightly schedule through the editorial_intelligence_feeds submodule
  • Wikipedia: summaries embedded for the taxonomy chain when scoring the species corpus
  • GBIF: living siblings of a species, pulled by its GBIF taxon key, for the species corpus
  • Ollama: a self-hosted server providing the embedding model that scores every candidate
  • Claude Haiku (Anthropic), optional: a short caption on why an approved candidate matters; the default is a local model, Claude is a production option, and either is configured as a Drupal AI module provider rather than fixed in code

Rescoring reuses stored embeddings by default, so retuning a threshold or adding a corpus does not call the embedding model again.

Design decisions

  • Curation, not aggregation. Every candidate is scored, ranked and shown only where it is relevant, rather than publishing everything a source provides.
  • One engine, several corpora. A single embeddings table serves any number of named corpora, each candidate tagged with the corpus it was scored against, so scoring against a new content type is mostly configuration once a first corpus exists.
  • A relevance gate per placement. A block can show only editor-approved items, or trust the score directly; the choice is made per placement, not site-wide.
  • Ingestion stays separate from scoring. The engine only stores, scores and displays; every way a candidate can arrive, polled or pasted by hand, is a sibling submodule that the engine knows nothing about.

Start a project.

Tell us what your site publishes already, and where external coverage should appear.