# Method


Source: `data/podcastindex_feeds.db`, table `podcasts`, the Podcast Index feed
dump dated 2026-08-16. 4,711,475 rows read, 4,711,475 rows parsed,
4,711,475 rows kept. No row is dropped anywhere in this dataset: a feed with no
dates, no title or no host still lands in a bucket and stays in every
denominator. Reference date 2026-08-16, unix 1786838400.

Producing script: `tools/report_stubs_facts.py`, one pass to mine title shapes
and one pass to classify. Host normalisation comes from `tools/count.py`, so
Spotify's creator domains fold into `anchor.fm` and the Buzzsprout feed domain
folds into `buzzsprout.com`.

## Fields used

- `title`, lowercased, runs of whitespace collapsed to one space.
- `episodeCount`. The dump caps it at 1,000 for recently refreshed feeds, which
  does not affect any cut here (every cut is at 0, 1 or 2 episodes).
- `oldestItemPubdate`, the oldest episode still in the feed. Its year is the
  start year, not the launch year: a feed that trims its archive reads younger
  than it is.
- `newestItemPubdate`. active90 is a newest episode at or after
  2026-08-16 minus 90 days; active365 and active30 are the same over 365 and 30
  days. A feed with no newest date is never active and stays in the
  denominator.
- `itunesAuthor` and `language`, both self-declared in the feed.

## How the pattern list was found

Not guessed. For each of the 25 platforms with the most feeds,
every title on a feed holding one episode or none was reduced to its shapes:
the title itself when it is eight words or shorter, its first one to four words
with the rest replaced by `*`, and its last one to three words with the front
replaced by `*`. The shapes were counted per platform and the top
15 per platform ship as `stub_patterns.csv`.

A shape was kept as a sign-up default only where one platform holds nearly all
of its matches, which is what a title written by a sign-up form looks like. A
shape that is merely a common thing for people to type ("the *", "* podcast",
"* show" with no possessive) was not kept, because a person naming a real show
writes those too.

## The classifier, verbatim

`*` is any text. Matching is a regular expression against the normalised title.
"matches in the index" counts every feed whose title matches, at any episode
count; "classed stub" counts the ones that also hold one episode or none, which
is the stub bucket. Buzzsprout rows never reach this test, see below.

| Pattern | Regex | Where it comes from | Titles matching in the index | Feeds classed stub |
| --- | --- | --- | ---: | ---: |
| `el podcast de *` | `^el podcast de .+$` | Spreaker, Spanish | 81906 | 39222 |
| `il podcast di *` | `^il podcast di .+$` | Spreaker, Italian | 3207 | 1142 |
| `le podcast de *` | `^le podcast de .+$` | French form of the same | 325 | 34 |
| `o podcast de *` | `^o podcast de .+$` | Portuguese form of the same | 10 | <5 |
| `der podcast von *` | `^der podcast von .+$` | German form of the same | 14 | <5 |
| `podcast de *` | `^podcast de .+$` | Spreaker, Spanish, short form | 24351 | 5635 |
| `el show de *` | `^el show de .+$` | Spreaker, Spanish | 15490 | 266 |
| `lo show di *` | `^lo show di .+$` | Spreaker, Italian | 2221 | 61 |
| `*'s podcast` | `^.+['’ʼ´`]s podcast$` | Buzzsprout, Spreaker, Podomatic, Podbean | 100963 | 26917 |
| `*'s show` | `^.+['’ʼ´`]s show$` | Spreaker, Castbox | 61776 | 2192 |
| `*'s channel / canal / kanal` | `^.+['’ʼ´`]s (channel|canal|canale|kanal|kanaal)$` | Castbox | 3615 | 2407 |
| `*'s first / second / third podcast` | `^.+['’ʼ´`]s (first|second|third) podcast$` | Hubhopper | 1822 | 1357 |
| `replays for *` | `^replays for .+$` | Castbox | 3249 | 2409 |
| `this rss has been deleted` | `^this rss has been deleted$` | Castbox | 490 | 490 |
| `my podcast site` | `^.*my podcast site.*$` | Podbean | 168 | 60 |
| `* podcast weblog` | `^.*podcast weblog.*$` | Podbean | 82 | 58 |
| `(no title at all)` | `^$` | every host | 14559 | 9255 |
| `untitled / sin titulo / no title` | `^(untitled|untitled podcast|no title|sin t[ií]tulo|sem t[ií]tulo)$` | every host | 121 | 55 |
| `test* / testing / prueba / teste / prova` | `^(test|testing|tests|prueba|pruebas|teste|testes|prova|prove)\b` | RSS.com, Spreaker, Anchor, Libsyn | 3276 | 1840 |
| `exact sign-up placeholders` | `^(podcast|my podcast|my first podcast|my new podcast|my awesome podcast|my sample podcast|sample podcast|new podcast|mi podcast|mi primer podcast|meu podcast|el podcast|el podcast de|the podcast|demo|demo podcast|audio|podcast project|first podcast|primer podcast)$` | Spreaker, RSS.com, Anchor, Hubhopper | 2982 | 1715 |

