51 lines
3.0 KiB
JavaScript
51 lines
3.0 KiB
JavaScript
const norm = v => String(v || '').trim().replace(/\s+/g,' ').toLocaleLowerCase('tr-TR');
|
||
export function tenderIdentity(t) {
|
||
const ikn = String(t.ikn || '').replace(/\s/g,'');
|
||
// Awards from different lots must never be collapsed into one contract.
|
||
if (norm(t.durum) === 'sonuçlandı') return `award:${ikn}:${t.kisim ?? t.id}:${norm(t.kazananFirma)}`;
|
||
if (/^\d{2}DT\d+$/i.test(ikn)) return `dt:${ikn.toUpperCase()}:${norm(t.kurum)}`;
|
||
// Corrections/cancellations remain separate events until their source linkage is verified.
|
||
if (/düzeltme|zeyilname|iptal/i.test(t.baslik || '')) return `event:${t.id}`;
|
||
return /^\d{4}\/\d+$/.test(ikn) ? `tender:${ikn}` : `source:${t.id}`;
|
||
}
|
||
export function uniqueTenders(records) {
|
||
const found = new Map();
|
||
for (const t of records) {
|
||
// Kaynaklardan gelen bozuk/null satırlar tüm havuz birleştirmesini düşürmemeli.
|
||
if (!t || typeof t !== 'object' || Array.isArray(t) || typeof t.id !== 'string' || !t.id.trim()) continue;
|
||
const key = tenderIdentity(t), old = found.get(key);
|
||
if (!old) { found.set(key,{...t,sourceIds:[t.id]}); continue; }
|
||
const sourceIds = [...new Set([...old.sourceIds,t.id])];
|
||
const merged = {...old};
|
||
for (const [field,value] of Object.entries(t)) {
|
||
if (merged[field] == null || merged[field] === '' || (Array.isArray(merged[field]) && !merged[field].length)) merged[field] = value;
|
||
}
|
||
if ((t.aciklama || '').length > (old.aciklama || '').length) merged.aciklama = t.aciklama;
|
||
found.set(key,{...merged,id:old.id,sourceIds});
|
||
}
|
||
return [...found.values()];
|
||
}
|
||
export function announcementFields(text) {
|
||
const content = String(text || '');
|
||
const result = {aciklama: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, ' ');
|
||
// EKAP/ilan kaynakları aynı son teklif anını farklı başlıklarla verir.
|
||
// Yalnız tarih + saat birlikte bulunduğunda alan üret; teslim süresi gibi
|
||
// yalnızca tarih içeren maddeleri son teklif tarihi sanma.
|
||
const deadlinePatterns = [
|
||
/Tarih\s*ve\s*Saati\s*:\s*(\d{2}\.\d{2}\.\d{4})\s*[-–]?\s*(\d{2}:\d{2})/i,
|
||
/(?:İhale\s+|Son\s+Teklif\s+)?Tarih(?:i)?\s*(?:\/\s*Saat(?:i)?|ve\s+Saat(?:i)?|\s+Saat(?:i)?)\s*:\s*(\d{2}\.\d{2}\.\d{4})\s*[-–]?\s*(\d{2}:\d{2})/i,
|
||
/Son\s+Teklif\s+Tarihi\s*:\s*(\d{2}\.\d{2}\.\d{4})\s*[-–]?\s*(\d{2}:\d{2})/i,
|
||
];
|
||
const deadline = deadlinePatterns.map(pattern => pattern.exec(fields)).find(Boolean);
|
||
if (deadline) { result.tarih = deadline[1]; result.saat = deadline[2]; }
|
||
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(fields);
|
||
if (place) result.teslimYeri = place[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ı';
|
||
return result;
|
||
}
|