Populate search detail summaries from EKAP announcement tables

This commit is contained in:
2026-09-06 10:06:23 +03:00
parent a136a74a1b
commit 132bd815c6
4 changed files with 25 additions and 4 deletions
+8
View File
@@ -413,3 +413,11 @@ Son gerçek tarayıcı sonucu: ters Tab aktif INPUT ile dialog içinde; Escape s
Ürün arama sonuçlarında geçerli EKAP İKN varsa artık ana havuzda bulunmasa da detay açılır. Sonuç kararlı ekap-İKN kimliğiyle mevcut listeye eklenir; varsa sunucudan yüklenmiş kullanıcı durumu uygulanır. Böylece mevcut takip/hazırlık işlemleri aynı kayıt modeliyle çalışabilir. Detay metni İKN üzerinden ilan API'sinden alınır; yanıt başka İKN taşıyorsa reddedilir. İlan metni bulunmayıp yalnız temel bilgi dönmüşse belirgin kaynak uyarısı gösterilir. Hizmet/mal türü kaynaktan korunur; kalemler ve bilinmeyen tarih/saat uydurulmaz. Ürün arama sonuçlarında geçerli EKAP İKN varsa artık ana havuzda bulunmasa da detay açılır. Sonuç kararlı ekap-İKN kimliğiyle mevcut listeye eklenir; varsa sunucudan yüklenmiş kullanıcı durumu uygulanır. Böylece mevcut takip/hazırlık işlemleri aynı kayıt modeliyle çalışabilir. Detay metni İKN üzerinden ilan API'sinden alınır; yanıt başka İKN taşıyorsa reddedilir. İlan metni bulunmayıp yalnız temel bilgi dönmüşse belirgin kaynak uyarısı gösterilir. Hizmet/mal türü kaynaktan korunur; kalemler ve bilinmeyen tarih/saat uydurulmaz.
82 test ve üretim derlemesi geçti. Ek testler arama kaydının kararlı kimlik ve kaynak türüyle dönüşmesini, bilinmeyen alanları boş tutmasını, İKN endpoint seçimini, eksik resmi ilan uyarısını ve farklı İKN yanıtının reddini doğruluyor. Gerçek ürün arama yanıtından detay açma/takip kaydetme tarayıcı kabulü hâlâ açık. Ürün aramasının döndürdüğü her alanın resmi dokümanla teyidi ayrıca gereklidir. 82 test ve üretim derlemesi geçti. Ek testler arama kaydının kararlı kimlik ve kaynak türüyle dönüşmesini, bilinmeyen alanları boş tutmasını, İKN endpoint seçimini, eksik resmi ilan uyarısını ve farklı İKN yanıtının reddini doğruluyor. Gerçek ürün arama yanıtından detay açma/takip kaydetme tarayıcı kabulü hâlâ açık. Ürün aramasının döndürdüğü her alanın resmi dokümanla teyidi ayrıca gereklidir.
### D05 — Gerçek ürün araması, takip kalıcılığı ve EKAP tablo alanları
Yerel 5175 test hesabında gerçek `hemogram` araması 2.531 sonuç bildirdi. Ana havuzdaki beş sentetik kayıt arasında bulunmayan 2026/1431768 (İnönü Üniversitesi, Biyokimya Laboratuvarı 19 Kalem Tıbbi Sarf Malzeme Alımı) ürün aramasından açıldı. Kaynak ilan metni ve 08.09.2026 11:00 tarihi göründü. Hazırlık Görevi Aç işleminden sonra “Takipte” ve “✓ Görev açık · 08.09.2026 11:00” gösterildi. Sayfa yeniden yüklenip aynı ihale açıldığında iki durum da korundu; kaynak metni yeniden yükleniyordu. Gerçek teklif gönderilmedi. Bu test firma yeterliğini veya resmi doküman dosyasının tamamlandığını kanıtlamaz.
Bu akışta kaynakta 3.3 teslim yeri ve 3.4 teslim süresi bulunurken özet alanların boş kaldığı gözlendi. `/api/documents/ilan` yanıtına mevcut kaynak alanı ayrıştırıcısı bağlandı. EKAP HTML tablolarının metne dönüşümündeki `| : |` ayraçları yalnız ayrıştırma kopyasında normalleştirildi; özgün ilan metni korunuyor. Gerçek yanıtta görülen tablo biçimiyle tarih/saat, teslim yeri ve tam teslim süresi için regresyon testi eklendi.
Doğrulama: `npm test` ve `npm run build` başarılı. Mevcut büyük paket/statik-dinamik import uyarıları sürüyor. Çalışan eski test sunucusu yeni backend değişikliğini yüklemediğinden yeni özet alanlar için tarayıcı kabulü henüz tamamlanmadı. Son kanıt dosyası oluşturma denemesinde seçici zaman aşımı oluştu; dosya oluşturulduğu iddia edilmiyor. Yukarıdaki takip kalıcılığı bulgusu daha önce başarıyla dönen tarayıcı okumalarına dayanır. D01–D11 planının tümü tamamlanmış değildir.
+1 -1
View File
@@ -717,7 +717,7 @@ const server = http.createServer(async (req, res) => {
const ikn = url.searchParams.get('ikn'); const ikn = url.searchParams.get('ikn');
if (!ikn) return json(res, 400, { success: false, error: 'ikn gerekli' }); if (!ikn) return json(res, 400, { success: false, error: 'ikn gerekli' });
const out = await ilanMetni(ikn); const out = await ilanMetni(ikn);
return json(res, 200, { success: true, ...out }); return json(res, 200, { success: true, ...out, ...announcementFields(out.metin) });
} catch (e) { } catch (e) {
return json(res, 502, { success: false, error: e.message }); return json(res, 502, { success: false, error: e.message });
} }
+6 -3
View File
@@ -26,10 +26,13 @@ export function uniqueTenders(records) {
export function announcementFields(text) { export function announcementFields(text) {
const content = String(text || ''); const content = String(text || '');
const result = {aciklama:content}; const result = {aciklama:content};
const deadline = /Tarih\s*ve\s*Saati\s*:\s*(\d{2}\.\d{2}\.\d{4})\s*[-–]?\s*(\d{2}:\d{2})/i.exec(content); // EKAP table cells are separated by pipes in extracted text. Keep the
// original announcement intact; normalize separators only for field parsing.
const fields = content.replace(/\s*\|\s*/g, ' ');
const deadline = /Tarih\s*ve\s*Saati\s*:\s*(\d{2}\.\d{2}\.\d{4})\s*[-–]?\s*(\d{2}:\d{2})/i.exec(fields);
if (deadline) { result.tarih = deadline[1]; result.saat = deadline[2]; } if (deadline) { result.tarih = deadline[1]; result.saat = deadline[2]; }
const place = /3\.3\.?\s*[^:]{0,120}:\s*([\s\S]*?)(?=3\.4\.?)/i.exec(content); const place = /3\.3\.?\s*[^:]{0,120}:\s*([\s\S]*?)(?=3\.4\.?)/i.exec(fields);
const duration = /3\.4\.?\s*[^:]{0,100}:\s*([\s\S]*?)(?=3\.5\.?|4\s*[-–])/i.exec(content); const duration = /3\.4\.?\s*[^:]{0,100}:\s*([\s\S]*?)(?=3\.5\.?|4\s*[-–])/i.exec(fields);
if (place) result.teslimYeri = place[1].trim(); if (place) result.teslimYeri = place[1].trim();
if (duration) result.teslimatSuresi = duration[1].trim(); if (duration) result.teslimatSuresi = duration[1].trim();
if (/hizmet alımı\s+4734/i.test(content)) result.alimTuru = 'Kiralama ve Hizmet Alımı'; if (/hizmet alımı\s+4734/i.test(content)) result.alimTuru = 'Kiralama ve Hizmet Alımı';
+10
View File
@@ -16,3 +16,13 @@ test('announcement preserves full text and extracts source-backed fields',()=>{
const d=announcementFields(text);assert.equal(d.aciklama,text);assert.equal(d.saat,'10:00');assert.equal(d.teslimYeri,'Patoloji laboratuvarı');assert.equal(d.teslimatSuresi,'30 gün'); const d=announcementFields(text);assert.equal(d.aciklama,text);assert.equal(d.saat,'10:00');assert.equal(d.teslimYeri,'Patoloji laboratuvarı');assert.equal(d.teslimatSuresi,'30 gün');
assert.deepEqual(announcementFields('Eksik ilan'),{aciklama:'Eksik ilan'}); assert.deepEqual(announcementFields('Eksik ilan'),{aciklama:'Eksik ilan'});
}); });
// Shape observed in the real EKAP announcement for 2026/1431768.
test('EKAP table separators do not hide the deadline or leak into delivery fields',()=>{
const text='2.1. Tarih ve Saati | : | 08.09.2026 - 11:00 | 2.2. Adres | : | İhale salonu | 3.3. Yapılacağı/teslim edileceği yer | : | Turgut Özal Tıp Merkezi Deposu-Malatya | 3.4. Süresi/teslim tarihi | : | Her siparişin tebliğinden itibaren 30 (otuz) takvim günü | 3.5. İşe başlama tarihi | : | Sözleşme tarihi';
const d=announcementFields(text);
assert.equal(d.aciklama,text);
assert.equal(d.tarih,'08.09.2026');assert.equal(d.saat,'11:00');
assert.equal(d.teslimYeri,'Turgut Özal Tıp Merkezi Deposu-Malatya');
assert.equal(d.teslimatSuresi,'Her siparişin tebliğinden itibaren 30 (otuz) takvim günü');
});