Files
COMERCIAL-GTI/gti-maquina-comercial/src/sources/wikidata.mjs
T
GustavoandClaude Sonnet 5 f5af038e19 chore: import gti-maquina-comercial history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

git-subtree-dir: gti-maquina-comercial
git-subtree-mainline: 93ca82979e
git-subtree-split: b77484946f
2026-09-28 15:50:08 -03:00

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 };
}
}