Cancel disconnected offer analysis and stop remaining provider calls

This commit is contained in:
2026-09-06 10:22:29 +03:00
parent 673d6c1bc8
commit d534ea20b9
10 changed files with 110 additions and 22 deletions
+7 -7
View File
@@ -57,7 +57,7 @@ export async function ihaleMetni(ikn, verilen, ilan) {
return { text: '', kaynak: null };
}
export async function belgeyeSor({ ikn, soru, text, tender, chain }) {
export async function belgeyeSor({ ikn, soru, text, tender, chain, signal }) {
const q = String(soru ?? '').trim().slice(0, 500);
if (q.length < 3) throw new Error('Soru en az 3 karakter olmalı.');
const { text: metin, kaynak } = await ihaleMetni(ikn, text, tender?.aciklama);
@@ -67,7 +67,7 @@ export async function belgeyeSor({ ikn, soru, text, tender, chain }) {
if (!kaynaklar.length) return { cevap: 'Terim aramasında bu soruyla eşleşen bir bölüm bulunamadı; bu, şartın belgede bulunmadığını kanıtlamaz; belgedeki terimlerle (teminat, teslim, ceza, adet) yeniden deneyin.', kaynaklar: [], kaynak };
const baglam = kaynaklar.map((k) => `[${k.n}]\n${k.icerik}`).join('\n\n');
const sistem = `Sen Türkiye kamu ihalesi uzmanısın. Sana bir ihale dokümanının numaralı parçaları verilecek. YALNIZCA bu parçalardaki bilgiyle yanıt ver; parçalarda yoksa "Seçilen kaynak parçalarında bu bilgi bulunamadı" de ve kontrol edilen parçalara atıf ver, tahmin etme. Her iddianın sonuna dayandığı parça numarasını köşeli parantezle yaz: [1], [3]. Sayı, tarih, tutar, süre değerlerini aynen aktar. Kısa ve net Türkçe. Yalnızca JSON döndür: {"cevap":"..."}`;
const out = await analyzeSpec({ text: `PARÇALAR:\n\n${baglam}\n\nSORU: ${q}\n\nYalnızca {"cevap":"..."} JSON'u döndür.`, tender, chain, systemOverride: sistem });
const out = await analyzeSpec({ text: `PARÇALAR:\n\n${baglam}\n\nSORU: ${q}\n\nYalnızca {"cevap":"..."} JSON'u döndür.`, tender, chain, signal, systemOverride: sistem });
return {...qualifyQuestionAnswer(out?.result, kaynaklar), kaynak,
sourceReview:{sourceHash:createHash('sha256').update(metin).digest('hex'), totalChunks:parcalar.length, selectedChunks:kaynaklar.length},
kapsam:'Yanıt yalnız soruyla eşleşen kaynak parçalarına dayanır. Kaynak numarası kontrolü, yanıtın anlam bakımından doğruluğunu tek başına kanıtlamaz; diğer bölümlerde ek veya çelişen şartlar bulunabilir.'};
@@ -92,7 +92,7 @@ export function gereklilikMetni(tender, gereklilikler, metin, allItems = false)
return [...new Set(g)].slice(0, allItems ? undefined : 60);
}
export async function teklifDenetle({ ikn, taslak, gereklilikler, tender, chain, text }) {
export async function teklifDenetle({ ikn, taslak, gereklilikler, tender, chain, text, signal }) {
const t = String(taslak ?? '').trim();
if (t.length < 200) throw new Error('Taslak en az 200 karakter olmalı.');
const { text: metin } = await ihaleMetni(ikn, text, tender?.aciklama);
@@ -101,9 +101,9 @@ export async function teklifDenetle({ ikn, taslak, gereklilikler, tender, chain,
const sistem = `Sen kamu ihalelerinde teklif dosyası denetçisisin. Gereklilik listesi ve teklif TASLAĞI verilecek. Her gereklilik için taslağı kontrol et. "karsilaniyor" yalnızca taslakta açık karşılığı varsa (alıntıyı 'kanit'e aynen kopyala); değinilmiyorsa "eksik"; kısmen/belirsizse "belirsiz". Sayı üretme. Yalnızca JSON: {"ozet":"2-3 cümle","maddeler":[{"gereklilik":"","durum":"karsilaniyor"|"eksik"|"belirsiz","kanit":"veya null","oneri":"veya null"}]} — maddeleri verilen sırada döndür; gereklilik alanına verilen gereklilik metnini değiştirmeden aynen kopyala.`;
return auditOfferInParts(liste,t,async (group,draftPart) => {
const out = await analyzeSpec({text:`GEREKLİLİKLER:\n${group.map((g,i)=>`${i+1}. ${g}`).join('\n')}\n\nTEKLİF TASLAĞI BÖLÜMÜ:\n${draftPart}\n\nYalnızca JSON döndür.`,
tender:tender ? {...tender,items:[]} : undefined,chain,systemOverride:sistem,mode:'derin',_singlePart:true});
tender:tender ? {...tender,items:[]} : undefined,chain,systemOverride:sistem,mode:'derin',signal,_singlePart:true});
return out?.result;
});
},{signal});
}
const BOLUMLER = {
@@ -113,7 +113,7 @@ const BOLUMLER = {
tam: ['Teklif Mektubu (Kapak)', 'Firma Tanıtımı ve Yeterlilik', 'Teknik Teklif — Kapsam ve Yaklaşım', 'Ürün / Hizmet Uygunluğu (Şartname Maddeleriyle)', 'Teslim Planı ve Kalite Güvencesi', 'Ticari Teklif — Fiyat Özeti', 'Ödeme ve Teslim Koşulları', 'Teklif Geçerlilik Süresi ve Teminat', 'Ekler'],
};
export async function teklifTaslak({ ikn, tur, tender, firma, belgeler, fiyat, gereklilikler, chain, text }) {
export async function teklifTaslak({ ikn, tur, tender, firma, belgeler, fiyat, gereklilikler, chain, text, signal }) {
const istenen = BOLUMLER[tur] || BOLUMLER.tam;
const { text: metin } = await ihaleMetni(ikn, text, tender?.aciklama);
const liste = gereklilikMetni(tender, gereklilikler, metin);
@@ -125,6 +125,6 @@ export async function teklifTaslak({ ikn, tur, tender, firma, belgeler, fiyat, g
fiyat ? `FİYAT VERİSİ: ${typeof fiyat === 'string' ? fiyat : JSON.stringify(fiyat).slice(0, 1500)}` : null,
].filter(Boolean).join('\n\n');
const sistem = `Sen Türkiye kamu ihalelerinde (4734) deneyimli bir teklif yazarısın. Verilen bilgilerle resmî, kısa ve somut bir teklif dosyası taslağı yaz. Verilmeyen bilgiyi UYDURMA: firma adı, yetkili, tarih, fiyat, süre boşluklarına [DOLDURUN: ...] yaz. Fiyat yalnızca FİYAT VERİSİ varsa yazılır. Ekler bölümünde istenen belgeleri madde madde ver. Üslup resmî; abartı yok. Yalnızca JSON: {"bolumler":[{"baslik":"","icerik":""}]} — istenen bölümleri aynı sırada ver; bölüm başlıklarını değiştirmeden aynen kopyala.`;
const out = await analyzeSpec({ text: `BAĞLAM:\n${baglam}\n\nİSTENEN BÖLÜMLER:\n${istenen.map((x, i) => `${i + 1}. ${x}`).join('\n')}\n\nYalnızca JSON döndür.`, tender, chain, systemOverride: sistem, mode: 'derin' });
const out = await analyzeSpec({ text: `BAĞLAM:\n${baglam}\n\nİSTENEN BÖLÜMLER:\n${istenen.map((x, i) => `${i + 1}. ${x}`).join('\n')}\n\nYalnızca JSON döndür.`, tender, chain, signal, systemOverride: sistem, mode: 'derin' });
return qualifyOfferDraft(out?.result, istenen);
}