import { cleanText } from '../lead-engine.mjs'; import { UA_HEADERS, buildLeadFromPlace } from './common.mjs'; import { escapeOverpassRegex } from './overpass.mjs'; async function findWikidataCityId(city) { // Busca por rotulo exato em SPARQL falha para nomes sem acento (base usa // "Santo Antonio", Wikidata registra "Santo Antônio"). wbsearchentities // e tolerante a acentos. try { const url = new URL('https://www.wikidata.org/w/api.php'); url.searchParams.set('action', 'wbsearchentities'); url.searchParams.set('search', city); url.searchParams.set('language', 'pt'); url.searchParams.set('type', 'item'); url.searchParams.set('format', 'json'); const response = await fetch(url, { headers: UA_HEADERS }); if (!response.ok) return ''; const data = await response.json(); const match = (data.search || []).find((item) => { const description = cleanText(item.description || ''); return description.includes('municipality') || description.includes('municipio') || description.includes('munic'); }); return match?.id || ''; } catch { return ''; } } // Fonte wikidata: empresas registradas no Wikidata por municipio (SPARQL). // Volume baixo, mas gratuito e util para achar empresas com website. export async function* collect(params, ctx) { const { uf, cities, segments, limits } = params; let executedQueries = 0; for (const city of cities || []) { if (ctx.isCancelled()) return; const cityId = await findWikidataCityId(city); if (!cityId) { ctx.log(`wikidata: municipio nao encontrado para ${city} — pulando`); continue; } const label = `${city} ${uf}`; const cityLeads = []; for (const segment of segments) { if (ctx.isCancelled()) return; if (limits.queryLimit > 0 && executedQueries >= limits.queryLimit) break; const regex = (segment.queries || []) .filter((term) => term.length >= 4) .slice(0, 10) .map(escapeOverpassRegex) .join('|'); if (!regex) continue; const sparql = ` SELECT ?item ?itemLabel ?website WHERE { ?item wdt:P31/wdt:P279* wd:Q4830453. { ?item wdt:P131 wd:${cityId}. } UNION { ?item wdt:P159/wdt:P131* wd:${cityId}. } OPTIONAL { ?item wdt:P856 ?website. } SERVICE wikibase:label { bd:serviceParam wikibase:language "pt,en". } FILTER(REGEX(?itemLabel, "${regex}", "i")) } LIMIT ${limits.maxPerQuery || 10} `; ctx.emit({ phase: 'wikidata', message: `${segment.name} em ${label}` }); executedQueries += 1; try { const url = new URL(ctx.endpoints.wikidataUrl); url.searchParams.set('query', sparql); url.searchParams.set('format', 'json'); const response = await fetch(url, { headers: { ...UA_HEADERS, accept: 'application/sparql-results+json' } }); if (!response.ok) throw new Error(`Wikidata HTTP ${response.status}`); const data = await response.json(); for (const result of data.results?.bindings || []) { const place = { name: result.itemLabel?.value || '', city, state: uf, segment: segment.name, website: result.website?.value || '', query: `wikidata ${segment.name} em ${label}`, place_id: result.item?.value || '' }; if (place.name) cityLeads.push(buildLeadFromPlace(place, segment, city, uf, 'wikidata')); } } catch (error) { ctx.log(`wikidata falhou em ${segment.name} ${label}: ${error.message}`); } } if (cityLeads.length) yield { origin: 'wikidata', label, leads: cityLeads }; } }