Files
website/src/lib/world-opinions/data.ts
2026-10-25 22:16:25 +00:00

137 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// World Opinions: a hand-curated orientation guide, not a dataset of record.
// Positions are approximate and meant for comparing countries/parties relative
// to each other. The curated content (countries, party placements, profiles,
// narratives, sources) lives in the `stats.wo_*` tables (seeded by the
// stats_wo_seed migration) and is loaded by +page.server.ts; this file keeps
// the types, the label dictionaries and the lookups the components share.
import { getContext, setContext } from 'svelte';
export type RegionId =
| 'north-america'
| 'western-europe'
| 'nordics'
| 'eastern-europe'
| 'east-asia'
| 'south-asia'
| 'southeast-asia'
| 'latin-america'
| 'middle-east'
| 'africa'
| 'oceania';
export const regions: Record<RegionId, string> = {
'north-america': 'North America',
'western-europe': 'Western Europe',
nordics: 'Nordics',
'eastern-europe': 'Central & Eastern Europe',
'east-asia': 'East Asia',
'south-asia': 'South Asia',
'southeast-asia': 'Southeast Asia',
'latin-america': 'Latin America',
'middle-east': 'Middle East & North Africa',
africa: 'Sub-Saharan Africa',
oceania: 'Oceania'
};
// What a local means by "liberal".
export type LiberalMeaning = 'left' | 'market' | 'western';
export const liberalMeanings: Record<LiberalMeaning, string> = {
left: 'left-leaning, progressive',
market: 'free-market, often center-right',
western: 'pro-Western opposition to nationalists'
};
// What the main political divide is mostly about.
export type Divide = 'economy' | 'culture' | 'history' | 'none';
export const divides: Record<Divide, string> = {
economy: 'economy (taxes, welfare, unions)',
culture: 'culture (religion, identity, values)',
history: 'history & geopolitics',
none: 'no open party competition'
};
export type Country = {
id: string;
/** ISO 3166-1 numeric, as used by world-atlas */
iso: string;
name: string;
region: RegionId;
/**
* Inglehart–Welzel position. x: survival (-) ↔ self-expression (+);
* y: traditional (-) ↔ secular-rational (+). Undefined = not surveyed.
*/
values?: { x: number; y: number };
liberal?: LiberalMeaning;
divide?: Divide;
/** One line shown on hover, only where the label alone would mislead. */
note?: string;
};
// ---------------------------------------------------------------------------
// Party compass: parties on one shared scale
// econ: 0 = far left (state-led, redistributive) … 10 = far right (free market)
// social: 0 = progressive/libertarian … 10 = traditional/authoritarian
// Hand-placed approximations inspired by the Chapel Hill Expert Survey and
// Manifesto Project; good for relative position, not for decimals.
// ---------------------------------------------------------------------------
export type Party = {
country: string;
short: string;
name: string;
econ: number;
social: number;
note?: string;
};
// Country profiles, kept short enough to scan.
export type Profile = {
id: string;
/** What "normal" politics looks like, in one line. */
center: string;
fights: string[];
timeline: { year: string; label: string }[];
blindSpots: string[];
};
// One event, many stories.
export type Narrative = {
event: string;
years: string;
views: { country: string; story: string }[];
};
export type Source = { name: string; href: string };
/** What the page loads: plain arrays in display order. */
export type WorldOpinionsData = {
countries: Country[];
parties: Party[];
profiles: Profile[];
narratives: Narrative[];
sources: Source[];
};
/** The loaded data plus the lookups the components use. */
export type WorldOpinions = WorldOpinionsData & {
countryById: Map<string, Country>;
profileById: Map<string, Profile>;
/** Countries with compass parties, in the order their parties are listed. */
partyCountries: string[];
};
export function buildWorldOpinions(data: WorldOpinionsData): WorldOpinions {
return {
...data,
countryById: new Map(data.countries.map((country) => [country.id, country])),
profileById: new Map(data.profiles.map((profile) => [profile.id, profile])),
partyCountries: [...new Set(data.parties.map((party) => party.country))]
};
}
const KEY = Symbol('world-opinions');
/** Called once by the page; the map, panels and compass read it with `worldOpinions()`. */
export const provideWorldOpinions = (data: WorldOpinions) => setContext(KEY, data);
export const worldOpinions = () => getContext<WorldOpinions>(KEY);