/** * ClinicalTrials.gov country names → ISO 3166-1 alpha-3 (pure, no I/O). * * ClinicalTrials.gov stores the country of a location as free text chosen by the registrant from * its own list; the API v2 returns mostly short English names ("United States", "South Korea", * "Turkey (Türkiye)", "Reunion") while older records and the legacy API used ISO-style long names * ("Korea, Republic of", "Russian Federation", "Réunion", "Viet Nam"). Both families are accepted * here. Territories keep their own ISO code (Puerto Rico → PRI, Hong Kong → HKG, Réunion → REU…) * because that is how the registrant counted the site; the UI may roll them up if it wishes. * * Dissolved states that have no ISO 3166-1 code today are deliberately left unmapped and listed in * `UNMAPPED_COUNTRY_NAMES` so a test fails when a new name appears in the database without a decision. */ export interface CountryCode { /** ISO 3166-1 alpha-3 (Kosovo uses the user-assigned XKX, as the World Bank and EU do). */ iso3: string; /** Display name (short English form). */ name: string; /** Spellings seen in ClinicalTrials.gov exports, in addition to `name`. */ aliases?: string[]; } export const COUNTRY_CODES: readonly CountryCode[] = [ { iso3: 'AFG', name: 'Afghanistan' }, { iso3: 'ALB', name: 'Albania' }, { iso3: 'DZA', name: 'Algeria' }, { iso3: 'ASM', name: 'American Samoa' }, { iso3: 'AND', name: 'Andorra' }, { iso3: 'AGO', name: 'Angola' }, { iso3: 'ATG', name: 'Antigua and Barbuda' }, { iso3: 'ARG', name: 'Argentina' }, { iso3: 'ARM', name: 'Armenia' }, { iso3: 'ABW', name: 'Aruba' }, { iso3: 'AUS', name: 'Australia' }, { iso3: 'AUT', name: 'Austria' }, { iso3: 'AZE', name: 'Azerbaijan' }, { iso3: 'BHS', name: 'Bahamas', aliases: ['The Bahamas', 'Bahamas, The'] }, { iso3: 'BHR', name: 'Bahrain' }, { iso3: 'BGD', name: 'Bangladesh' }, { iso3: 'BRB', name: 'Barbados' }, { iso3: 'BLR', name: 'Belarus' }, { iso3: 'BEL', name: 'Belgium' }, { iso3: 'BLZ', name: 'Belize' }, { iso3: 'BEN', name: 'Benin' }, { iso3: 'BMU', name: 'Bermuda' }, { iso3: 'BTN', name: 'Bhutan' }, { iso3: 'BOL', name: 'Bolivia', aliases: ['Bolivia, Plurinational State of', 'Bolivia (Plurinational State of)'] }, { iso3: 'BIH', name: 'Bosnia and Herzegovina' }, { iso3: 'BWA', name: 'Botswana' }, { iso3: 'BRA', name: 'Brazil' }, { iso3: 'BRN', name: 'Brunei', aliases: ['Brunei Darussalam'] }, { iso3: 'BGR', name: 'Bulgaria' }, { iso3: 'BFA', name: 'Burkina Faso' }, { iso3: 'BDI', name: 'Burundi' }, { iso3: 'KHM', name: 'Cambodia' }, { iso3: 'CMR', name: 'Cameroon' }, { iso3: 'CAN', name: 'Canada' }, { iso3: 'CPV', name: 'Cape Verde', aliases: ['Cabo Verde'] }, { iso3: 'CYM', name: 'Cayman Islands' }, { iso3: 'CAF', name: 'Central African Republic' }, { iso3: 'TCD', name: 'Chad' }, { iso3: 'CHL', name: 'Chile' }, { iso3: 'CHN', name: 'China' }, { iso3: 'COL', name: 'Colombia' }, { iso3: 'COM', name: 'Comoros' }, { iso3: 'COG', name: 'Congo', aliases: ['Republic of the Congo', 'Congo, Republic of the'] }, { iso3: 'COD', name: 'Democratic Republic of the Congo', aliases: ['Congo, The Democratic Republic of the', 'Congo, Democratic Republic of the', 'Congo, The Democratic Republic'] }, { iso3: 'CRI', name: 'Costa Rica' }, { iso3: 'CIV', name: "Côte d'Ivoire", aliases: ["Cote d'Ivoire", 'Ivory Coast'] }, { iso3: 'HRV', name: 'Croatia' }, { iso3: 'CUB', name: 'Cuba' }, { iso3: 'CUW', name: 'Curaçao', aliases: ['Curacao'] }, { iso3: 'CYP', name: 'Cyprus' }, { iso3: 'CZE', name: 'Czechia', aliases: ['Czech Republic'] }, { iso3: 'DNK', name: 'Denmark' }, { iso3: 'DJI', name: 'Djibouti' }, { iso3: 'DMA', name: 'Dominica' }, { iso3: 'DOM', name: 'Dominican Republic' }, { iso3: 'ECU', name: 'Ecuador' }, { iso3: 'EGY', name: 'Egypt' }, { iso3: 'SLV', name: 'El Salvador' }, { iso3: 'GNQ', name: 'Equatorial Guinea' }, { iso3: 'ERI', name: 'Eritrea' }, { iso3: 'EST', name: 'Estonia' }, { iso3: 'SWZ', name: 'Eswatini', aliases: ['Swaziland'] }, { iso3: 'ETH', name: 'Ethiopia' }, { iso3: 'FRO', name: 'Faroe Islands', aliases: ['Faeroe Islands'] }, { iso3: 'FJI', name: 'Fiji' }, { iso3: 'FIN', name: 'Finland' }, { iso3: 'FRA', name: 'France' }, { iso3: 'GUF', name: 'French Guiana' }, { iso3: 'PYF', name: 'French Polynesia' }, { iso3: 'GAB', name: 'Gabon' }, { iso3: 'GMB', name: 'Gambia', aliases: ['The Gambia', 'Gambia, The'] }, { iso3: 'GEO', name: 'Georgia' }, { iso3: 'DEU', name: 'Germany' }, { iso3: 'GHA', name: 'Ghana' }, { iso3: 'GIB', name: 'Gibraltar' }, { iso3: 'GRC', name: 'Greece' }, { iso3: 'GRL', name: 'Greenland' }, { iso3: 'GRD', name: 'Grenada' }, { iso3: 'GLP', name: 'Guadeloupe' }, { iso3: 'GUM', name: 'Guam' }, { iso3: 'GTM', name: 'Guatemala' }, { iso3: 'GIN', name: 'Guinea' }, { iso3: 'GNB', name: 'Guinea-Bissau' }, { iso3: 'GUY', name: 'Guyana' }, { iso3: 'HTI', name: 'Haiti' }, { iso3: 'HND', name: 'Honduras' }, { iso3: 'HKG', name: 'Hong Kong', aliases: ['Hong Kong SAR', 'Hong Kong, China'] }, { iso3: 'HUN', name: 'Hungary' }, { iso3: 'ISL', name: 'Iceland' }, { iso3: 'IND', name: 'India' }, { iso3: 'IDN', name: 'Indonesia' }, { iso3: 'IRN', name: 'Iran', aliases: ['Iran, Islamic Republic of', 'Iran (Islamic Republic of)'] }, { iso3: 'IRQ', name: 'Iraq' }, { iso3: 'IRL', name: 'Ireland' }, { iso3: 'ISR', name: 'Israel' }, { iso3: 'ITA', name: 'Italy' }, { iso3: 'JAM', name: 'Jamaica' }, { iso3: 'JPN', name: 'Japan' }, { iso3: 'JOR', name: 'Jordan' }, { iso3: 'KAZ', name: 'Kazakhstan' }, { iso3: 'KEN', name: 'Kenya' }, { iso3: 'PRK', name: 'North Korea', aliases: ["Korea, Democratic People's Republic of", "Democratic People's Republic of Korea"] }, { iso3: 'KOR', name: 'South Korea', aliases: ['Korea, Republic of', 'Republic of Korea', 'Korea'] }, { iso3: 'XKX', name: 'Kosovo' }, { iso3: 'KWT', name: 'Kuwait' }, { iso3: 'KGZ', name: 'Kyrgyzstan' }, { iso3: 'LAO', name: 'Laos', aliases: ["Lao People's Democratic Republic"] }, { iso3: 'LVA', name: 'Latvia' }, { iso3: 'LBN', name: 'Lebanon' }, { iso3: 'LSO', name: 'Lesotho' }, { iso3: 'LBR', name: 'Liberia' }, { iso3: 'LBY', name: 'Libya', aliases: ['Libyan Arab Jamahiriya'] }, { iso3: 'LIE', name: 'Liechtenstein' }, { iso3: 'LTU', name: 'Lithuania' }, { iso3: 'LUX', name: 'Luxembourg' }, { iso3: 'MAC', name: 'Macao', aliases: ['Macau', 'Macao SAR'] }, { iso3: 'MDG', name: 'Madagascar' }, { iso3: 'MWI', name: 'Malawi' }, { iso3: 'MYS', name: 'Malaysia' }, { iso3: 'MDV', name: 'Maldives' }, { iso3: 'MLI', name: 'Mali' }, { iso3: 'MLT', name: 'Malta' }, { iso3: 'MTQ', name: 'Martinique' }, { iso3: 'MRT', name: 'Mauritania' }, { iso3: 'MUS', name: 'Mauritius' }, { iso3: 'MYT', name: 'Mayotte' }, { iso3: 'MEX', name: 'Mexico' }, { iso3: 'MDA', name: 'Moldova', aliases: ['Moldova, Republic of', 'Republic of Moldova'] }, { iso3: 'MCO', name: 'Monaco' }, { iso3: 'MNG', name: 'Mongolia' }, { iso3: 'MNE', name: 'Montenegro' }, { iso3: 'MAR', name: 'Morocco' }, { iso3: 'MOZ', name: 'Mozambique' }, { iso3: 'MMR', name: 'Myanmar', aliases: ['Burma'] }, { iso3: 'NAM', name: 'Namibia' }, { iso3: 'NPL', name: 'Nepal' }, { iso3: 'NLD', name: 'Netherlands', aliases: ['The Netherlands', 'Netherlands, The'] }, { iso3: 'NCL', name: 'New Caledonia' }, { iso3: 'NZL', name: 'New Zealand' }, { iso3: 'NIC', name: 'Nicaragua' }, { iso3: 'NER', name: 'Niger' }, { iso3: 'NGA', name: 'Nigeria' }, { iso3: 'MKD', name: 'North Macedonia', aliases: ['Macedonia, The Former Yugoslav Republic of', 'Macedonia', 'North Macedonia, Republic of'] }, { iso3: 'MNP', name: 'Northern Mariana Islands' }, { iso3: 'NOR', name: 'Norway' }, { iso3: 'OMN', name: 'Oman' }, { iso3: 'PAK', name: 'Pakistan' }, { iso3: 'PSE', name: 'Palestinian Territories', aliases: ['Palestinian Territory, occupied', 'Palestinian Territory', 'Palestine, State of', 'State of Palestine', 'Palestine'] }, { iso3: 'PAN', name: 'Panama' }, { iso3: 'PNG', name: 'Papua New Guinea' }, { iso3: 'PRY', name: 'Paraguay' }, { iso3: 'PER', name: 'Peru' }, { iso3: 'PHL', name: 'Philippines' }, { iso3: 'POL', name: 'Poland' }, { iso3: 'PRT', name: 'Portugal' }, { iso3: 'PRI', name: 'Puerto Rico' }, { iso3: 'QAT', name: 'Qatar' }, { iso3: 'REU', name: 'Réunion', aliases: ['Reunion'] }, { iso3: 'ROU', name: 'Romania' }, { iso3: 'RUS', name: 'Russia', aliases: ['Russian Federation'] }, { iso3: 'RWA', name: 'Rwanda' }, { iso3: 'KNA', name: 'Saint Kitts and Nevis' }, { iso3: 'LCA', name: 'Saint Lucia' }, { iso3: 'VCT', name: 'Saint Vincent and the Grenadines' }, { iso3: 'WSM', name: 'Samoa' }, { iso3: 'SMR', name: 'San Marino' }, { iso3: 'STP', name: 'São Tomé and Príncipe', aliases: ['Sao Tome and Principe'] }, { iso3: 'SAU', name: 'Saudi Arabia' }, { iso3: 'SEN', name: 'Senegal' }, { iso3: 'SRB', name: 'Serbia' }, { iso3: 'SYC', name: 'Seychelles' }, { iso3: 'SLE', name: 'Sierra Leone' }, { iso3: 'SGP', name: 'Singapore' }, { iso3: 'SVK', name: 'Slovakia' }, { iso3: 'SVN', name: 'Slovenia' }, { iso3: 'SLB', name: 'Solomon Islands' }, { iso3: 'SOM', name: 'Somalia' }, { iso3: 'ZAF', name: 'South Africa' }, { iso3: 'SSD', name: 'South Sudan' }, { iso3: 'ESP', name: 'Spain' }, { iso3: 'LKA', name: 'Sri Lanka' }, { iso3: 'SDN', name: 'Sudan' }, { iso3: 'SUR', name: 'Suriname' }, { iso3: 'SWE', name: 'Sweden' }, { iso3: 'CHE', name: 'Switzerland' }, { iso3: 'SYR', name: 'Syria', aliases: ['Syrian Arab Republic'] }, { iso3: 'TWN', name: 'Taiwan', aliases: ['Taiwan, Province of China'] }, { iso3: 'TJK', name: 'Tajikistan' }, { iso3: 'TZA', name: 'Tanzania', aliases: ['Tanzania, United Republic of', 'United Republic of Tanzania'] }, { iso3: 'THA', name: 'Thailand' }, { iso3: 'TLS', name: 'Timor-Leste', aliases: ['East Timor'] }, { iso3: 'TGO', name: 'Togo' }, { iso3: 'TON', name: 'Tonga' }, { iso3: 'TTO', name: 'Trinidad and Tobago' }, { iso3: 'TUN', name: 'Tunisia' }, { iso3: 'TUR', name: 'Türkiye', aliases: ['Turkey (Türkiye)', 'Turkey', 'Turkiye'] }, { iso3: 'TKM', name: 'Turkmenistan' }, { iso3: 'UGA', name: 'Uganda' }, { iso3: 'UKR', name: 'Ukraine' }, { iso3: 'ARE', name: 'United Arab Emirates' }, { iso3: 'GBR', name: 'United Kingdom', aliases: ['United Kingdom of Great Britain and Northern Ireland', 'UK', 'Great Britain'] }, { iso3: 'USA', name: 'United States', aliases: ['United States of America', 'USA'] }, { iso3: 'URY', name: 'Uruguay' }, { iso3: 'UZB', name: 'Uzbekistan' }, { iso3: 'VUT', name: 'Vanuatu' }, { iso3: 'VEN', name: 'Venezuela', aliases: ['Venezuela, Bolivarian Republic of', 'Venezuela (Bolivarian Republic of)'] }, { iso3: 'VNM', name: 'Vietnam', aliases: ['Viet Nam'] }, // ClinicalTrials.gov writes "Virgin Islands" for sites in the U.S. Virgin Islands (facilities in St. Thomas / St. Croix); the British Virgin Islands are spelled out. { iso3: 'VIR', name: 'U.S. Virgin Islands', aliases: ['Virgin Islands', 'Virgin Islands (U.S.)', 'Virgin Islands, U.S.', 'United States Virgin Islands'] }, { iso3: 'VGB', name: 'British Virgin Islands', aliases: ['Virgin Islands, British'] }, { iso3: 'YEM', name: 'Yemen' }, { iso3: 'ZMB', name: 'Zambia' }, { iso3: 'ZWE', name: 'Zimbabwe' }, ]; /** * Names present in ClinicalTrials.gov exports that have NO current ISO 3166-1 code (dissolved * states or an empty field). They keep `iso3 = null` in `trial_site_country_counts`, appear in the * table by name, and are never painted on the map. */ export const UNMAPPED_COUNTRY_NAMES: readonly string[] = ['', 'Serbia and Montenegro', 'Former Serbia and Montenegro', 'Federal Republic of Yugoslavia', 'Former Yugoslavia', 'Yugoslavia', 'Netherlands Antilles', 'Czechoslovakia', 'Union of Soviet Socialist Republics', 'USSR']; /** Lower-case, trimmed, straight apostrophes, collapsed whitespace. */ export function normalizeCountryName(name: string): string { return name .normalize('NFC') .replace(/[‘’ʼ`´]/g, "'") .replace(/\s+/g, ' ') .trim() .toLowerCase(); } const NAME_INDEX: ReadonlyMap = (() => { const m = new Map(); for (const c of COUNTRY_CODES) { m.set(normalizeCountryName(c.name), c); for (const a of c.aliases ?? []) m.set(normalizeCountryName(a), c); m.set(c.iso3.toLowerCase(), c); } return m; })(); const ISO_INDEX: ReadonlyMap = new Map(COUNTRY_CODES.map((c) => [c.iso3, c])); /** ISO 3166-1 alpha-3 for a ClinicalTrials.gov country name, or null when unknown/unmappable. */ export function countryToIso3(name: string | null | undefined): string | null { if (name == null) return null; const c = NAME_INDEX.get(normalizeCountryName(name)); return c ? c.iso3 : null; } /** Full entry (iso3 + display name) for a ClinicalTrials.gov country name or an ISO3 code. */ export function lookupCountry(nameOrIso3: string | null | undefined): CountryCode | null { if (nameOrIso3 == null) return null; return NAME_INDEX.get(normalizeCountryName(nameOrIso3)) ?? null; } /** Display name for an ISO3 code (falls back to the code). */ export function iso3ToName(iso3: string): string { return ISO_INDEX.get(iso3.toUpperCase())?.name ?? iso3; } export function isKnownUnmapped(name: string): boolean { const n = normalizeCountryName(name); return UNMAPPED_COUNTRY_NAMES.some((u) => normalizeCountryName(u) === n); }