fix(news): keep World News API's own category for a country's news

The bundled rules read fighting and crises; in everyday headlines their
words mislead ("Kazakhstan Advances in Global Digital Rankings" read as
ground fighting). A country's own stories in Local now keep World News
API's category (politics, business, else other) and carry no severity.
Fighting in the area still shows through the Conflicts reports placed
there.
This commit is contained in:
Denis Groshev committed 2026-11-06 00:45:33 +01:00
1 parent 1e78ac931c
commit 82057cbb51
5 files changed
+47 -14

No files matched your search

+12 -12
View File
@@ -5,8 +5,9 @@
// (WORLDNEWSAPI_KEY) by the country at the view centre; without its key the
// section answers 503 {error: 'no_key', key} and reads nothing, and past
// its day's reads (WORLDNEWSAPI_DAILY_READS) 503 {error: 'key_quota', key}.
// Categories, relevance and severity are the shared rules (categorize.js);
// a news server may replace these readings with its own.
// Conflicts' categories, relevance and severity are the shared rules
// (categorize.js); a country's own news keeps World News API's category.
// A news server may replace these readings with its own.
//
// A read that has something to serve is not waited for: what was read is
// served while it is read again. A section that cannot be read answers 502,
@@ -93,17 +94,19 @@ const EMPTY_OUTLINE = Object.freeze({
disputed: [],
area: null,
});
// World News API's categories, where the rules find none of their own.
// A country's own news keeps World News API's category: the rules read
// fighting and crises, and in everyday news their words mislead ("advances
// in the rankings" is no ground fighting). Fighting in the country reaches
// Local as the Conflicts reports placed there.
const WORLD_NEWS_CATEGORY = { politics: 'political', business: 'economy' };
const list = (value) =>
value ? new Set(value.split(',').filter(Boolean)) : null;
/** The rules' reading of a headline: category, relevance and severity. */
function readTitle(title, { dedicated, fallback = null }) {
function readTitle(title, { dedicated }) {
const read = categorizeText({ title });
const category =
read.category === 'other' && fallback ? fallback : read.category;
const { category } = read;
return {
category,
relevance: relevanceOf({ category, tags: read.tags, title, dedicated }),
@@ -213,13 +216,10 @@ export function worldNewsReport(article, country) {
source: { name: article.publisher, attribution: 'World News API' },
kind: 'worldnewsapi',
side: 'unknown',
...readTitle(article.title, {
dedicated: false,
fallback: WORLD_NEWS_CATEGORY[article.category] || null,
}),
// Local news: what the country's own outlets publish.
category: WORLD_NEWS_CATEGORY[article.category] || 'other',
// Local news: what the country's own outlets publish, with no severity
// of its own.
relevance: 'minor',
severityBy: 'rules',
publishedAt: article.publishedAt,
geo: {
lat: anchor.lat,
+30
View File
@@ -1052,3 +1052,33 @@ test('a failing file gets one strike a round, not three within seconds', async (
gdeltCalls(calls).filter((c) => String(c.url) === flaky).length >= 2,
);
});
test("a country's own news keeps World News API's category and no severity, whatever its words", async () => {
const { worldNewsReport } = await import('./keyed.js');
const { countryByCode } = await import('./areas.js');
const country = countryByCode('KZ');
const read = (title, category) =>
worldNewsReport(
{
id: 1,
title,
url: 'https://times.example/x',
publisher: 'times.example',
publishedAt: '2026-11-05T08:00:00.000Z',
category,
},
country,
);
const rankings = read(
'Kazakhstan Advances in Global Digital Rankings as AI Infrastructure Expands',
'technology',
);
assert.equal(rankings.category, 'other');
assert.equal(rankings.severity, undefined);
assert.equal(
read('Startup advances to the next funding round', 'business').category,
'economy',
);
assert.equal(read('King visits Astana', 'politics').category, 'political');
assert.equal(read('Parliament session opens', null).category, 'other');
});