mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 05:42:22 +00:00
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:
1 parent
1e78ac931c
commit
82057cbb51
5 files changed
+47
-14
No files matched your search
+1
-1
@@ -42,7 +42,7 @@ How to read this:
|
||||
| **News server** (`GEV_NEWS_ORIGIN`, optional; none by default) | News layer, both sections: the last 24 h of reports from many publishers. Set, the local server forwards the layer's `/api/news/hotspots`, `/feed` and `/outline` GET requests to it and Core reads none of the three sources below. Which providers stand behind its feed is the news server's to state | Each report stays its publisher's: headlines and summaries link to the original article, and pictures are hotlinked from the publisher's site (no referrer), never stored | Per report: publisher name and article link on every card; picture cards name the picture's publisher. The Data attribution popover states that places, categories, severity and English headlines are derived automatically and can be wrong |
|
||||
| **GDELT 2.0 events and GKG** (`data.gdeltproject.org/gdeltv2/`; no key; used only without a news server) | News layer, Conflicts: the last 24 h of GDELT's 15-minute event exports (`*.export.CSV.zip`, about 100 KB each), read server-side as they appear. Physical violence only (CAMEO roots 18–20); one report per story, copies of one agency story merged; placed at GDELT's location for it (a town the article's words name beats a bare country). Titled by the article's page title from the GKG export of the same quarter hour (`*.gkg.csv.zip`, about 6 MB each) for the newest 3 h, else by the words of the article's address. GDELT's actor coding is never shown. Categories, relevance and severity by bundled rules | [GDELT Terms of Use](https://www.gdeltproject.org/about.html#termsofuse): unrestricted academic, commercial and governmental use, with citation and a link to the GDELT Project. Coded events, page titles and article links only; linked articles keep their publishers' terms | "GDELT Project" with a link to gdeltproject.org in the Data attribution popover; "via GDELT Project", the publisher's domain and the article link on every card |
|
||||
| **Liveuamap** (`a.liveuamap.com/api`, `LIVEUAMAP_API_KEY`, optional, paid; used only without a news server) | News layer, Conflicts, beside GDELT: Liveuamap's worldwide mapped items of the last 24 h (`a=mpts`), read server-side every 5 minutes; categories and severity are read from the headline by bundled rules | Liveuamap API terms of the operator's own subscription. Headlines, places and links only; item pictures and clips are hotlinked from Liveuamap, never stored | "Liveuamap" with a link in the Data attribution popover once its items show; "Liveuamap" and the item's Liveuamap link on every card |
|
||||
| **World News API** (`api.worldnewsapi.com/search-news`, `WORLDNEWSAPI_KEY`, optional; used only without a news server) | News layer, Local: the last 24 h of news by the source country at the view centre (country from the bundled Natural Earth admin-0 pack; none in a disputed area), read server-side once per country every 30 minutes, about 1.5 points a read; at most `WORLDNEWSAPI_DAILY_READS` reads a day (default 30), after which Local says the daily limit is reached. Sport, entertainment, lifestyle and travel left out | [World News API terms](https://worldnewsapi.com/terms/) of the operator's own plan; the free plan (50 points a day) requires a visible backlink to worldnewsapi.com in the app, paid plans do not. Titles and links only; article text and summaries are not kept or shown; pictures are hotlinked from the publisher | The "World News API" link to worldnewsapi.com, always in the Data attribution popover (the free plan's required backlink); "via World News API", the publisher's domain and the article link on every card |
|
||||
| **World News API** (`api.worldnewsapi.com/search-news`, `WORLDNEWSAPI_KEY`, optional; used only without a news server) | News layer, Local: the last 24 h of news by the source country at the view centre (country from the bundled Natural Earth admin-0 pack; none in a disputed area), read server-side once per country every 30 minutes, about 1.5 points a read; at most `WORLDNEWSAPI_DAILY_READS` reads a day (default 30), after which Local says the daily limit is reached. Each story keeps World News API's own category (politics, business, else other) with no severity of its own; sport, entertainment, lifestyle and travel left out | [World News API terms](https://worldnewsapi.com/terms/) of the operator's own plan; the free plan (50 points a day) requires a visible backlink to worldnewsapi.com in the app, paid plans do not. Titles and links only; article text and summaries are not kept or shown; pictures are hotlinked from the publisher | The "World News API" link to worldnewsapi.com, always in the Data attribution popover (the free plan's required backlink); "via World News API", the publisher's domain and the article link on every card |
|
||||
| **InciWeb** (inciweb.wildfire.gov) | Per-incident "InciWeb ↗" card links to official incident pages (narratives, evacuation/closure notices) | U.S. government public incident information. Links are matched best-effort by incident name and state, with incident page origin and update times checked on selection. Same-origin `/api/fire-perimeters/inciweb` routes cache the catalog for 1 hour and publication metadata for 30 minutes; linked pages remain InciWeb content | "Incident information: InciWeb (inciweb.wildfire.gov)" |
|
||||
| **FAA Special Use Airspace** (`services6.arcgis.com` FeatureServer `Special_Use_Airspace`) | Prohibited & Restricted Airspace layer: US prohibited (P) and restricted (R) special-use airspace polygons (not the UAS Facility Map or UAS flight restrictions), altitude limits from the `*_VAL`/`*_UOM`/`*_CODE` fields, times of use, and controlling agency | U.S. public domain (FAA aeronautical data published through the [FAA AIS Open Data portal](https://adds-faa.opendata.arcgis.com/)); fetched keyless through `/api/drone-airspace` (1-hour server cache). Informational only — not a substitute for official UAS flight-planning tools | "Prohibited & restricted airspace: FAA Special Use Airspace" |
|
||||
| **OpenFreeMap / OpenMapTiles** | Vector tiles | OpenStreetMap data under ODbL 1.0 | OpenFreeMap © OpenMapTiles |
|
||||
|
||||
@@ -5853,7 +5853,7 @@ request viewbox and continues searching without that geographic preference.
|
||||
- **Panel.** The right-rail NEWS panel (`src/ui/newsPanel.js`, `src/ui/news/`) has a time window at the top (the server's 24 h, or 12, 6, 3 or 1 h; the hotspot counts are read for the chosen window) and two sections. **Conflicts**: hotspot chips with counts, a severity floor (significant and above by default), a category filter, the chosen event, and a continuous feed, newest first. **Local**, while the camera is below 3,000 km: every report in the area at the view centre, named at the top ("News from …") and outlined on the globe, which then shows those reports instead of the hotspots' news; at sea, reports within 500 km. A disputed area of the bundled list (11, see `src/data/local_data/natural_earth/README.md`), or a territory Natural Earth gives no country code (Somaliland, Northern Cyprus and others), is named as itself and read from no country's outlets; elsewhere Natural Earth's default country lines apply. Each card shows the publisher's picture when the event has one (hotlinked, no referrer, publisher credited), the headline (a model's English headline marked as such), a mono meta line (severity marker, category, place, sources, age), who reported it, attestation and media chips, and the service it came through ("via GDELT Project"). Open, a card plays the post's clip on request. A click flies the camera straight down from 150 km and selects the report.
|
||||
- **Notices.** A section whose key the server lacks says "{provider} key required" and names the variable; a refused key says "{provider} refused its key"; a spent daily allowance says "{provider} limit reached for today". The other section keeps working.
|
||||
- **Source.** `sources.news` (`createNewsSource`, export `./layers/news/source`) reads same-origin `/api/news/*` with a 15 s timeout, a 4 MiB cap and record-by-record validation; `newsQueryAllowed` lets a host admit exactly the client's requests. Core's local server (`server/providers/news.js`) forwards only those GET requests to `GEV_NEWS_ORIGIN` when it is set.
|
||||
- **Built-in backend.** With `GEV_NEWS_ORIGIN` unset, Core answers the same routes itself (`server/providers/news/keyed.js`). **Conflicts** needs no key: the last 24 h of GDELT 2.0 event exports (15-minute files on `data.gdeltproject.org`, about 100 KB each), physical violence only (CAMEO roots 18–20), one report per story with copies of one agency story merged, placed at GDELT's location for it (a town the article's words name beats a bare country). Titles are the article's page title from GDELT's GKG export of the same quarter hour (about 6 MB each) for the newest 3 h, else the words of the article's address; GDELT's actor coding is never shown. With `LIVEUAMAP_API_KEY`, Liveuamap's mapped items stand beside GDELT's. **Local** reads World News API (`WORLDNEWSAPI_KEY`) by the source country at the view centre, once per country every 30 minutes (about 1.5 points a read, at most `WORLDNEWSAPI_DAILY_READS` reads a day, default 30), plus the Conflicts reports placed inside the area. Areas come from the bundled Natural Earth countries and disputed areas (`server/providers/news/areas.js`). Categories, relevance and severity are bundled rules (`categorize.js`); a news server may replace them.
|
||||
- **Built-in backend.** With `GEV_NEWS_ORIGIN` unset, Core answers the same routes itself (`server/providers/news/keyed.js`). **Conflicts** needs no key: the last 24 h of GDELT 2.0 event exports (15-minute files on `data.gdeltproject.org`, about 100 KB each), physical violence only (CAMEO roots 18–20), one report per story with copies of one agency story merged, placed at GDELT's location for it (a town the article's words name beats a bare country). Titles are the article's page title from GDELT's GKG export of the same quarter hour (about 6 MB each) for the newest 3 h, else the words of the article's address; GDELT's actor coding is never shown. With `LIVEUAMAP_API_KEY`, Liveuamap's mapped items stand beside GDELT's. **Local** reads World News API (`WORLDNEWSAPI_KEY`) by the source country at the view centre, once per country every 30 minutes (about 1.5 points a read, at most `WORLDNEWSAPI_DAILY_READS` reads a day, default 30), plus the Conflicts reports placed inside the area. A country's own stories keep World News API's category (politics, business, else other) and no severity: the rules read fighting and crises, and everyday headlines mislead them. Areas come from the bundled Natural Earth countries and disputed areas (`server/providers/news/areas.js`). Conflicts' categories, relevance and severity are bundled rules (`categorize.js`); a news server may replace them.
|
||||
- **State.** Share/restore token `9`, enabled-only. Voice is off for this layer (`VOICE_OFF_LAYERS`).
|
||||
|
||||
### S2 GhostMaps (October 2026)
|
||||
|
||||
@@ -142,6 +142,9 @@ Status: Open (limits)
|
||||
Natural Earth's default country lines apply, so a place in Western Sahara,
|
||||
for example, is named with the country those lines put it in. GDELT places a whole city at one point: Jerusalem's falls
|
||||
inside East Jerusalem, so it is named "Jerusalem" alone.
|
||||
- A country's own stories in Local keep World News API's category (political,
|
||||
economy, else other) and carry no severity; fighting there shows through the
|
||||
Conflicts reports placed in the area.
|
||||
- Local needs a World News API key. Its free plan allows 50 points a day; Core
|
||||
stops at `WORLDNEWSAPI_DAILY_READS` reads (default 30) and Local then says the
|
||||
daily limit is reached.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
Reference in new issue
Block a user