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](/products/species-ext) 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](/projects/biodiversa).



 

 





 

## At a glance

Modules`editorial_intelligence`, the engine: storage, embeddings and scoring; `editorial_intelligence_feeds` and `editorial_intelligence_manual`, two interchangeable input adaptersDrupal core10 or 11RequiresThe contributed `external_entities` module and `key`; `editorial_intelligence_feeds` also requires FeedsEmbeddingsA self-hosted Ollama server running `nomic-embed-text`, configured through the Drupal AI moduleWorks with`species_ext`, for a per-species widget and a species corpus to score againstStatusWorking. 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.



[Get in touch →](/contact)