CivicSignal
CivicSignal Lab Open experiments, not shipped features

CivicSignal's playroom and experimental workbench.

Lab is where emerging methods, unconventional ideas and new analytical techniques are tested against real-world media data. It is a space for researchers, technologists, academics and curious minds to explore what might come next, collaborate on live experiments, and help shape which ideas move from research into product.

Experiment 01 · not a live feature

Narrative Threads: can a story's evolution be traced automatically?

An experiment run against real CivicSignal data: bucket articles by time, link them by shared entities, and see what structure falls out — without a single LLM call deciding which stories connect. This section documents the method, a failure mode it took two tries to avoid, and three real chains it actually found.

01 · The idea

Major Events already clusters coverage. This is a different question.

CivicSignal's Major Events panel groups articles that are almost certainly about the same real-world incident — shared entities, a tight date window, a minimum bar of independent publishers. That answers "did multiple outlets cover this?"

This experiment asks something else: can individual stories be linked across a longer window to see a narrative move — a demand, then a response, then a consequence — even when the entities only partially overlap from one link to the next? And separately: can we tell, without reading the text ourselves, whether two linked articles are actually the same report re-published, or a genuinely new development?

02 · The algorithm

Every step here is deterministic. None of it is an LLM judgment call.

This was built, run, and re-run as plain code against content.db. Anyone can re-run the same script on the same data and get the same graph.

  1. Entity extraction (already existed) — every article's PERSON/ORG entities, tagged earlier by extract_entities.py via spaCy's en_core_web_sm model. Not this experiment's work, and not an LLM — a real, offline, free NER model.
  2. Document-frequency filtering — count how often each entity string appears across the article slice; drop anything above a threshold (police-force names, city names, generic wire bylines). Plain counting, nothing more.
  3. Edge construction — for every pair of articles within a day-gap window, take the set intersection of their surviving entities. Three or more shared entities → an edge. That's set_a & set_b and a length check — no text is read at this step.
  4. Component detection — union-find over those edges. A standard, textbook graph algorithm.
  5. Duplicate-vs-development labeling — for each linked pair, compute title similarity (Python's stdlib difflib.SequenceMatcher, the Ratcliff/Obershelp algorithm — string matching, not meaning) and the share of the later article's entities that are genuinely new. High title similarity → labeled duplicate/republished. Low similarity but mostly new entities → new development. Neither → ambiguous.
Where does an LLM ever touch this? It doesn't, anywhere in steps 1–5. The only place a language model was involved was in the first pass at reading the output and describing what a chain "meant" in plain English — a summary of the graph's output, not a part of how the graph got built. That distinction turned out to matter: the first summary called one chain "duplicate coverage" when the actual metric showed it was a mix — see below.
03 · What happens without guardrails

The first version linked almost everything together.

Run against 1,075 real "Conflict & Security" articles from Nigeria (three weeks, Aug 2026) with loose settings — any 2 shared entities, a 10-day window, a weak frequency cutoff — one single connected component swallowed 649 of the 1,075 articles. Not a narrative graph, just a blob: everything touches everything eventually once "the Nigeria Police Force" or "Abuja" counts as a real link.

SettingMax entity freq.Min shared entitiesDay windowResult
Too loose>3% of corpus210 daysone 649-article blob
Tightened>1.5%37 days140 components, largest 31
Used below>1%35 days157 components, largest 10

This is the same anti-chaining problem CivicSignal's Major Events detector already solves in production (detect_events.py's MAX_ENTITY_DF / MIN_SHARED_ENTITIES safeguards) — this experiment just re-discovered why those exist by hitting the same wall first.

04 · Three real chains

Nigeria, Conflict & Security, Aug 21–26 2026 — found by the algorithm above, unedited.

Each dot is a real published article, positioned chronologically top to bottom. Each line is a real computed edge between two articles that shared 3+ surviving entities within 5 days of each other, colored by the duplicate/development label above.

new development duplicate/republished ambiguous / related coverage

The state-police bill

9 articles · Aug 21–26

Mostly green toward the end — a demand from Northern senators (Aug 21) evolves into a government response and a named appointment to a policing working group (Aug 25–26), with genuinely new entities entering at each step.

2026-08-21 Northern Senators Demand State Police After Adamawa, Sokoto Attacks 2026-08-21 Terrorist attacks: Northern senators demand urgent action on State Police Bill 2026-08-21 Northern Senators Call for Urgent Action on State Police Bill 2026-08-21 Northern senators demand immediate state police after Adamawa, Sokoto terror a… 2026-08-22 Northern Senators Demand Immediate State Police After Fresh Terrorist Attacks 2026-08-25 Tinubu Govt Gives Fresh Details, Reveals Who Will Supervise State Police 2026-08-25 FG Makes New Appointment 2026-08-25 Nigerian Newspapers: 10 things you need to know Tuesday morning 2026-08-26 OAU historian Rotimi joins presidential working group on national policing bil…

Zolani Tete's killing

9 articles · Aug 22–26

Starts almost entirely amber — five outlets reporting the same killing within hours, high title similarity, few new entities — then shifts as arrests are reported a day later.

