fix: make news geotagging deterministic
All checks were successful
Codex Template Compliance / template-compliance (pull_request) Successful in 5s
Build / test-and-image (pull_request) Successful in 1m4s

This commit is contained in:
2026-05-17 18:54:35 +02:00
parent a809a55881
commit 900f43ba13
3 changed files with 85 additions and 6 deletions

View File

@@ -83,16 +83,48 @@ const geoKeywords = {
'IMF':[38.9,-77],'World Bank':[38.9,-77],'UN':[40.7,-74],
};
function geoTagText(text) {
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function geoKeywordRegex(keyword) {
const flags = keyword.length <= 3 && keyword === keyword.toUpperCase() ? 'u' : 'iu';
return new RegExp(`(^|[^\\p{L}\\p{N}])${escapeRegex(keyword)}(?=$|[^\\p{L}\\p{N}])`, flags);
}
const geoKeywordEntries = Object.entries(geoKeywords)
.sort((a, b) => b[0].length - a[0].length)
.map(([keyword, coords]) => ({ keyword, coords, pattern: geoKeywordRegex(keyword) }));
export function geoTagText(text) {
if (!text) return null;
for (const [keyword, [lat, lon]] of Object.entries(geoKeywords)) {
if (text.includes(keyword)) {
for (const { keyword, coords, pattern } of geoKeywordEntries) {
if (pattern.test(text)) {
const [lat, lon] = coords;
return { lat, lon, region: keyword };
}
}
return null;
}
function stableHash(value) {
let hash = 2166136261;
for (let i = 0; i < value.length; i++) {
hash ^= value.charCodeAt(i);
hash = Math.imul(hash, 16777619);
}
return hash >>> 0;
}
export function stableGeoJitter(key, axis) {
const bucket = stableHash(`${axis}:${key}`) / 0xffffffff;
return (bucket - 0.5) * 2;
}
function newsGeoKey(item) {
return `${item.source || ''}|${item.title || ''}|${item.date || ''}|${item.url || ''}`;
}
function sanitizeExternalUrl(raw) {
if (!raw) return undefined;
try {
@@ -235,8 +267,8 @@ export async function fetchAllNews() {
source: item.source,
date: item.date,
url: item.url,
lat: geo.lat + (Math.random() - 0.5) * 2,
lon: geo.lon + (Math.random() - 0.5) * 2,
lat: geo.lat + stableGeoJitter(newsGeoKey(item), 'lat'),
lon: geo.lon + stableGeoJitter(newsGeoKey(item), 'lon'),
region: geo.region
});
}