Patterns are tested in the order printed and the first match wins, so a feed
titled "El podcast de test" counts once, under `el podcast de *`.

## The buckets

Tested in this order, first match wins, so the buckets are exclusive and add to
4,711,475:

1. **stub**: the title matches a pattern above and `episodeCount <= 1`.
2. **empty**: `episodeCount = 0` and not a stub.
3. **one-shot**: exactly one episode and not a stub.
4. **dormant**: two episodes or more, no episode in the 365 days to 2026-08-16
   (a feed with no newest date at all lands here).
5. **fading**: two episodes or more, an episode inside 365 days but not inside
   90.
6. **active**: two episodes or more and an episode in the 90 days to
   2026-08-16.

Because stub, empty and one-shot are tested first, the **active** bucket holds
only feeds with two episodes or more. The plain active-90 count over every feed
in the dump, which is the figure `tools/count.py` publishes and the one in
`how_many.csv`, is 437,693. The difference of
15,479 feeds splits into 11,786 one-shots and 3,693 stubs
that published inside the window. No feed in the empty bucket is active.

## Buzzsprout

Buzzsprout's episode counts in this dump carry no episode data at all for half
its rows: 133,259 of its 270,067 rows report zero episodes, 49.34%, against
4.82% across the whole index, and not one of those 133,259 rows carries an
oldest-episode or newest-episode date either. We fetched four of them on
2026-09-16 through the proxy: `https://feeds.buzzsprout.com/24527.rss` returned
404, and `https://rss.buzzsprout.com/2360578.rss`,
`https://rss.buzzsprout.com/44761.rss` and
`https://rss.buzzsprout.com/2595625.rss` returned 200 with no `<item>` element.
Four feeds do not settle 133,259 rows, so no Buzzsprout row is ever classed
stub, empty or one-shot.
Buzzsprout is classified on dates alone: active when its newest episode falls
in the 90 days to 2026-08-16, fading when it falls in the 365 days but not the
90, and **unknown (Buzzsprout)** for everything else, because without a usable
episode count a quiet Buzzsprout feed cannot be told apart from a stub, an
empty feed or a one-shot. That bucket holds
200,740 feeds, 4.26%
of the index. Every Buzzsprout row stays in every denominator.

Buzzsprout titles are still mined for shapes and still appear in
`stub_patterns.csv`: 2,416 of its one-or-zero-episode rows are titled
"<name> 's podcast", its own sign-up default. Those rows are counted as
unknown, not as stubs.

## The live check on five Spreaker stubs

Five Spreaker feeds from the 2025 cohort, titled "El podcast de ..." and
holding one episode in the dump, fetched on 2026-09-16 through the Webshare
rotating gateway (`fetch_response_with_fallback` in
`ai_sending_tool_v2/proxy_fetch.py`). All five returned HTTP 200 and held
exactly one `<item>`:

| Feed URL | Status | Items | Episode title |
| --- | ---: | ---: | --- |
| https://www.spreaker.com/show/4584987/episodes/feed | 200 | 1 | Episodio 7 - El podcast de Mario Cucul |
| https://www.spreaker.com/show/5701832/episodes/feed | 200 | 1 | LA BIBLIA SIGNOS DE VIDA |
| https://www.spreaker.com/show/5742522/episodes/feed | 200 | 1 | Episodio 1 - El podcast de Vicente Vaquero |
| https://www.spreaker.com/show/5806919/episodes/feed | 200 | 1 | Discriminada por mi nacionalidad |
| https://www.spreaker.com/show/6340862/episodes/feed | 200 | 1 | Podcast sobre el Cuarto objetivo de desarrollo sostenible |

## Limitations

- A stub is a title, not a verdict about a person. Someone who signed up, was
  given a default title and never recorded is counted as a stub; so is someone
  who kept the default title and recorded one episode.
- A one-shot may be a finished show: a single lecture, a single sermon, a
  single audiobook chapter. Institutional publishers with one feed per item
  (sms.cam.ac.uk, islamhouse.com) sit almost entirely in that bucket.
- Start year is the oldest episode still in the feed. A show that trims its
  archive moves into a later cohort. 256,764 rows carry no
  oldest-episode date and appear in the overall and per-platform tables but in
  no cohort row.
- Dormant includes feeds whose newest date is missing:
  256,537 rows in the dump have none.
- The Podcast Index is not every podcast. It is the largest open index, it
  keeps dead feeds, and it holds feeds that no podcast app lists.
- Language and itunesAuthor are self-declared and unchecked.
- Cells under 5 print `<5` and a share on a base under 100 is left
  blank.

## Licence

CC BY 4.0. Attribution: Podcastquery, https://podcastquery.com.