2026-08-22 Former World Champion Boxer, Zolani Tete Shot Dead Outside Home In South Afric… 2026-08-22 Former Boxing World Champion Zolani Tete Shot Dead In South Africa | Naija247n… 2026-08-22 Former world boxing champion Zolani Tete shot dead outside his home in South A… 2026-08-22 Former World Boxing Champion, Zolani Tete Shot Dead In South Africa 2026-08-23 Two suspects arrested as South Africans mourn slain boxer Zolani Tete - Amatro… 2026-08-23 Two suspects arrested as South Africans mourn slain boxer Zolani Tete - Amatro… 2026-08-23 Police arrest two over death of former world boxing champion Zolani Tete 2026-08-23 Tragedy as former world boxing champion shot dead outside his home in South Af… 2026-08-26 Zolani Tete's murder suspect nabbed

The Ogun "baby factory" raid

9 articles · Aug 25–26

A genuine mix, not a clean verdict either way — some links are near-identical rewrites (amber), others introduce a different suspect's name or detail the metric correctly flags as new (green). The first read of this chain called it "just duplicate coverage"; the actual metric says otherwise.

2026-08-25 Police rescue six, arrest six over running a baby factory in Ogun - Neptune Pr… 2026-08-25 6 Rescued, 6 Arrested As Police Bust Alleged Baby Factory In Ogun | Daily Repo… 2026-08-25 Police Raid Ogun 'Baby Factory', Rescue Pregnant Women And Children, Arrest Si… 2026-08-25 Ogun Police Burst 'Baby Factory', Rescue Pregnant Women, Children | National R… 2026-08-25 Ogun police bust illegal 'baby factory' , rescue pregnant women, children 2026-08-25 Police Uncover Baby Factory In Ogun 2026-08-26 Police Uncover Alleged Baby Factory In Ogun, Rescue Women, Children | Daily Re… 2026-08-26 Police burst baby factory in Ogun 2026-08-26 Police burst baby factory in Ogun
05 · Where this breaks

Known limitations, not smoothed over.

Experiment 02 · not a live feature

Narrative Roles: who does a story cast as hero, threat and victim?

A second, related question: not "did this story develop," but "how does it cast its actors" — and does that casting cluster into recognisable patterns across outlets, before anyone tells the model which outlet leans which way. Adapted from published methods (Chambers & Jurafsky's narrative event chains and narrative schemas; Das et al.'s structured clustering (code), ACL 2026), tested here against a real, independently-labelled corpus.

01 · The idea

Every story assigns roles. Usually silently.

The same event can cast the same actor as protector in one outlet and threat in another — "police restore order" versus "police crack down." This experiment annotates each article's actors as Hero, Threat, Victim or Neutral, then clusters articles so that stories with genuinely conflicting casts of the same actor are pushed apart ("cannot-link" constraints) rather than lumped together by topic alone. Outlet identity and any bias label are withheld from the clustering step, and only checked afterward, against the resulting clusters.

02 · A real case study

Run against 45 real climate-news articles, human-labelled in advance.

Rather than invent an example, this ran against the Narrative Frames Corpus (Frermann, Li, Khanehzar & Mikolajczak, ACL 2023) — 428 English-language climate-change articles from the NELA news archive, human-coded for Hero/Villain/Victim roles, Media Bias/Fact Check (MBFC) rating and topical frame. A balanced 45-article subset (15 right-leaning, 15 left-centre, 15 left-leaning by MBFC) was clustered with and without the role constraints:

Role purity 0.820 → 0.859

Semantic-only clustering vs. role-constrained clustering — higher means actors keep a more consistent role within their assigned cluster.

Constraint violations 86 → 55 (−36%)

Cases where two stories with conflicting actor roles still ended up in the same cluster — fewer is better.

Exact-match purity 0.200 → 0.156

Stories sharing an identical full role assignment, clustered together — this went down, not up. Not every metric improved.

Seven named clusters came out legible enough to read directly — real outlets, real dates, real role counts, no synthetic examples:

Policy mobilisation vs. economic disruption

Fox News (2019-02-05) · PoliticusUSA (2019-05-14) — the same policy push cast as economic threat by one outlet, mobilising response by the other.

Green New Deal as policy solution

Mother Jones (2019-07-31) · The New York Times (2019-05-21) — government and policy cast as Hero, climate impact as the Threat.

Amazon devastation and local harm

BBC (2019-04-29) · PBS (2019-05-27) — nature and local communities cast as Victim; fire/deforestation as Threat.

Pipelines, states and environmental conflict

The Daily Caller (2019-03-13) · CBS News (2019-09-06) — state and industry actors split between Hero and Threat depending on outlet.

Numbers and cluster labels above are a specific reproduction run, not a claim of exactly replicating the original Das et al. paper's own reported results.

03 · Where this breaks

Limitations and Caveats

Open to research labs and product teams.

Real CivicSignal data, working methods and honest failure modes — open for a joint study, a pilot integration, or a second opinion.

Get in touch →

The map is moving. CivicSignal is how you keep up with it.

Investigate the full spectrum of Africa's media landscape — from legacy broadcasters to digital-native newsrooms, creator-journalists, and the newsletters and channels that never touch a website.

Connect to CivicSignal
CivicSignal · Code for Africa — working showcase, not for distribution All figures and chains on this page are real, drawn directly from content.db