Editorial Intelligence
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_feedsandeditorial_intelligence_manual, two interchangeable input adapters- Drupal core
- 10 or 11
- Requires
- The contributed
external_entitiesmodule andkey;editorial_intelligence_feedsalso 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_queuedoption 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_feedssubmodule - 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.