Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: gti-maquina-comercial git-subtree-mainline:93ca82979egit-subtree-split:b77484946f
105 lines
3.7 KiB
JavaScript
105 lines
3.7 KiB
JavaScript
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 };
|
|
}
|
|
}
|