prefer official EKAP detail deadline
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
{
|
||||
"fetchedAt": "2026-09-06T16:38:41.047Z",
|
||||
"scope": "Salt okunur EKAP API sorgusu; teklif, ödeme veya başvuru gönderilmedi.",
|
||||
"cases": [
|
||||
{
|
||||
"scenario": "mikroskop",
|
||||
"expectedCity": "Giresun",
|
||||
"source": "EKAP v2 resmi ihale arama/detay/ilan API",
|
||||
"sourceUrl": "https://ekapv2.kik.gov.tr/ekap/search",
|
||||
"ikn": "2026/1582991",
|
||||
"title": "2 KALEM MİKROSKOP ALIM İHALESİ",
|
||||
"institution": "GİRESUN İL SAĞLIK MÜDÜRLÜĞÜ GİRESUN EĞİTİM VE ARAŞTIRMA HASTANESİ BAŞHEKİMLİĞİ",
|
||||
"province": "GİRESUN",
|
||||
"tenderDateTime": "10.09.2026 10:00",
|
||||
"status": "İhale İlanı Yayımlanmış, Katılıma Açık",
|
||||
"type": "Mal",
|
||||
"procedure": "İhale Usulü: Açık",
|
||||
"announcementDate": "2026-08-27T00:00:00",
|
||||
"documentCount": 0,
|
||||
"announcementPresent": true,
|
||||
"eTender": true,
|
||||
"partialOffer": true,
|
||||
"deliveryPlace": "Hastanemize bağlı Patoloji Laboratuvarı Birimine çalışır vaziyette teslim edilecektir.",
|
||||
"tenderPlace": "Satınalma Birimi Aksu Mah. Mehmet İzmen Cad. No:145 Merkez / GİRESUN Toplantı Salonu",
|
||||
"okas": [
|
||||
{
|
||||
"code": "38510000",
|
||||
"name": "Mikroskoplar"
|
||||
},
|
||||
{
|
||||
"code": "38515200",
|
||||
"name": "Floresan mikroskop"
|
||||
}
|
||||
],
|
||||
"declaredFeatures": [
|
||||
"TENDER_DETAIL.E_IHALE",
|
||||
"TENDER_DETAIL.KISMI_TEKLIF_VEREBILIR",
|
||||
"TENDER_DETAIL.YERLI_ISTEKLI_AVANTAJ",
|
||||
"TENDER_DETAIL.YABANCI_ISTEKLI_KATILIM",
|
||||
"TENDER_DETAIL.MESLEKI_TEKNIK_YETERLIK"
|
||||
],
|
||||
"parsedAnnouncement": {
|
||||
"tarih": "10.09.2026",
|
||||
"saat": "10:00",
|
||||
"teslimYeri": "Hastanemize bağlı Patoloji Laboratuvarı Birimine çalışır vaziyette teslim edilecektir.",
|
||||
"teslimatSuresi": "Mal teslimi sözleşme imzalandığı tarihten itibaren 30 gün içerisinde yapılacaktır."
|
||||
},
|
||||
"announcement": {
|
||||
"count": 1,
|
||||
"items": [
|
||||
{
|
||||
"tip": "1",
|
||||
"tarih": "2026-08-27T00:00:00",
|
||||
"baslik": "MİKROSKOP SATIN ALINACAKTIR",
|
||||
"htmlChars": 15283,
|
||||
"htmlSha256": "153cb6f50bc97007e83e655520871bc03d76e1069f215d63590b5144ee5649a3"
|
||||
}
|
||||
]
|
||||
},
|
||||
"textChars": 5502,
|
||||
"textSha256": "02a2465e0fbb1addc3fb231803bac9c9cf755fc98544ab49942601a69496c4c7"
|
||||
},
|
||||
{
|
||||
"scenario": "sarf",
|
||||
"expectedCity": "Kayseri",
|
||||
"source": "EKAP v2 resmi ihale arama/detay/ilan API",
|
||||
"sourceUrl": "https://ekapv2.kik.gov.tr/ekap/search",
|
||||
"ikn": "2026/1534665",
|
||||
"title": "26 KALEM LABORATUVAR SARF MALZEME ALIMI",
|
||||
"institution": "KAYSERİ İL SAĞLIK MÜDÜRLÜĞÜ KAYSERİ DEVLET HASTANESİ BAŞHEKİMLİĞİ",
|
||||
"province": "KAYSERİ",
|
||||
"tenderDateTime": "22.09.2026 11:00",
|
||||
"status": "İhale İlanı Yayımlanmış, Katılıma Açık",
|
||||
"type": "Mal",
|
||||
"procedure": "İhale Usulü: Açık",
|
||||
"announcementDate": "2026-08-25T00:00:00",
|
||||
"documentCount": 0,
|
||||
"announcementPresent": true,
|
||||
"eTender": true,
|
||||
"partialOffer": true,
|
||||
"deliveryPlace": "HASTANEMİZ LABORATUVAR DEPOSU",
|
||||
"tenderPlace": "Hastanemiz 1. Blok İhale Salonu ",
|
||||
"okas": [
|
||||
{
|
||||
"code": "33793000",
|
||||
"name": "Cam laboratuar gereçleri"
|
||||
}
|
||||
],
|
||||
"declaredFeatures": [
|
||||
"TENDER_DETAIL.E_IHALE",
|
||||
"TENDER_DETAIL.KISMI_TEKLIF_VEREBILIR",
|
||||
"TENDER_DETAIL.YERLI_ISTEKLI_AVANTAJ",
|
||||
"TENDER_DETAIL.MESLEKI_TEKNIK_YETERLIK"
|
||||
],
|
||||
"parsedAnnouncement": {
|
||||
"tarih": "22.09.2026",
|
||||
"saat": "11:00",
|
||||
"teslimYeri": "HASTANEMİZ LABORATUVAR DEPOSU",
|
||||
"teslimatSuresi": "İlk parti sözleşme imzalanmasından itibaren 15 (on beş) gün içerisinde ilgili depo ile irtibata geçilerek depo talebi ve stok durumlarına göre teslim edilecektir. Diğer partiler yükleniciye en az 15 (on beş) gün önce bildirim yapmak kaydı ile malzemelerin istemi yapılarak teslimatı yapılacaktır. Öngörülemeyen durumlar olması halinde idarece en az 15(on beş)gün önce bildirim yapmak kaydı ile idare teslimat programında değişiklik yapma hakkına sahiptir."
|
||||
},
|
||||
"announcement": {
|
||||
"count": 1,
|
||||
"items": [
|
||||
{
|
||||
"tip": "1",
|
||||
"tarih": "2026-08-25T00:00:00",
|
||||
"baslik": "LABORATUVAR SARF MALZEME SATIN ALINACAKTIR",
|
||||
"htmlChars": 16114,
|
||||
"htmlSha256": "7b4e8d444b378014c5e2be428f393abeea5acdecb27d315d80f26205870d34e6"
|
||||
}
|
||||
]
|
||||
},
|
||||
"textChars": 6260,
|
||||
"textSha256": "55fb32a8eec91dc17bdbb966f987f4af66d351a70b3204fbac41f5654650ecd5"
|
||||
},
|
||||
{
|
||||
"scenario": "cihaz-servis",
|
||||
"expectedCity": "Antalya (Akdeniz Üniversitesi)",
|
||||
"source": "EKAP v2 resmi ihale arama/detay/ilan API",
|
||||
"sourceUrl": "https://ekapv2.kik.gov.tr/ekap/search",
|
||||
"ikn": "2026/1499061",
|
||||
"title": "(2696) MERKEZ LABORATUVARI İHTİYACINA LABORATUVAR MALZEME (13 KALEM)",
|
||||
"institution": "AKDENİZ ÜNİVERSİTESİ REKTÖRLÜĞÜ ÜNİVERSİTE HASTANESİ BAŞMÜDÜRLÜĞÜ",
|
||||
"province": "ANTALYA",
|
||||
"tenderDateTime": "22.09.2026 10:30",
|
||||
"status": "İhale İlanı Yayımlanmış, Katılıma Açık",
|
||||
"type": "Mal",
|
||||
"procedure": "İhale Usulü: Açık",
|
||||
"announcementDate": "2026-08-25T00:00:00",
|
||||
"documentCount": 0,
|
||||
"announcementPresent": true,
|
||||
"eTender": true,
|
||||
"partialOffer": true,
|
||||
"deliveryPlace": "Antalya Akdeniz Üniversitesi Hastanesi ",
|
||||
"tenderPlace": "Antalya Akdeniz Üniversitesi Hastanesi Satınalma Müdürlüğü Dumlupınar Bulvarı Kampüs Antalya ",
|
||||
"okas": [
|
||||
{
|
||||
"code": "38000000",
|
||||
"name": "Laboratuar, optik ve hassas ekipmanları (gözlük hariç)"
|
||||
}
|
||||
],
|
||||
"declaredFeatures": [
|
||||
"TENDER_DETAIL.E_IHALE",
|
||||
"TENDER_DETAIL.E_EKSILTME",
|
||||
"TENDER_DETAIL.KISMI_TEKLIF_VEREBILIR",
|
||||
"TENDER_DETAIL.YERLI_ISTEKLI_AVANTAJ",
|
||||
"TENDER_DETAIL.YABANCI_ISTEKLI_KATILIM"
|
||||
],
|
||||
"parsedAnnouncement": {
|
||||
"tarih": "22.09.2026",
|
||||
"saat": "10:30",
|
||||
"teslimYeri": "Antalya Akdeniz Üniversitesi Hastanesi",
|
||||
"teslimatSuresi": "Tüm cihazlar teknik şartnamede belirtilen koşullarda çalışır ve sonuç veriyor şekilde kurulduktan sonra 16 ay kurumumuzda kalacaktır. Malzemeler sipariş verildikçe olacaktır. Bu süreler içerisinde firmanın cihazın çalışması ve teknik bakım gibi tüm konulardaki yükümlülükleri devam edecektir.\n-Tüm cihazlar ve sistemler, sözleşme yapıldıktan sonra 30 takvim günü içerisinde laboratuvara çalışır şekilde kurulmuş olmalıdır.\n- Sözleşmenin imzalanmasından sonra idarenin ihtiyacına göre idare tarafından yüklenicinin bu sözleşmede belirtilen bildirime esas faks numarasına gönderilen sipariş (malzeme talep formu), faks tarihinden itibaren 30 gün içerisinde İdareye teslim edilecektir."
|
||||
},
|
||||
"announcement": {
|
||||
"count": 1,
|
||||
"items": [
|
||||
{
|
||||
"tip": "1",
|
||||
"tarih": "2026-08-25T00:00:00",
|
||||
"baslik": "LABORATUVAR MALZEMESİ SATIN ALINACAKTIR",
|
||||
"htmlChars": 15710,
|
||||
"htmlSha256": "87d63a4980186a888db8b6da553adb529d3a8ffcfb612877079567397ad2bbd7"
|
||||
}
|
||||
]
|
||||
},
|
||||
"textChars": 6080,
|
||||
"textSha256": "a14d8ad2c18c120c62f9e3ed00032953139e9022be36ad7508b028c4a23e3b9f"
|
||||
},
|
||||
{
|
||||
"scenario": "kismi-teklif",
|
||||
"expectedCity": "Aksaray",
|
||||
"source": "EKAP v2 resmi ihale arama/detay/ilan API",
|
||||
"sourceUrl": "https://ekapv2.kik.gov.tr/ekap/search",
|
||||
"ikn": "2026/1573235",
|
||||
"title": "34 Kısım Nükleer Tıp ve Laboratuvar Sarf Malzemesi Alımı",
|
||||
"institution": "AKSARAY İL SAĞLIK MÜDÜRLÜĞÜ AKSARAY EĞİTİM VE ARAŞTIRMA HASTANESİ BAŞHEKİMLİĞİ",
|
||||
"province": "AKSARAY",
|
||||
"tenderDateTime": "16.09.2026 10:00",
|
||||
"status": "İhale İlanı Yayımlanmış, Katılıma Açık",
|
||||
"type": "Mal",
|
||||
"procedure": "İhale Usulü: Açık",
|
||||
"announcementDate": "2026-08-25T00:00:00",
|
||||
"documentCount": 1,
|
||||
"announcementPresent": true,
|
||||
"eTender": true,
|
||||
"partialOffer": true,
|
||||
"deliveryPlace": "Aksaray Eğitim ve Araştırma Hastanesi Laboratuvar Sarf Deposu ",
|
||||
"tenderPlace": "Aksaray Eğitim Araştırma Hastanesi Satınalma Birimi ",
|
||||
"okas": [
|
||||
{
|
||||
"code": "33141000",
|
||||
"name": "Tek kullanımlık kimyasal olmayan tıbbi sarf malzemeleri ve sarf hematolojik malzemeleri"
|
||||
}
|
||||
],
|
||||
"declaredFeatures": [
|
||||
"TENDER_DETAIL.E_IHALE",
|
||||
"TENDER_DETAIL.KISMI_TEKLIF_VEREBILIR",
|
||||
"TENDER_DETAIL.YERLI_ISTEKLI_AVANTAJ"
|
||||
],
|
||||
"parsedAnnouncement": {
|
||||
"tarih": "16.09.2026",
|
||||
"saat": "10:00",
|
||||
"teslimYeri": "Aksaray Eğitim ve Araştırma Hastanesi Laboratuvar Sarf Deposu",
|
||||
"teslimatSuresi": "Sözleşmeye Müteakip ilgili idarenin isteğiyle tek partide yada partiler halinde teslim edilecektir. Sipariş tarihinden itibaren malzemeler 15 gün içerisinde ilgili sağlık tesisine teslim edilecektir .",
|
||||
"sourceDateWarning": "Resmî ihale detay tarihi ile ilan metnindeki tarih farklı; güncel detay tarihi esas alındı.",
|
||||
"announcementDateObserved": "09.09.2026 10:00"
|
||||
},
|
||||
"announcement": {
|
||||
"count": 1,
|
||||
"items": [
|
||||
{
|
||||
"tip": "1",
|
||||
"tarih": "2026-08-25T00:00:00",
|
||||
"baslik": "TIBBİ SARF MALZEME SATIN ALINACAKTIR",
|
||||
"htmlChars": 14984,
|
||||
"htmlSha256": "353ffe8dd36dfac143fac4bc8a32197358dbb83989aed620309a77dc8aeb18a7"
|
||||
}
|
||||
]
|
||||
},
|
||||
"textChars": 5306,
|
||||
"textSha256": "f7a3b0e1c97ea6f14abd00123ec8a2e148a516f7481213e9740139c289d03fdf",
|
||||
"dateConsistency": {
|
||||
"officialDetail": "16.09.2026 10:00",
|
||||
"announcementHtml": "09.09.2026 10:00",
|
||||
"resolution": "Resmî ihale detay tarihi esas alındı; ilan tarihi farkı uyarı olarak korunuyor."
|
||||
}
|
||||
},
|
||||
{
|
||||
"scenario": "100-kalem",
|
||||
"expectedCity": "İzmir (Tepecik)",
|
||||
"source": "EKAP v2 resmi ihale arama/detay/ilan API",
|
||||
"sourceUrl": "https://ekapv2.kik.gov.tr/ekap/search",
|
||||
"ikn": "2026/1523851",
|
||||
"title": "100 KALEM LABORATUVAR MALZEMESİ ALIMI (NÜKLEER TIP-MİKROBİYOLOJİ-BİYOKİMYA-PATOLOJİ)",
|
||||
"institution": "İZMİR İL SAĞLIK MÜDÜRLÜĞÜ İZMİR TEPECİK EĞİTİM VE ARAŞTIRMA HASTANESİ BAŞHEKİMLİĞİ",
|
||||
"province": "İZMİR",
|
||||
"tenderDateTime": "11.09.2026 10:00",
|
||||
"status": "İhale İlanı Yayımlanmış, Katılıma Açık",
|
||||
"type": "Mal",
|
||||
"procedure": "İhale Usulü: Açık",
|
||||
"announcementDate": "2026-08-19T00:00:00",
|
||||
"documentCount": 1,
|
||||
"announcementPresent": true,
|
||||
"eTender": true,
|
||||
"partialOffer": true,
|
||||
"deliveryPlace": "HASTANEMİZ LABORATUVAR KİT DEPOSU",
|
||||
"tenderPlace": "İZMİR TEPECİK EĞİTİM VE ARAŞTIRMA HASTANESİ TOPLANTI SALONU ",
|
||||
"okas": [
|
||||
{
|
||||
"code": "33696500",
|
||||
"name": "Laboratuar ayıraçları"
|
||||
}
|
||||
],
|
||||
"declaredFeatures": [
|
||||
"TENDER_DETAIL.E_IHALE",
|
||||
"TENDER_DETAIL.KISMI_TEKLIF_VEREBILIR",
|
||||
"TENDER_DETAIL.YERLI_ISTEKLI_AVANTAJ",
|
||||
"TENDER_DETAIL.YABANCI_ISTEKLI_KATILIM"
|
||||
],
|
||||
"parsedAnnouncement": {
|
||||
"tarih": "11.09.2026",
|
||||
"saat": "10:00",
|
||||
"teslimYeri": "HASTANEMİZ LABORATUVAR KİT DEPOSU",
|
||||
"teslimatSuresi": "İhale kararının onaylanıp sözleşmenin imzalanmasına müteakip işe başlanır. Sözleşme bitiş tarihi sözleşme imzalanma tarihinden itibaren 6 ay'dır. Kalemler peyderpey alınabilecektir. İlgili depodan, depo birim sorumlusunun belirleyeceği miktarda siparişi alarak 10 gün içinde ilk teslimatı yapacaktır.(İlk teslimatta ayrıca sipariş verilmeyecek olup tüm sorumluluk yüklenici firmaya aittir.)"
|
||||
},
|
||||
"announcement": {
|
||||
"count": 1,
|
||||
"items": [
|
||||
{
|
||||
"tip": "1",
|
||||
"tarih": "2026-08-19T00:00:00",
|
||||
"baslik": "LABORATUVAR MALZEMESİ SATIN ALINACAKTIR",
|
||||
"htmlChars": 15296,
|
||||
"htmlSha256": "62f0f3305fd56bdb12e74d3b838f23b2ead439d62e64db755b36c9016b5fa055"
|
||||
}
|
||||
]
|
||||
},
|
||||
"textChars": 5612,
|
||||
"textSha256": "a9a055648cab9e52ecb1e951fb5d577e6060689629f1a9aaf54b332cefbfb040"
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
"Aksaray ilan HTML tarihindeki 09.09.2026 değeri ile EKAP ihale detayındaki 16.09.2026 değeri çelişmektedir; uygulama resmî ihale detayını esas alır ve farkı sourceDateWarning ile bildirir."
|
||||
]
|
||||
}
|
||||
@@ -2290,3 +2290,9 @@ Doğrulama: depo içinde bu modüle ait import/referans kalmadı; `npm test` ve
|
||||
Doğrudan temin worker’ı yalnızca seçili illeri tanıyan yerel tablo yerine ortak 81 il plaka haritasını kullanıyor. Böylece 2–5 ve 28–81 arası kodlar artık “Plaka N” olarak kaybolmuyor; arama, bölge ve sağlık tedarikçisi filtreleri kanonik il adını alıyor.
|
||||
|
||||
Doğrulama: yeni regresyon 1–81 kodlarının tamamını ve Giresun, Aksaray, Iğdır, Düzce örneklerini doğruluyor. `npm test` 297/297 ve üretim derlemesi başarılı; gerçek DT worker taraması ve canlı veri kabulü hâlâ açık.
|
||||
|
||||
### 2026-09-06 · EKAP resmî ihale detay tarihi önceliği
|
||||
|
||||
EKAP ilan metnindeki tarih ile `ihaleBilgi.ihaleTarihSaat` alanı çeliştiğinde resmî ihale detay tarihi artık tek kaynak olarak korunuyor. `officialDateFields` tarih/saat alanını normalize ediyor; `mergeAnnouncementFields` ilan metninin teslimat ve diğer alanlarını korurken resmî tarihi kullanıyor ve farkı `sourceDateWarning` ile bildiriyor. Aksaray `2026/1573235` için detay tarihi `16.09.2026 10:00`, ilan HTML'inde görülen eski değer `09.09.2026 10:00` olarak kanıtlandı. D10 sentetik kabul fixture'ı da güncel resmî tarihe çekildi.
|
||||
|
||||
Doğrulama: `npm test` 299/299 ve üretim derlemesi başarılı. Beş ihale kanıtı [`qa-official-five-tenders.json`](evidence/qa-official-five-tenders.json) içinde tutuluyor; gerçek EKAP doküman indirme ve canlı deployment hâlâ açık.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import { searchTenders, ekapPost, getIhaleDetay } from './ekap-v2.mjs';
|
||||
import { officialDateFields } from './tender-quality.mjs';
|
||||
|
||||
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
|
||||
|
||||
@@ -49,6 +50,7 @@ export async function ilanMetni(ikn) {
|
||||
const t = await findByIkn(ikn);
|
||||
const d = await getIhaleDetay(t.id);
|
||||
const bilgi = d?.ihaleBilgi || {};
|
||||
const officialDate = officialDateFields(bilgi.ihaleTarihSaat);
|
||||
const okas = d?.ihtiyacKalemiOkasList || [];
|
||||
const parcalar = [
|
||||
`İhale adı: ${d?.ihaleAdi || t.ihaleAdi || ''}`,
|
||||
@@ -80,6 +82,7 @@ export async function ilanMetni(ikn) {
|
||||
idareAdi: d?.idareAdi || t.idareAdi,
|
||||
ilanSayisi: ilanlar.length,
|
||||
okasSayisi: okas.length,
|
||||
...officialDate,
|
||||
metin: parcalar.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
+3
-2
@@ -10,7 +10,7 @@ import {documentReview,saveDocumentReview,reviewedAnalysisSource} from './docume
|
||||
import { documentMetadataPatch, decodeDocumentUpload } from './document-metadata.mjs';
|
||||
import { deadlineInfo } from '../src/services/tenderCalendar.js';
|
||||
import { exchangeRates } from './exchange-rates.mjs';
|
||||
import { uniqueTenders, announcementFields } from './tender-quality.mjs';
|
||||
import { uniqueTenders, announcementFields, mergeAnnouncementFields } from './tender-quality.mjs';
|
||||
/**
|
||||
* ihaleist API Proxy — ilan.gov.tr canlı ihale verisi
|
||||
*
|
||||
@@ -845,7 +845,8 @@ const server = http.createServer(async (req, res) => {
|
||||
const ikn = url.searchParams.get('ikn');
|
||||
if (!ikn) return json(res, 400, { success: false, error: 'ikn gerekli' });
|
||||
const out = await ilanMetni(ikn);
|
||||
return json(res, 200, { success: true, ...out, ...announcementFields(out.metin) });
|
||||
const parsed = mergeAnnouncementFields(out.metin, out);
|
||||
return json(res, 200, { success: true, ...out, ...parsed, tarih: out.tarih || parsed.tarih || '', saat: out.saat || parsed.saat || '' });
|
||||
} catch (e) {
|
||||
return json(res, 502, sourceUnavailable(e));
|
||||
}
|
||||
|
||||
@@ -48,3 +48,50 @@ export function announcementFields(text) {
|
||||
if (/hizmet alımı\s+4734/i.test(content)) result.alimTuru = 'Kiralama ve Hizmet Alımı';
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* EKAP ihale detayındaki tarih-saat alanını liste ve takvim alanlarına ayırır.
|
||||
* Ayrıştırılamayan/eksik değerler için varsayılan tarih veya saat üretmez.
|
||||
*/
|
||||
export function officialDateFields(value) {
|
||||
const raw = String(value || '').trim();
|
||||
if (!raw) return {};
|
||||
const dotted = raw.match(/(\d{2})\.(\d{2})\.(\d{4})\s*(?:[-–T]|\s)\s*(\d{1,2}):(\d{2})/);
|
||||
if (dotted) return {
|
||||
tarih: `${dotted[1]}.${dotted[2]}.${dotted[3]}`,
|
||||
saat: `${dotted[4].padStart(2, '0')}:${dotted[5]}`,
|
||||
sourceDateTime: raw,
|
||||
};
|
||||
const iso = raw.match(/(\d{4})-(\d{2})-(\d{2})[T\s]+(\d{1,2}):(\d{2})/);
|
||||
if (iso) return {
|
||||
tarih: `${iso[3]}.${iso[2]}.${iso[1]}`,
|
||||
saat: `${iso[4].padStart(2, '0')}:${iso[5]}`,
|
||||
sourceDateTime: raw,
|
||||
};
|
||||
return { sourceDateTime: raw };
|
||||
}
|
||||
|
||||
/**
|
||||
* İlan metninden çıkarılan alanları, resmî EKAP ihale detayındaki alanlarla
|
||||
* birleştirir. Resmî detay tarihi ilan HTML'inden eski/çelişkili bir tarih
|
||||
* gelirse üstün tutulur; fark kullanıcıya açık bir uyarı olarak taşınır.
|
||||
*/
|
||||
export function mergeAnnouncementFields(text, authoritative = {}) {
|
||||
const parsed = announcementFields(text);
|
||||
const sourceTarih = String(authoritative.tarih || '').trim();
|
||||
const sourceSaat = String(authoritative.saat || '').trim();
|
||||
const dateConflict = Boolean(
|
||||
(sourceTarih && parsed.tarih && sourceTarih !== parsed.tarih) ||
|
||||
(sourceSaat && parsed.saat && sourceSaat !== parsed.saat),
|
||||
);
|
||||
const merged = {
|
||||
...parsed,
|
||||
...(sourceTarih ? { tarih: sourceTarih } : {}),
|
||||
...(sourceSaat ? { saat: sourceSaat } : {}),
|
||||
...(authoritative.sourceDateTime ? { sourceDateTime: authoritative.sourceDateTime } : {}),
|
||||
};
|
||||
if (dateConflict) {
|
||||
merged.sourceDateWarning = 'Resmî ihale detay tarihi ile ilan metnindeki tarih farklı; güncel detay tarihi esas alındı.';
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,11 @@ export async function fetchTenderDetail(tenderId, {signal, timeoutMs = 30000} =
|
||||
if (searchResult) {
|
||||
if (data.ikn !== tenderId.slice(5)) throw new Error('Kaynak yanıtındaki İKN aranan ihaleyle uyuşmuyor.');
|
||||
data.aciklama=data.metin;
|
||||
data.sourceWarning=data.ilanSayisi ? null : 'EKAP ilan metni bulunamadı; yalnız temel ihale bilgileri alındı. Resmi dokümanı kontrol edin.';
|
||||
const warnings = [
|
||||
!data.ilanSayisi && 'EKAP ilan metni bulunamadı; yalnız temel ihale bilgileri alındı. Resmi dokümanı kontrol edin.',
|
||||
data.sourceDateWarning,
|
||||
].filter(Boolean);
|
||||
data.sourceWarning=warnings.length ? warnings.join(' ') : null;
|
||||
}
|
||||
if (typeof data.aciklama !== 'string' || !data.aciklama.trim())
|
||||
throw new Error('Kaynak ilan metni alınamadı. Yeniden deneyin veya asıl ilanı kontrol edin.');
|
||||
|
||||
Vendored
+3
-1
@@ -3,6 +3,8 @@ export const healthAcceptanceCases = [
|
||||
{ ikn: '1582991', city: 'Giresun', date: '10.09.2026', time: '10:00', scenario: 'mikroskop' },
|
||||
{ ikn: '1534665', city: 'Kayseri', date: '22.09.2026', time: '11:00', scenario: 'sarf' },
|
||||
{ ikn: '1499061', city: 'Akdeniz', date: '22.09.2026', time: '10:30', scenario: 'cihaz-servis' },
|
||||
{ ikn: '1573235', city: 'Aksaray', date: '09.09.2026', time: '10:00', scenario: 'kismi-teklif' },
|
||||
// EKAP ihaleBilgi.ihaleTarihSaat is authoritative; the older announcement
|
||||
// HTML contains 09.09.2026 and is retained only as a source discrepancy.
|
||||
{ ikn: '1573235', city: 'Aksaray', date: '16.09.2026', time: '10:00', scenario: 'kismi-teklif' },
|
||||
{ ikn: '1523851', city: 'Tepecik', date: '11.09.2026', time: '10:00', scenario: '100-kalem' },
|
||||
];
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const evidence = JSON.parse(fs.readFileSync(new URL('../docs/quality/evidence/qa-official-five-tenders.json', import.meta.url), 'utf8'));
|
||||
|
||||
test('official five-tender evidence is complete and records the Aksaray date discrepancy', () => {
|
||||
assert.equal(evidence.scope, 'Salt okunur EKAP API sorgusu; teklif, ödeme veya başvuru gönderilmedi.');
|
||||
assert.equal(evidence.cases.length, 5);
|
||||
assert.equal(new Set(evidence.cases.map((item) => item.ikn)).size, 5);
|
||||
for (const item of evidence.cases) {
|
||||
assert.equal(item.status, 'İhale İlanı Yayımlanmış, Katılıma Açık');
|
||||
assert.equal(item.announcementPresent, true);
|
||||
assert.match(item.tenderDateTime, /^\d{2}\.\d{2}\.2026 \d{2}:\d{2}$/);
|
||||
assert.ok(item.textChars > 100);
|
||||
assert.ok(item.textSha256);
|
||||
}
|
||||
const aksaray = evidence.cases.find((item) => item.ikn === '2026/1573235');
|
||||
assert.equal(aksaray.tenderDateTime, '16.09.2026 10:00');
|
||||
assert.equal(aksaray.parsedAnnouncement.tarih, '16.09.2026');
|
||||
assert.equal(aksaray.dateConsistency.announcementHtml, '09.09.2026 10:00');
|
||||
assert.match(aksaray.parsedAnnouncement.sourceDateWarning, /güncel detay tarihi esas alındı/);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {uniqueTenders,announcementFields} from '../server/tender-quality.mjs';
|
||||
import {uniqueTenders,announcementFields,officialDateFields,mergeAnnouncementFields} from '../server/tender-quality.mjs';
|
||||
test('duplicate DT records merge without merging another institution or losing source identities',()=>{
|
||||
const t={id:'dt-1',ikn:'26DT1693370',kurum:'Hastane'};
|
||||
const out=uniqueTenders([t,{...t,id:'dt-2',aciklama:'Tam metin'},{...t,id:'dt-3',kurum:'Başka hastane'}]);
|
||||
@@ -49,3 +49,19 @@ test('announcement deadline parser accepts common label variants',()=>{
|
||||
assert.equal(d.saat,['09:30','14:15','08:05'][i]);
|
||||
}
|
||||
});
|
||||
|
||||
test('official EKAP detail date takes precedence over stale announcement date',()=>{
|
||||
const official=officialDateFields('16.09.2026 10:00');
|
||||
assert.deepEqual(official,{tarih:'16.09.2026',saat:'10:00',sourceDateTime:'16.09.2026 10:00'});
|
||||
const merged=mergeAnnouncementFields('2.1. Tarih ve Saati: 09.09.2026 - 10:00',official);
|
||||
assert.equal(merged.tarih,'16.09.2026');
|
||||
assert.equal(merged.saat,'10:00');
|
||||
assert.match(merged.sourceDateWarning,/Resmî ihale detay tarihi/);
|
||||
});
|
||||
|
||||
test('official date merge preserves announcement fields when dates agree',()=>{
|
||||
const merged=mergeAnnouncementFields('2.1. Tarih ve Saati: 10.09.2026 - 10:00',officialDateFields('2026-09-10T10:00:00'));
|
||||
assert.equal(merged.tarih,'10.09.2026');
|
||||
assert.equal(merged.saat,'10:00');
|
||||
assert.equal(merged.sourceDateWarning,undefined);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user