feat: gercek sirket-ihale uyum skoru — sahte %98 rozeti gitti
Karttaki "%N Uyumlu" rozeti sabit yazili degerlerdi (liveTenderService 98/95/91/99), sunucudan null geliyordu. Artik profil terimleri ile ihale metni arasinda agirlikli sozcuk eslesmesi (src/services/uyum.js): - terimler: urunler (1.0), ihale konulari (1.0), sektorler (0.6), sertifikalar (0.3); "ve/alimi/malzeme" gibi durma kelimeleri sayilmaz - alanlar: baslik (1.0), teklif cetveli kalemleri (1.1 — en guclu sinyal), OKAS/CPV (0.7), ilan metni (0.6), sektor (0.5) - doygunluk egrisi: 1 guclu eslesme ≈ 55, 2 ≈ 75, 3+ ≈ 90 - profilde terim yoksa skor null → sahte yuzde gosterilmez - her skorun nedeni var: kartta ipucu, detayda "‘eldiven’ · teklif cetveli" Olculen: "VETERINER ILAC VE SARF MALZEME" basligi hicbir urunu icermiyor ama kalemlerinde ELDIVEN + HEMOGRAM KITI var → %86; kirtasiye → 0. - Siralama: "Sirketime En Uyumlu"; hizli filtre 'uyumlu' (≥60) - Profil yetkinlikleri artik kalici (localStorage + sunucu 'firma_profil'); onceden sayfa yenilenince siliniyordu - Web sitesi taramasi gercek uca bagli (/api/company/scan: site metni + model → sektorler/urunler/sertifikalar/ihaleKonulari); basarisizsa eski alan-adi tahmini yedek. Onceden 1,5 sn setTimeout ile taklitti. Sozluksel yol bilincli: dis servis yok, 8 bin ihalede aninda, deterministik, aciklanabilir. Embedding katmani istenirse ustune eklenebilir. (Fikir: kaanvp/kobi-ihale-eslestirme'nin esik + aciklama deseni; kod alinmadi.) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -149,7 +149,11 @@ export const TenderCard = ({ tender }) => {
|
||||
</span>
|
||||
)}
|
||||
{tender.aiMatchScore != null && (
|
||||
<span className="bg-blue-50 text-blue-700 border border-blue-200 text-[11px] font-bold px-2 py-1 rounded-md flex items-center gap-1">
|
||||
<span
|
||||
title={tender.uyumNedenleri?.length
|
||||
? tender.uyumNedenleri.map((n) => `‘${n.terim}’ ${n.alan}nde geçiyor`).join('\n')
|
||||
: 'Profilinizdeki terimlerden hiçbiri bu ihalede geçmiyor'}
|
||||
className="bg-blue-50 text-blue-700 border border-blue-200 text-[11px] font-bold px-2 py-1 rounded-md flex items-center gap-1">
|
||||
<Sparkles className="w-3 h-3 text-blue-600" />
|
||||
<span>%{tender.aiMatchScore} Uyumlu</span>
|
||||
</span>
|
||||
|
||||
@@ -450,6 +450,16 @@ END:VCALENDAR`;
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Skorun nedeni: hangi profil terimi ihalenin neresinde geçiyor */}
|
||||
{tender.uyumNedenleri?.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 pt-1">
|
||||
{tender.uyumNedenleri.map((n, i) => (
|
||||
<span key={i} className="bg-white/10 border border-white/15 text-[11px] text-amber-100 px-2 py-0.5 rounded-lg">
|
||||
<strong className="text-white">{n.terim}</strong> · {n.alan}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-2">
|
||||
<div className="bg-white/10 p-4 rounded-xl border border-white/10 space-y-1">
|
||||
|
||||
@@ -70,6 +70,7 @@ export const TenderList = () => {
|
||||
|
||||
return true;
|
||||
}).sort((a, b) => {
|
||||
if (sortBy === 'uyum') return (b.aiMatchScore ?? -1) - (a.aiMatchScore ?? -1) || b.id.localeCompare(a.id);
|
||||
if (sortBy === 'newest') return b.id.localeCompare(a.id);
|
||||
if (sortBy === 'urgent') return a.tarih.localeCompare(b.tarih);
|
||||
if (sortBy === 'distant') return b.tarih.localeCompare(a.tarih);
|
||||
@@ -162,6 +163,7 @@ export const TenderList = () => {
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
className="bg-slate-50 border border-slate-300 rounded-lg px-3 py-1.5 text-xs font-semibold text-slate-700 focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="uyum">Şirketime En Uyumlu</option>
|
||||
<option value="newest">En Yeni Eklenenler</option>
|
||||
<option value="urgent">Tarihi Yaklaşanlar (Acil)</option>
|
||||
<option value="distant">Tarihi Uzak Olanlar</option>
|
||||
|
||||
Reference in New Issue
Block a user