Require real draft evidence for offer audit findings
This commit is contained in:
+3
-10
@@ -1,3 +1,4 @@
|
||||
import { qualifyOfferAudit } from './offer-audit.mjs';
|
||||
import { documentContent } from './document-content.mjs';
|
||||
// Teklif asistanı — İhaleAB'den taşınan üç AI özelliği, ihaleist'in mevcut
|
||||
// analyzeSpec zinciri (yedekli sağlayıcılar) üzerinde:
|
||||
@@ -101,17 +102,9 @@ export async function teklifDenetle({ ikn, taslak, gereklilikler, tender, chain,
|
||||
const { text: metin } = await ihaleMetni(ikn, text, tender?.aciklama).catch(() => ({ text: '' }));
|
||||
const liste = gereklilikMetni(tender, gereklilikler, metin);
|
||||
if (!liste.length) throw new Error('Gereklilik listesi yok — önce derin analiz çalıştırın veya dokümanı yükleyin.');
|
||||
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.`;
|
||||
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.`;
|
||||
const out = await analyzeSpec({ text: `GEREKLİLİKLER:\n${liste.map((g, i) => `${i + 1}. ${g}`).join('\n')}\n\nTEKLİF TASLAĞI:\n${t}\n\nYalnızca JSON döndür.`, tender, chain, systemOverride: sistem, mode: 'derin' });
|
||||
const ham = Array.isArray(out?.result?.maddeler) ? out.result.maddeler : [];
|
||||
const DUR = new Set(['karsilaniyor', 'eksik', 'belirsiz']);
|
||||
const maddeler = liste.map((g, i) => {
|
||||
const m = ham[i] ?? {};
|
||||
return { gereklilik: g, durum: DUR.has(m.durum) ? m.durum : 'belirsiz', kanit: m.kanit ? String(m.kanit).slice(0, 400) : null, oneri: m.oneri ? String(m.oneri).slice(0, 400) : null };
|
||||
});
|
||||
const ok = maddeler.filter((m) => m.durum === 'karsilaniyor').length;
|
||||
const yarim = maddeler.filter((m) => m.durum === 'belirsiz').length;
|
||||
return { ozet: String(out?.result?.ozet ?? '').slice(0, 800), maddeler, uyum_yuzde: Math.round(((ok + yarim * 0.5) / maddeler.length) * 100), eksikler: maddeler.filter((m) => m.durum === 'eksik').map((m) => m.gereklilik) };
|
||||
return qualifyOfferAudit(out?.result, liste, t);
|
||||
}
|
||||
|
||||
const BOLUMLER = {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const normalize = text => String(text ?? '').normalize('NFC').replace(/\s+/g, ' ').trim();
|
||||
const optionalText = value => typeof value === 'string' && value.trim() ? value.trim() : null;
|
||||
|
||||
/** A model cannot certify a row without identifying it and quoting the actual draft. */
|
||||
export function qualifyOfferAudit(result, requirements, draft) {
|
||||
if (!result || !Array.isArray(result.maddeler) || !result.maddeler.length)
|
||||
throw new Error('Model denetim maddelerini döndürmedi. Yeniden deneyin.');
|
||||
const source = normalize(draft);
|
||||
const maddeler = requirements.map(gereklilik => {
|
||||
const matches = result.maddeler.filter(row => row && typeof row.gereklilik === 'string' && normalize(row.gereklilik) === normalize(gereklilik));
|
||||
const row = matches.length === 1 ? matches[0] : null;
|
||||
let durum = ['karsilaniyor','eksik','belirsiz'].includes(row?.durum) ? row.durum : 'belirsiz';
|
||||
let kanit = optionalText(row?.kanit);
|
||||
let oneri = optionalText(row?.oneri);
|
||||
if (!row) oneri = 'Model yanıtı bu gereklilikle tekil olarak eşleştirilemedi. Kaynak ve taslağı karşılaştırın.';
|
||||
if (durum === 'karsilaniyor' && (!kanit || !source.includes(normalize(kanit)))) {
|
||||
durum = 'belirsiz';
|
||||
oneri = 'Karşılandığı iddiası taslaktaki gerçek bir alıntıyla doğrulanamadı. Elle kontrol edin.';
|
||||
}
|
||||
// Never display invented text as a quote from the user's draft.
|
||||
if (kanit && !source.includes(normalize(kanit))) kanit = null;
|
||||
return {gereklilik, durum, kanit, oneri};
|
||||
});
|
||||
const verified = maddeler.filter(row => row.durum === 'karsilaniyor').length;
|
||||
const uncertain = maddeler.filter(row => row.durum === 'belirsiz').length;
|
||||
return {
|
||||
ozet: `${maddeler.length} gereklilikten ${verified} tanesi taslak alıntısıyla destekleniyor; ${uncertain} tanesi elle kontrol gerektiriyor.`, maddeler,
|
||||
uyum_yuzde: maddeler.length ? Math.round(verified / maddeler.length * 100) : 0,
|
||||
eksikler: maddeler.filter(row => row.durum === 'eksik').map(row => row.gereklilik),
|
||||
belirsizSayisi: uncertain,
|
||||
kapsam: 'Yalnız bu listede gösterilen gereklilikler kontrol edildi. Oran resmi başvuru uygunluğu veya tüm dokümanın eksiksiz denetlendiği anlamına gelmez. Alıntının taslakta bulunması şartın doğru karşılandığını tek başına kanıtlamaz.',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user