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 (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) { 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}; const deadline = /Tarih\s*ve\s*Saati\s*:\s*(\d{2}\.\d{2}\.\d{4})\s*[-–]?\s*(\d{2}:\d{2})/i.exec(content); 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 duration = /3\.4\.?\s*[^:]{0,100}:\s*([\s\S]*?)(?=3\.5\.?|4\s*[-–])/i.exec(content); 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; }