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:
+58
-29
@@ -1,5 +1,7 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import React, {createContext, useContext, useState, useEffect, useMemo } from 'react';
|
||||
import { parseWebsiteCapabilitiesDynamically } from '../services/webScraperService';
|
||||
import { havuzuPuanla } from '../services/uyum';
|
||||
import { getToken } from '../services/auth';
|
||||
import { fetchLiveTenders, fetchTenderDetail, mergeUserState } from '../services/tenderApi';
|
||||
import { restoreSession, clearSession, getStoredUser } from '../services/auth';
|
||||
import { getAllUserData, setUserData, getPlan } from '../services/account';
|
||||
@@ -406,36 +408,63 @@ export const AppProvider = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const [userProfile, setUserProfile] = useState({
|
||||
name: 'Hikmet Tanrıverdi',
|
||||
username: 'tanriverdi',
|
||||
company: 'Tanrıverdi Medikal & Sağlık Teknolojileri A.Ş.',
|
||||
website: 'https://www.tanriverdimedikal.com.tr',
|
||||
trialDaysLeft: 7,
|
||||
role: 'Yönetici Tedarikçi',
|
||||
scrapedCapabilities: {
|
||||
scrapedAt: null,
|
||||
extractedSectors: [],
|
||||
extractedProducts: [],
|
||||
extractedCertifications: [],
|
||||
matchScoreBoost: 0
|
||||
}
|
||||
const BOS_YETKINLIK = {
|
||||
scrapedAt: null, extractedSectors: [], extractedProducts: [], extractedCertifications: [],
|
||||
ihaleKonulari: [], matchScoreBoost: 0,
|
||||
};
|
||||
const [userProfile, setUserProfile] = useState(() => {
|
||||
// Yetkinlikler uyum skorunun girdisi — sayfa yenilenince sıfırlanmasın
|
||||
let kayitli = null;
|
||||
try { kayitli = JSON.parse(localStorage.getItem('ihaleist_firma_profil') || 'null'); } catch { /* yoksay */ }
|
||||
return {
|
||||
name: 'Hikmet Tanrıverdi',
|
||||
username: 'tanriverdi',
|
||||
company: kayitli?.company || 'Tanrıverdi Medikal & Sağlık Teknolojileri A.Ş.',
|
||||
website: kayitli?.website || 'https://www.tanriverdimedikal.com.tr',
|
||||
trialDaysLeft: 7,
|
||||
role: 'Yönetici Tedarikçi',
|
||||
scrapedCapabilities: { ...BOS_YETKINLIK, ...(kayitli?.scrapedCapabilities || {}) },
|
||||
};
|
||||
});
|
||||
useEffect(() => {
|
||||
const veri = { company: userProfile.company, website: userProfile.website, scrapedCapabilities: userProfile.scrapedCapabilities };
|
||||
yerelKaydet('ihaleist_firma_profil', veri);
|
||||
if (veriSenkron) sunucuyaYaz('firma_profil', veri);
|
||||
}, [userProfile.company, userProfile.website, userProfile.scrapedCapabilities, veriSenkron]);
|
||||
|
||||
const scanCompanyWebsite = (url) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const newCapabilities = parseWebsiteCapabilitiesDynamically(url);
|
||||
// Uyum skoru: havuz ya da profil değişince tüm ihaleler yeniden puanlanır.
|
||||
// Sözlüksel, dış servis yok — 8 bin ihalede anında.
|
||||
const tendersSkorlu = useMemo(() => havuzuPuanla(tenders, userProfile), [tenders, userProfile.scrapedCapabilities]);
|
||||
|
||||
setUserProfile(prev => ({
|
||||
...prev,
|
||||
website: url,
|
||||
scrapedCapabilities: newCapabilities
|
||||
}));
|
||||
|
||||
resolve(newCapabilities);
|
||||
}, 1500);
|
||||
});
|
||||
// Önce gerçek tarama (/api/company/scan: site metni + model), başarısızsa
|
||||
// alan adından tahmin eden eski yol. Sonuç uyum skorunun girdisi olur.
|
||||
const scanCompanyWebsite = async (url) => {
|
||||
let yeni = null;
|
||||
try {
|
||||
const r = await fetch('/api/company/scan', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` },
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.success && d.sonuc) {
|
||||
const so = d.sonuc;
|
||||
yeni = {
|
||||
scrapedAt: new Date().toLocaleString('tr-TR'),
|
||||
extractedSectors: so.sektorler || [],
|
||||
extractedProducts: so.urunler || [],
|
||||
extractedCertifications: so.sertifikalar || [],
|
||||
ihaleKonulari: so.ihaleKonulari || [],
|
||||
ozet: so.ozet || null,
|
||||
tarananSayfa: d.tarananSayfa,
|
||||
matchScoreBoost: 0,
|
||||
gercekTarama: true,
|
||||
};
|
||||
}
|
||||
} catch { /* aşağıda yedek yol */ }
|
||||
if (!yeni) yeni = { ...parseWebsiteCapabilitiesDynamically(url), ihaleKonulari: [], gercekTarama: false };
|
||||
setUserProfile((prev) => ({ ...prev, website: url, scrapedCapabilities: yeni }));
|
||||
return yeni;
|
||||
};
|
||||
|
||||
const updateCompanyCapabilities = (updatedCapabilities) => {
|
||||
@@ -539,7 +568,7 @@ export const AppProvider = ({ children }) => {
|
||||
<AppContext.Provider value={{
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
tenders,
|
||||
tenders: tendersSkorlu,
|
||||
setTenders,
|
||||
isLiveMode,
|
||||
setIsLiveMode,
|
||||
|
||||
Reference in New Issue
Block a user