feat: doküman metninden sektör zenginleştirme — başlığı sinyalsiz ilanlar ilan metninden sınıflandırılıyor (kalıcı önbellekli)
This commit is contained in:
+80
-1
@@ -25,7 +25,8 @@ import { billingConfigured, webhookConfigured, planCatalog, createCheckoutSessio
|
||||
import * as store from './user-store.mjs';
|
||||
import { planOf, haklar, kullanim, kotaKontrol, ozellikVar } from './entitlements.mjs';
|
||||
import { searchTed } from './ted.mjs';
|
||||
import { readHistory, historyStats, havuzOlc, havuzGecmisi, havuzOzet } from './history-store.mjs';
|
||||
import { readHistory, historyStats, havuzOlc, havuzGecmisi, havuzOzet,
|
||||
sektorZenginOku, sektorZenginYaz, sektorZenginDenenmis, sektorZenginSayi } from './history-store.mjs';
|
||||
import { startScheduler, schedulerStatus } from './scheduler.mjs';
|
||||
import { searchKik, kararMetni } from './kik.mjs';
|
||||
import { documentUrl, ilanMetni, documentText, browserAvailable } from './documents.mjs';
|
||||
@@ -299,6 +300,8 @@ async function arkaPlandaTamamla(numFound, ilkListe) {
|
||||
}
|
||||
console.log(`[ihaleist-api] havuz hazır: ${cache.tenders.length}/${numFound} ilan`);
|
||||
havuzKaydet(numFound, cache.tenders.length);
|
||||
zenginUygula(); // önceden çözülmüşleri hemen uygula
|
||||
setTimeout(() => sektorZenginlestir().catch(() => {}), 5000); // kalanları arka planda
|
||||
} catch (e) {
|
||||
console.warn('[ihaleist-api] arka plan doldurma hatası:', e.message);
|
||||
} finally {
|
||||
@@ -306,6 +309,67 @@ async function arkaPlandaTamamla(numFound, ilkListe) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sektör zenginleştirme -------------------------------------------------
|
||||
// Başlığında sektör sinyali olmayan ilanların ("MUHTELİF MALZEME ALIMI")
|
||||
// ilan METNİ indirilip sınıflandırılır. Sonuç kalıcı; aynı ilan bir daha
|
||||
// indirilmez. Kaynağı yormamak için turda sınırlı sayıda ilan işlenir.
|
||||
const ZENGIN_TUR_LIMIT = Number(process.env.SEKTOR_ZENGIN_LIMIT || 300);
|
||||
const ZENGIN_PARALEL = Number(process.env.SEKTOR_ZENGIN_PARALEL || 4);
|
||||
let zenginCalisiyor = false;
|
||||
|
||||
async function sektorZenginlestir() {
|
||||
if (zenginCalisiyor || !cache.tenders?.length) return;
|
||||
zenginCalisiyor = true;
|
||||
try {
|
||||
const adaylar = cache.tenders.filter((t) => t.sektor === 'Genel' && t.isLiveFeed && t.id?.startsWith('ilan-'));
|
||||
if (!adaylar.length) return;
|
||||
const denenmis = sektorZenginDenenmis(adaylar.map((t) => t.id));
|
||||
const kuyruk = adaylar.filter((t) => !denenmis.has(t.id)).slice(0, ZENGIN_TUR_LIMIT);
|
||||
if (!kuyruk.length) return;
|
||||
|
||||
console.log(`[sektor-zengin] ${kuyruk.length} ilan metni sınıflandırılacak (kalan aday ${adaylar.length - denenmis.size})`);
|
||||
const sonuc = [];
|
||||
for (let i = 0; i < kuyruk.length; i += ZENGIN_PARALEL) {
|
||||
const grup = kuyruk.slice(i, i + ZENGIN_PARALEL);
|
||||
await Promise.all(grup.map(async (t) => {
|
||||
try {
|
||||
const det = await getDetail(t.id.replace(/^ilan-/, ''));
|
||||
const sek = sektorBul(t.baslik || '', det.aciklama || '');
|
||||
sonuc.push({ id: t.id, sektor: sek });
|
||||
} catch (e) {
|
||||
// Metin alınamadıysa 'Genel' yaz — sonsuz yeniden deneme olmasın.
|
||||
sonuc.push({ id: t.id, sektor: 'Genel' });
|
||||
}
|
||||
}));
|
||||
// Kaynağı yormamak için gruplar arasında kısa duraklama.
|
||||
if (i + ZENGIN_PARALEL < kuyruk.length) await new Promise((r) => setTimeout(r, 250));
|
||||
}
|
||||
const yazilan = sektorZenginYaz(sonuc);
|
||||
const cozulen = sonuc.filter((r) => r.sektor !== 'Genel').length;
|
||||
console.log(`[sektor-zengin] ${yazilan} kayıt işlendi, ${cozulen} tanesi sınıflandırıldı`);
|
||||
zenginUygula();
|
||||
} catch (e) {
|
||||
console.warn('[sektor-zengin] hata:', e.message);
|
||||
} finally {
|
||||
zenginCalisiyor = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Kayıtlı zenginleştirmeyi havuzdaki 'Genel' ilanlara uygular. */
|
||||
function zenginUygula() {
|
||||
if (!cache.tenders?.length) return 0;
|
||||
const genel = cache.tenders.filter((t) => t.sektor === 'Genel');
|
||||
if (!genel.length) return 0;
|
||||
const harita = sektorZenginOku(genel.map((t) => t.id));
|
||||
let n = 0;
|
||||
for (const t of genel) {
|
||||
const s = harita.get(t.id);
|
||||
if (s && s !== 'Genel') { t.sektor = s; n++; }
|
||||
}
|
||||
if (n) console.log(`[sektor-zengin] ${n} ilan doküman metninden sınıflandırıldı`);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Havuz büyüklüğünü kaydeder; ani düşüş / eksik yükleme varsa yöneticiyi uyarır.
|
||||
let sonHavuzUyarisi = 0;
|
||||
function dtSayisiGuvenli() {
|
||||
@@ -939,6 +1003,21 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/health') return json(res, 200, { ok: true });
|
||||
if (url.pathname === '/api/health/sektor') {
|
||||
const say = sektorZenginSayi();
|
||||
const genel = (cache.tenders || []).filter((t) => t.sektor === 'Genel').length;
|
||||
const toplam = (cache.tenders || []).length;
|
||||
return json(res, 200, {
|
||||
success: true,
|
||||
havuz: toplam,
|
||||
genelKalan: genel,
|
||||
genelOran: toplam ? Number((genel / toplam).toFixed(3)) : null,
|
||||
dokumandanDenenen: say.toplam,
|
||||
dokumandanCozulen: say.cozulen,
|
||||
calisiyor: zenginCalisiyor,
|
||||
});
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/meta/filtreler') {
|
||||
return json(res, 200, {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user