borderless / apis /official_sources.py
spagestic's picture
Add country metadata and source quality signals for immigration research.
897a75f
Raw
History Blame Contribute Delete
4.59 kB
"""Helpers for identifying official immigration source domains."""
from __future__ import annotations
from urllib.parse import urlparse
OFFICIAL_DOMAIN_HINTS: dict[str, list[str]] = {
"AU": ["homeaffairs.gov.au", "immi.homeaffairs.gov.au"],
"CA": ["canada.ca", "cic.gc.ca"],
"DE": ["make-it-in-germany.com", "bamf.de", "auswaertiges-amt.de"],
"DK": ["nyidanmark.dk"],
"ES": ["inclusion.gob.es", "exteriores.gob.es"],
"FI": ["migri.fi"],
"FR": ["france-visas.gouv.fr", "service-public.fr"],
"GB": ["gov.uk"],
"IE": ["irishimmigration.ie", "enterprise.gov.ie"],
"JP": ["isa.go.jp", "mofa.go.jp"],
"NL": ["ind.nl"],
"NO": ["udi.no"],
"NZ": ["immigration.govt.nz"],
"PT": ["aima.gov.pt", "eportugal.gov.pt", "vistos.mne.gov.pt"],
"SE": ["migrationsverket.se"],
"SG": ["ica.gov.sg", "mom.gov.sg"],
"US": ["uscis.gov", "travel.state.gov"],
}
COUNTRY_ALIASES: dict[str, str] = {
"australia": "AU",
"canada": "CA",
"denmark": "DK",
"finland": "FI",
"france": "FR",
"germany": "DE",
"ireland": "IE",
"japan": "JP",
"netherlands": "NL",
"new zealand": "NZ",
"norway": "NO",
"portugal": "PT",
"singapore": "SG",
"spain": "ES",
"sweden": "SE",
"uk": "GB",
"united kingdom": "GB",
"united states": "US",
"usa": "US",
}
UNOFFICIAL_CONTEXT_TERMS = (
"blog",
"forum",
"reddit",
"quora",
"lawfirm",
"law-firm",
"immigrationlaw",
"relocation",
"movingto",
)
def domain_from_url(url: str) -> str:
parsed = urlparse(url or "")
host = parsed.netloc.lower().split("@")[-1].split(":")[0]
if host.startswith("www."):
host = host[4:]
return host
def is_domain_match(domain: str, official_domain: str) -> bool:
normalized = official_domain.lower()
return domain == normalized or domain.endswith(f".{normalized}")
def hints_for_country(country: str | None) -> list[str]:
if not country:
return []
normalized = country.strip().upper()
return OFFICIAL_DOMAIN_HINTS.get(normalized, [])
def infer_country_codes(text: str, country: str | None = None) -> list[str]:
codes: list[str] = []
if country:
normalized = country.strip().upper()
if normalized in OFFICIAL_DOMAIN_HINTS:
codes.append(normalized)
lowered = (text or "").lower()
for alias, code in COUNTRY_ALIASES.items():
if alias in lowered and code not in codes:
codes.append(code)
return codes
def infer_official_domains(
text: str,
country: str | None = None,
*,
max_domains: int = 8,
) -> list[str]:
domains: list[str] = []
for code in infer_country_codes(text, country):
for domain in OFFICIAL_DOMAIN_HINTS.get(code, []):
if domain not in domains:
domains.append(domain)
if len(domains) >= max_domains:
return domains
return domains
def classify_source(url: str) -> dict[str, object]:
domain = domain_from_url(url)
known_official = [
official_domain
for domains in OFFICIAL_DOMAIN_HINTS.values()
for official_domain in domains
if is_domain_match(domain, official_domain)
]
if known_official:
return {
"domain": domain,
"type": "official_government",
"is_official": True,
"reason": f"Matches official domain hint {known_official[0]}",
}
if domain.endswith(".gov") or ".gov." in domain or domain.endswith(".gouv.fr"):
return {
"domain": domain,
"type": "government",
"is_official": True,
"reason": "Government domain pattern",
}
if "embassy" in domain or "consulate" in domain:
return {
"domain": domain,
"type": "embassy_or_consulate",
"is_official": True,
"reason": "Embassy or consulate domain pattern",
}
if any(term in domain for term in UNOFFICIAL_CONTEXT_TERMS):
return {
"domain": domain,
"type": "unofficial_context",
"is_official": False,
"reason": "Likely blog, forum, legal-service, or relocation context",
}
return {
"domain": domain,
"type": "unknown",
"is_official": False,
"reason": "No official-domain signal detected",
}