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:
2026-08-30 01:39:46 +03:00
parent a6a421d745
commit 495118ef9e
2 changed files with 164 additions and 1 deletions
+80 -1
View File
@@ -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,