prefer official EKAP detail deadline

This commit is contained in:
2026-09-06 19:44:29 +03:00
parent 74e784e2f7
commit 9fd1db3c8a
9 changed files with 393 additions and 5 deletions
+3
View File
@@ -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
View File
@@ -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));
}
+47
View File
@@ -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;
}