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 { emailConfigured, sendEmail, tenderDigestHtml, emailFrom } from './email.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 * as store from './user-store.mjs';
|
||||
import { planOf, haklar, kullanim, kotaKontrol, ozellikVar } from './entitlements.mjs';
|
||||
@@ -292,16 +292,54 @@ async function cpvUygula(t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/** Bir tender listesini, sınırlı paralellikle, CPV işlenmiş hale getirir. */
|
||||
async function cpvUygulaListe(tenders) {
|
||||
for (let i = 0; i < tenders.length; i += CPV_ESZAMANLI) {
|
||||
const grup = tenders.slice(i, i + CPV_ESZAMANLI);
|
||||
await Promise.all(grup.map((t) => cpvUygula(t)));
|
||||
/**
|
||||
* HIZLI CPV geçişi — yalnızca önbellek + anahtar kelime, AI YOK.
|
||||
*
|
||||
* Eskiden havuz, her ilan için AI çağrısı bitene kadar yayınlanmıyordu:
|
||||
* 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 eklenen = [];
|
||||
for (const ad of yeniAds) {
|
||||
@@ -310,9 +348,9 @@ async function ilanlariBirlestir(mevcut, yeniAds) {
|
||||
seen.add(t.id);
|
||||
eklenen.push(t);
|
||||
}
|
||||
// GATE: CPV sınıflandırması tamamlanmadan hiçbir ilan çağırana dönmez —
|
||||
// bu dizi doğrudan arkaPlandaTamamla() içinde cache.tenders'a eklenir.
|
||||
await cpvUygulaListe(eklenen);
|
||||
// Hızlı sınıflandırma (önbellek + anahtar kelime). AI'a kalanlar arka planda
|
||||
// tamamlanır; havuzun yayınlanması AI'ı beklemez.
|
||||
for (const t of eklenen) cpvHizli(t);
|
||||
return eklenen;
|
||||
}
|
||||
|
||||
@@ -331,7 +369,7 @@ async function arkaPlandaTamamla(numFound, ilkListe) {
|
||||
const sonuclar = await ilanSayfalari(page, grup);
|
||||
const ads = sonuclar.flatMap((r) => r.ads || []);
|
||||
if (!ads.length) break;
|
||||
const eklenen = await ilanlariBirlestir(yeni, ads);
|
||||
const eklenen = ilanlariBirlestir(yeni, ads);
|
||||
if (eklenen.length) yeni.push(...eklenen);
|
||||
page += grup;
|
||||
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);
|
||||
zenginUygula(); // önceden çözülmüşleri hemen uygula
|
||||
setTimeout(() => sektorZenginlestir().catch(() => {}), 5000); // kalanları arka planda
|
||||
setTimeout(() => cpvZenginlestir().catch(() => {}), 15000); // CPV'yi AI ile tamamla
|
||||
} catch (e) {
|
||||
console.warn('[ihaleist-api] arka plan doldurma hatası:', e.message);
|
||||
} finally {
|
||||
@@ -462,9 +501,8 @@ async function getTenders(force = false) {
|
||||
const ilkListe = ads
|
||||
.filter((ad) => (seen.has(ad.id) ? false : seen.add(ad.id)))
|
||||
.map(mapAd);
|
||||
// GATE: CPV sınıflandırması tamamlanmadan bu liste ne hemen servis edilir
|
||||
// ne de arkaPlandaTamamla()'nın temeli olur (bkz. cpvUygula yorumu).
|
||||
await cpvUygulaListe(ilkListe);
|
||||
// Hızlı sınıflandırma; AI'a kalanlar arka planda tamamlanır.
|
||||
for (const t of ilkListe) cpvHizli(t);
|
||||
|
||||
cache.totalCount = numFound || ilkListe.length;
|
||||
cache.fetchedAt = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user