diff --git a/data/tables/messaging-channels.json b/data/tables/messaging-channels.json index 6b505cb50..cfd2b784e 100644 --- a/data/tables/messaging-channels.json +++ b/data/tables/messaging-channels.json @@ -2,7 +2,7 @@ "meta": { "title": "Messaging & App Channels", "grain": "one row per country x communication app", - "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp is the top messaging app in 54 countries, Telegram in 38 — neither appears in referral share at all.", + "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp (Messenger or Business) is the top messaging app in 73 countries, Telegram in 38 — neither appears in referral share at all.", "ios_caveat": "iOS ONLY. Most of these countries are Android-majority and Google publishes no keyless equivalent, so this is one eye open. An app absent here is NOT absent in-country. 50 of 175 countries have no Apple storefront.", "ranking_caveat": "A top-free RANK is most-DOWNLOADED recently, not most-used.", "classification_caveat": "kind is a name-keyword HEURISTIC over a known list, not a verified taxonomy.", @@ -2830,19 +2830,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Thailand", - "iso3": "THA", - "region": "Asia", - "app": "LINE MAN: Food Delivery & more", - "kind": "messaging", - "rank": 13, - "is_top_messaging": true, - "people_groups": 8, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Burkina Faso", "iso3": "BFA", @@ -3006,7 +2993,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 37, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 8, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", @@ -7809,19 +7796,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Maldives", - "iso3": "MDV", - "region": "Asia", - "app": "Malé Taxi Line", - "kind": "messaging", - "rank": 1, - "is_top_messaging": true, - "people_groups": 1, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Northern Mariana Islands", "iso3": "MNP", @@ -13120,7 +13094,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 33, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 1, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", diff --git a/library/scripts/research/app-kind.mjs b/library/scripts/research/app-kind.mjs new file mode 100644 index 000000000..74008e106 --- /dev/null +++ b/library/scripts/research/app-kind.mjs @@ -0,0 +1,26 @@ +// app-kind.mjs — what KIND of communication channel an App Store app is, from its name. +// +// A NAME-AND-KEYWORD HEURISTIC over a known list, recorded as such in every table that uses it. +// It lives in its own file so the fetcher (fetch-top-apps.mjs) and the table builder +// (build-tabulator-tables.mjs) classify with the SAME rule: the builder re-derives the kind from +// the stored name, so a cached fetch made with an older rule cannot carry an old answer forward. +// +// ★ A BRAND THAT IS ALSO AN ENGLISH WORD MUST BE ANCHORED. `line` matched as a bare word made +// "LINE MAN: Food Delivery & more" (Thailand) and "Malé Taxi Line" (Maldives) each country's TOP +// messaging app, ahead of WhatsApp. LINE's own app is "LINE" or "LINE: Calls & Messages", so it +// matches only as the whole name or the name before a colon. +export const KIND = [ + [/\b(whatsapp|telegram|signal|viber|imo|zalo|wechat|weixin|kakaotalk|botim|imessage|messenger|skype|hike|jio ?chat|soul|beetalk)\b|^line(?:$|\s*:)/i, 'messaging'], + [/\b(tiktok|douyin|kwai|likee|snack ?video|moj|josh|triller|reels)\b/i, 'short-video'], + [/\b(facebook|instagram|snapchat|x|twitter|threads|vk|odnoklassniki|weibo|qzone|xiaohongshu|little red|sharechat|koo|mastodon|bluesky)\b/i, 'social'], + [/\b(youtube|bigo|nimo|twitch|rumble|dailymotion|vimeo)\b/i, 'video'], + [/\b(opera|uc ?browser|chrome|firefox|brave|puffin|phoenix ?browser)\b/i, 'browser'], +]; + +export function classify(name) { + for (const [re, k] of KIND) if (re.test(String(name || '').trim())) return k; + return 'other'; +} + +// The kinds a reachability table keeps (everything else is not a communication channel). +export const isComms = (kind) => kind !== 'other' && kind !== 'browser'; diff --git a/library/scripts/research/build-tabulator-tables.mjs b/library/scripts/research/build-tabulator-tables.mjs index 37fac6da5..b65a121b4 100644 --- a/library/scripts/research/build-tabulator-tables.mjs +++ b/library/scripts/research/build-tabulator-tables.mjs @@ -28,6 +28,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; +import { classify, isComms } from './app-kind.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const OUT = join(__dirname, 'out'); @@ -553,6 +554,15 @@ if (has('social-platforms-by-country.json')) { } } +// The headline counts are COUNTED from the rows, never typed: a typed "54" stayed true only until +// the classifier changed. WhatsApp is its two apps (Messenger + Business) together. +function messagingWhy(rows) { + const top = new Map(); + for (const r of rows) if (r.is_top_messaging) top.set(r.iso3, r.app); + const count = (re) => [...top.values()].filter((a) => re.test(a)).length; + return `Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp (Messenger or Business) is the top messaging app in ${count(/whatsapp/i)} countries, Telegram in ${count(/telegram/i)} — neither appears in referral share at all.`; +} + // ═══ TABLE 7 — MESSAGING & APP CHANNELS (what referral stats cannot see) ═══ // StatCounter's ENTIRE tracked universe is Western referral platforms — measured: // Facebook/Instagram/Twitter/YouTube/Pinterest/reddit/LinkedIn/Tumblr/HN/VK/Fark. @@ -572,7 +582,11 @@ if (has('top-apps-by-country.json')) { for (const c of Object.values(apps.countries)) { const meta = g[c.iso3] || {}; // One row per COUNTRY x APP so a list view can sort "where does Telegram rank". - for (const a of (c.comms_apps || [])) { + // The kind is RE-DERIVED from the name with the shared classifier (app-kind.mjs), and the top + // messaging app with it: the cached fetch may carry a kind from an older rule. + const comms = (c.comms_apps || []).map((a) => ({ ...a, kind: classify(a.name) })).filter((a) => isComms(a.kind)); + const topMessaging = comms.slice().sort((x, y) => x.rank - y.rank).find((a) => a.kind === 'messaging')?.name || null; + for (const a of comms) { rows.push({ country: meta.country || c.iso3, iso3: c.iso3, @@ -580,7 +594,7 @@ if (has('top-apps-by-country.json')) { app: a.name, kind: a.kind, rank: a.rank, - is_top_messaging: a.name === c.top_messaging, + is_top_messaging: a.name === topMessaging, people_groups: meta.groups || 0, platform_bias: c.platform_bias, classification: c.classification_method, @@ -605,7 +619,7 @@ if (has('top-apps-by-country.json')) { ], { title: 'Messaging & App Channels', grain: 'one row per country x communication app', - why: 'Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp is the top messaging app in 54 countries, Telegram in 38 — neither appears in referral share at all.', + why: messagingWhy(rows), ios_caveat: 'iOS ONLY. Most of these countries are Android-majority and Google publishes no keyless equivalent, so this is one eye open. An app absent here is NOT absent in-country. 50 of 175 countries have no Apple storefront.', ranking_caveat: 'A top-free RANK is most-DOWNLOADED recently, not most-used.', classification_caveat: 'kind is a name-keyword HEURISTIC over a known list, not a verified taxonomy.', diff --git a/library/scripts/research/fetch-top-apps.mjs b/library/scripts/research/fetch-top-apps.mjs index e0d83cf63..0cf380d28 100644 --- a/library/scripts/research/fetch-top-apps.mjs +++ b/library/scripts/research/fetch-top-apps.mjs @@ -40,6 +40,7 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; +import { classify, isComms } from './app-kind.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const OUT = join(__dirname, 'out'); @@ -55,19 +56,7 @@ const SOURCE = { why: 'StatCounter tracks only Western referral platforms and cannot see WhatsApp/WeChat/Telegram/TikTok/LINE/Viber/imo/Zalo — the channels that dominate most of this corpus', }; -// Known non-Western + messaging platforms worth surfacing explicitly. Matching is -// a HEURISTIC on the app name; recorded as such. -const KIND = [ - [/\b(whatsapp|telegram|signal|viber|imo|line|zalo|wechat|weixin|kakaotalk|botim|imessage|messenger|skype|hike|jio ?chat|soul|beetalk)\b/i, 'messaging'], - [/\b(tiktok|douyin|kwai|likee|snack ?video|moj|josh|triller|reels)\b/i, 'short-video'], - [/\b(facebook|instagram|snapchat|x|twitter|threads|vk|odnoklassniki|weibo|qzone|xiaohongshu|little red|sharechat|koo|mastodon|bluesky)\b/i, 'social'], - [/\b(youtube|bigo|nimo|twitch|rumble|dailymotion|vimeo)\b/i, 'video'], - [/\b(opera|uc ?browser|chrome|firefox|brave|puffin|phoenix ?browser)\b/i, 'browser'], -]; -const classify = (name) => { - for (const [re, k] of KIND) if (re.test(name)) return k; - return 'other'; -}; +// The classifier (KIND / classify) is shared with build-tabulator-tables.mjs: see app-kind.mjs. const ISO2 = existsSync(join(OUT, 'iso3-to-iso2.json')) ? JSON.parse(readFileSync(join(OUT, 'iso3-to-iso2.json'), 'utf8')) @@ -113,7 +102,7 @@ for (let n = 0; n < todo.length; n++) { } } catch (e) { err = String(e.message || e).slice(0, 60); } - const comms = apps.filter((a) => a.kind !== 'other' && a.kind !== 'browser'); + const comms = apps.filter((a) => isComms(a.kind)); out[iso3] = { iso3, iso2: ISO2[iso3], apps: apps.slice(0, 25), diff --git a/library/scripts/research/out/tables/messaging-channels.json b/library/scripts/research/out/tables/messaging-channels.json index 6b505cb50..cfd2b784e 100644 --- a/library/scripts/research/out/tables/messaging-channels.json +++ b/library/scripts/research/out/tables/messaging-channels.json @@ -2,7 +2,7 @@ "meta": { "title": "Messaging & App Channels", "grain": "one row per country x communication app", - "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp is the top messaging app in 54 countries, Telegram in 38 — neither appears in referral share at all.", + "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp (Messenger or Business) is the top messaging app in 73 countries, Telegram in 38 — neither appears in referral share at all.", "ios_caveat": "iOS ONLY. Most of these countries are Android-majority and Google publishes no keyless equivalent, so this is one eye open. An app absent here is NOT absent in-country. 50 of 175 countries have no Apple storefront.", "ranking_caveat": "A top-free RANK is most-DOWNLOADED recently, not most-used.", "classification_caveat": "kind is a name-keyword HEURISTIC over a known list, not a verified taxonomy.", @@ -2830,19 +2830,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Thailand", - "iso3": "THA", - "region": "Asia", - "app": "LINE MAN: Food Delivery & more", - "kind": "messaging", - "rank": 13, - "is_top_messaging": true, - "people_groups": 8, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Burkina Faso", "iso3": "BFA", @@ -3006,7 +2993,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 37, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 8, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", @@ -7809,19 +7796,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Maldives", - "iso3": "MDV", - "region": "Asia", - "app": "Malé Taxi Line", - "kind": "messaging", - "rank": 1, - "is_top_messaging": true, - "people_groups": 1, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Northern Mariana Islands", "iso3": "MNP", @@ -13120,7 +13094,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 33, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 1, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", diff --git a/plugin-designs/cities-internet/data/tables/messaging-channels.json b/plugin-designs/cities-internet/data/tables/messaging-channels.json index 6b505cb50..cfd2b784e 100644 --- a/plugin-designs/cities-internet/data/tables/messaging-channels.json +++ b/plugin-designs/cities-internet/data/tables/messaging-channels.json @@ -2,7 +2,7 @@ "meta": { "title": "Messaging & App Channels", "grain": "one row per country x communication app", - "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp is the top messaging app in 54 countries, Telegram in 38 — neither appears in referral share at all.", + "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp (Messenger or Business) is the top messaging app in 73 countries, Telegram in 38 — neither appears in referral share at all.", "ios_caveat": "iOS ONLY. Most of these countries are Android-majority and Google publishes no keyless equivalent, so this is one eye open. An app absent here is NOT absent in-country. 50 of 175 countries have no Apple storefront.", "ranking_caveat": "A top-free RANK is most-DOWNLOADED recently, not most-used.", "classification_caveat": "kind is a name-keyword HEURISTIC over a known list, not a verified taxonomy.", @@ -2830,19 +2830,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Thailand", - "iso3": "THA", - "region": "Asia", - "app": "LINE MAN: Food Delivery & more", - "kind": "messaging", - "rank": 13, - "is_top_messaging": true, - "people_groups": 8, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Burkina Faso", "iso3": "BFA", @@ -3006,7 +2993,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 37, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 8, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", @@ -7809,19 +7796,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Maldives", - "iso3": "MDV", - "region": "Asia", - "app": "Malé Taxi Line", - "kind": "messaging", - "rank": 1, - "is_top_messaging": true, - "people_groups": 1, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Northern Mariana Islands", "iso3": "MNP", @@ -13120,7 +13094,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 33, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 1, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", diff --git a/public/dx-maps/assets/tables/messaging-channels.json b/public/dx-maps/assets/tables/messaging-channels.json index 6b505cb50..cfd2b784e 100644 --- a/public/dx-maps/assets/tables/messaging-channels.json +++ b/public/dx-maps/assets/tables/messaging-channels.json @@ -2,7 +2,7 @@ "meta": { "title": "Messaging & App Channels", "grain": "one row per country x communication app", - "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp is the top messaging app in 54 countries, Telegram in 38 — neither appears in referral share at all.", + "why": "Referral stats (StatCounter) structurally cannot see messaging apps. Measured here: WhatsApp (Messenger or Business) is the top messaging app in 73 countries, Telegram in 38 — neither appears in referral share at all.", "ios_caveat": "iOS ONLY. Most of these countries are Android-majority and Google publishes no keyless equivalent, so this is one eye open. An app absent here is NOT absent in-country. 50 of 175 countries have no Apple storefront.", "ranking_caveat": "A top-free RANK is most-DOWNLOADED recently, not most-used.", "classification_caveat": "kind is a name-keyword HEURISTIC over a known list, not a verified taxonomy.", @@ -2830,19 +2830,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Thailand", - "iso3": "THA", - "region": "Asia", - "app": "LINE MAN: Food Delivery & more", - "kind": "messaging", - "rank": 13, - "is_top_messaging": true, - "people_groups": 8, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Burkina Faso", "iso3": "BFA", @@ -3006,7 +2993,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 37, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 8, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy", @@ -7809,19 +7796,6 @@ "classification": "name-keyword HEURISTIC, not a verified taxonomy", "source": "Apple App Store RSS — top free apps by country" }, - { - "country": "Maldives", - "iso3": "MDV", - "region": "Asia", - "app": "Malé Taxi Line", - "kind": "messaging", - "rank": 1, - "is_top_messaging": true, - "people_groups": 1, - "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", - "classification": "name-keyword HEURISTIC, not a verified taxonomy", - "source": "Apple App Store RSS — top free apps by country" - }, { "country": "Northern Mariana Islands", "iso3": "MNP", @@ -13120,7 +13094,7 @@ "app": "WhatsApp Messenger", "kind": "messaging", "rank": 33, - "is_top_messaging": false, + "is_top_messaging": true, "people_groups": 1, "platform_bias": "iOS only — most of these countries are Android-majority; absence here is not absence in-country", "classification": "name-keyword HEURISTIC, not a verified taxonomy",