fix: CPV kapisi havuzu blokluyordu — 3000 ilan icin AI beklenirken kullanici 120 ilan goruyordu; havuz aninda yayinlaniyor, AI arka planda tamamliyor
This commit is contained in:
+53
-15
@@ -21,7 +21,7 @@ import { SECTOR_RULES, INSTITUTION_RULES, classify, sektorBul, SEKTOR_ETIKETLERI
|
|||||||
import { loginWithGoogle, userFromAuthHeader } from './auth.mjs';
|
import { loginWithGoogle, userFromAuthHeader } from './auth.mjs';
|
||||||
import { emailConfigured, sendEmail, tenderDigestHtml, emailFrom } from './email.mjs';
|
import { emailConfigured, sendEmail, tenderDigestHtml, emailFrom } from './email.mjs';
|
||||||
import { aiConfigured, availableProviders, analyzeSpec } from './ai.mjs';
|
import { aiConfigured, availableProviders, analyzeSpec } from './ai.mjs';
|
||||||
import { classifyTenderCpv } from './cpv-classify.mjs';
|
import { classifyTenderCpv, classifyCpvByKeyword } from './cpv-classify.mjs';
|
||||||
import { billingConfigured, webhookConfigured, planCatalog, createCheckoutSession, verifyWebhook, planForPrice, cancelSubscription, IPTAL_NEDENLERI } from './billing.mjs';
|
import { billingConfigured, webhookConfigured, planCatalog, createCheckoutSession, verifyWebhook, planForPrice, cancelSubscription, IPTAL_NEDENLERI } from './billing.mjs';
|
||||||
import * as store from './user-store.mjs';
|
import * as store from './user-store.mjs';
|
||||||
import { planOf, haklar, kullanim, kotaKontrol, ozellikVar } from './entitlements.mjs';
|
import { planOf, haklar, kullanim, kotaKontrol, ozellikVar } from './entitlements.mjs';
|
||||||
@@ -292,16 +292,54 @@ async function cpvUygula(t) {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bir tender listesini, sınırlı paralellikle, CPV işlenmiş hale getirir. */
|
/**
|
||||||
async function cpvUygulaListe(tenders) {
|
* HIZLI CPV geçişi — yalnızca önbellek + anahtar kelime, AI YOK.
|
||||||
for (let i = 0; i < tenders.length; i += CPV_ESZAMANLI) {
|
*
|
||||||
const grup = tenders.slice(i, i + CPV_ESZAMANLI);
|
* Eskiden havuz, her ilan için AI çağrısı bitene kadar yayınlanmıyordu:
|
||||||
await Promise.all(grup.map((t) => cpvUygula(t)));
|
* 3.000 ilan × AI = saatler, bu sürede kullanıcı yalnızca ilk 120 ilanı
|
||||||
|
* görüyordu. Artık havuz anında yayınlanır; anahtar kelimenin çözemediği
|
||||||
|
* ilanlar arka planda AI ile tamamlanır.
|
||||||
|
*/
|
||||||
|
function cpvHizli(t) {
|
||||||
|
const ikn = t.ikn || '';
|
||||||
|
if (ikn) {
|
||||||
|
const onbellek = cpvCacheOku(ikn);
|
||||||
|
if (onbellek) { Object.assign(t, onbellek); return true; }
|
||||||
}
|
}
|
||||||
return tenders;
|
const anahtar = classifyCpvByKeyword(t);
|
||||||
|
if (anahtar) {
|
||||||
|
Object.assign(t, anahtar);
|
||||||
|
if (ikn) cpvCacheYaz(ikn, anahtar);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Object.assign(t, { cpv: [], cpvBolum: [], cpvLabel: 'Genel', cpvSource: 'bekliyor' });
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ilanlariBirlestir(mevcut, yeniAds) {
|
// Arka planda AI ile CPV tamamlama (havuzu bloklamaz)
|
||||||
|
const CPV_TUR_LIMIT = Number(process.env.CPV_TUR_LIMIT || 200);
|
||||||
|
let cpvCalisiyor = false;
|
||||||
|
async function cpvZenginlestir() {
|
||||||
|
if (cpvCalisiyor || !cache.tenders?.length) return;
|
||||||
|
cpvCalisiyor = true;
|
||||||
|
try {
|
||||||
|
const kuyruk = cache.tenders.filter((t) => t.cpvSource === 'bekliyor').slice(0, CPV_TUR_LIMIT);
|
||||||
|
if (!kuyruk.length) return;
|
||||||
|
const bekleyenToplam = cache.tenders.filter((t) => t.cpvSource === 'bekliyor').length;
|
||||||
|
console.log(`[cpv-zengin] ${kuyruk.length} ilan AI ile sınıflandırılacak (bekleyen ${bekleyenToplam})`);
|
||||||
|
for (let i = 0; i < kuyruk.length; i += CPV_ESZAMANLI) {
|
||||||
|
const grup = kuyruk.slice(i, i + CPV_ESZAMANLI);
|
||||||
|
await Promise.all(grup.map((t) => cpvUygula(t)));
|
||||||
|
}
|
||||||
|
console.log(`[cpv-zengin] ${kuyruk.length} ilan tamamlandı`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[cpv-zengin] hata:', e.message);
|
||||||
|
} finally {
|
||||||
|
cpvCalisiyor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ilanlariBirlestir(mevcut, yeniAds) {
|
||||||
const seen = new Set(mevcut.map((t) => t.id));
|
const seen = new Set(mevcut.map((t) => t.id));
|
||||||
const eklenen = [];
|
const eklenen = [];
|
||||||
for (const ad of yeniAds) {
|
for (const ad of yeniAds) {
|
||||||
@@ -310,9 +348,9 @@ async function ilanlariBirlestir(mevcut, yeniAds) {
|
|||||||
seen.add(t.id);
|
seen.add(t.id);
|
||||||
eklenen.push(t);
|
eklenen.push(t);
|
||||||
}
|
}
|
||||||
// GATE: CPV sınıflandırması tamamlanmadan hiçbir ilan çağırana dönmez —
|
// Hızlı sınıflandırma (önbellek + anahtar kelime). AI'a kalanlar arka planda
|
||||||
// bu dizi doğrudan arkaPlandaTamamla() içinde cache.tenders'a eklenir.
|
// tamamlanır; havuzun yayınlanması AI'ı beklemez.
|
||||||
await cpvUygulaListe(eklenen);
|
for (const t of eklenen) cpvHizli(t);
|
||||||
return eklenen;
|
return eklenen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +369,7 @@ async function arkaPlandaTamamla(numFound, ilkListe) {
|
|||||||
const sonuclar = await ilanSayfalari(page, grup);
|
const sonuclar = await ilanSayfalari(page, grup);
|
||||||
const ads = sonuclar.flatMap((r) => r.ads || []);
|
const ads = sonuclar.flatMap((r) => r.ads || []);
|
||||||
if (!ads.length) break;
|
if (!ads.length) break;
|
||||||
const eklenen = await ilanlariBirlestir(yeni, ads);
|
const eklenen = ilanlariBirlestir(yeni, ads);
|
||||||
if (eklenen.length) yeni.push(...eklenen);
|
if (eklenen.length) yeni.push(...eklenen);
|
||||||
page += grup;
|
page += grup;
|
||||||
await new Promise((r) => setTimeout(r, 120)); // kaynağı yormamak için
|
await new Promise((r) => setTimeout(r, 120)); // kaynağı yormamak için
|
||||||
@@ -345,6 +383,7 @@ async function arkaPlandaTamamla(numFound, ilkListe) {
|
|||||||
havuzKaydet(numFound, cache.tenders.length);
|
havuzKaydet(numFound, cache.tenders.length);
|
||||||
zenginUygula(); // önceden çözülmüşleri hemen uygula
|
zenginUygula(); // önceden çözülmüşleri hemen uygula
|
||||||
setTimeout(() => sektorZenginlestir().catch(() => {}), 5000); // kalanları arka planda
|
setTimeout(() => sektorZenginlestir().catch(() => {}), 5000); // kalanları arka planda
|
||||||
|
setTimeout(() => cpvZenginlestir().catch(() => {}), 15000); // CPV'yi AI ile tamamla
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[ihaleist-api] arka plan doldurma hatası:', e.message);
|
console.warn('[ihaleist-api] arka plan doldurma hatası:', e.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -462,9 +501,8 @@ async function getTenders(force = false) {
|
|||||||
const ilkListe = ads
|
const ilkListe = ads
|
||||||
.filter((ad) => (seen.has(ad.id) ? false : seen.add(ad.id)))
|
.filter((ad) => (seen.has(ad.id) ? false : seen.add(ad.id)))
|
||||||
.map(mapAd);
|
.map(mapAd);
|
||||||
// GATE: CPV sınıflandırması tamamlanmadan bu liste ne hemen servis edilir
|
// Hızlı sınıflandırma; AI'a kalanlar arka planda tamamlanır.
|
||||||
// ne de arkaPlandaTamamla()'nın temeli olur (bkz. cpvUygula yorumu).
|
for (const t of ilkListe) cpvHizli(t);
|
||||||
await cpvUygulaListe(ilkListe);
|
|
||||||
|
|
||||||
cache.totalCount = numFound || ilkListe.length;
|
cache.totalCount = numFound || ilkListe.length;
|
||||||
cache.fetchedAt = Date.now();
|
cache.fetchedAt = Date.now();
|
||||||
|
|||||||
Reference in New Issue
Block a user