CPV/OKAS ham kod sınıflandırması: yayınlanmadan önce tamamlanan bir gate + sidebar filtresi

- server/cpv-classify.mjs (yeni): existing (kalem kodu) → keyword (66 kural, 45 OKAS_BOLUM kodunun tamamı kapsanıyor) → AI (analyzeSpec, sadece sözlükte gerçekten var olan kod kabul edilir) hibrit sınıflandırma
- server/index.js: cache.tenders'a eklenmeden ÖNCE her ihale için CPV sınıflandırması await'leniyor (mevcut "sektör" alanının aksine, sonradan zenginleştirilmiyor) — hem ilk yükleme hem arka plan tamamlama yolunda
- İKN bazlı kalıcı SQLite önbellek (history-store.mjs) — aynı ilan bir daha asla yeniden sınıflandırılmıyor
- /api/meta/filtreler artık cpvBolumler döndürüyor
- Frontend: Sektörler'in hemen altına CPV Kodu filtresi (arama + çoklu seçim), ihale kartında CPV rozeti, landing page'de yeni özellik: "AB uyumlu CPV sınıflandırması — Türkiye'de ilk"
This commit is contained in:
2026-08-30 14:00:56 +03:00
parent f98568da8e
commit c5662d2e60
10 changed files with 490 additions and 15 deletions
+11 -1
View File
@@ -5,6 +5,7 @@
import fs from 'node:fs';
import { sektorKarar } from './siniflandir.mjs';
import { classifyCpvByKeyword } from './cpv-classify.mjs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { titleCaseTr } from './tr-text.mjs';
@@ -85,7 +86,7 @@ function classifyKurum(text) {
function mapDtRow(row) {
const det = parseDetail(row.detail_json);
const kurum = det.idareTam || row.idare_adi || '';
return {
const tender = {
id: `dt-${row.dogrudan_temin_id}`,
kaynak: 'EKAP · Doğrudan Temin',
kaynakUrl: 'https://ekap.kik.gov.tr/EKAP/YeniIhaleArama.aspx',
@@ -138,6 +139,15 @@ function mapDtRow(row) {
sozlesmeBedeli: det.sozlesmeBedeli || null,
kazananFirma: det.kazananFirma || null,
};
// CPV/OKAS — YAYINLANMADAN ÖNCE tamamlanır (bkz. cpv-classify.mjs).
// BransKodList zaten gerçek OKAS kodu taşır; 'existing' dalı bunu anında
// kullanır, AI'a nadiren düşer.
const cpv = classifyCpvByKeyword(tender) || { cpv: [], cpvBolum: [], cpvLabel: 'Genel', cpvSource: 'none' };
tender.cpv = cpv.cpv;
tender.cpvBolum = cpv.cpvBolum;
tender.cpvLabel = cpv.cpvLabel;
tender.cpvSource = cpv.cpvSource;
return tender;
}
export function readDtTenders(limit = 300) {