fix: expose incomplete AI analysis and track source versions
This commit is contained in:
+6
-3
@@ -1,3 +1,4 @@
|
||||
import { qualifyAnalysis } from './analysis-quality.mjs';
|
||||
/**
|
||||
* Gerçek AI şartname analizi — OpenRouter öncelikli, çoklu sağlayıcı fallback.
|
||||
* Bağımlılık yok (Node 18+ built-in fetch).
|
||||
@@ -163,7 +164,7 @@ YALNIZCA geçerli JSON döndür, başka hiçbir metin yazma. Şema:
|
||||
}
|
||||
|
||||
Kurallar:
|
||||
- criticalDetails en fazla 12, documentChecklist en fazla 18, deadlines en fazla 10, roadmap en fazla 10, itemsAndSpecs en fazla 20 madde olsun.
|
||||
- criticalDetails en fazla 12, documentChecklist en fazla 18, deadlines en fazla 10, roadmap en fazla 10 madde olsun. itemsAndSpecs içinde kaynakta bulunan kalemleri eksiltme veya birleştirme; sığmayan listeyi tamamlanmış gibi sunma.
|
||||
- itemsAndSpecs'i teknik şartnamedeki kalem/miktar tablolarından çıkar; keySpecs'e yalnızca teklifi bağlayan zorunlu evsafları yaz (ör. "en az 17x17 inç dedektör", "IP54"), tanıtım cümlelerini yazma.
|
||||
- estimatedUnitPrice / estimatedTotal yalnızca metinde geçen bir yaklaşık maliyet, tahmini bedel veya birim fiyat varsa doldurulur; TAHMİN ÜRETME, yoksa null bırak. Sayıları yalnızca rakam olarak yaz (para birimi simgesi, nokta/virgül ayracı koyma).
|
||||
- roadmap adımlarını mantıklı sırayla ve step alanını 1'den başlayarak numaralandır.
|
||||
@@ -282,7 +283,7 @@ function extractJson(raw) {
|
||||
const govde = s.slice(start, end + 1);
|
||||
try { return JSON.parse(govde); } catch (e) {
|
||||
const onarilmis = kesikJsonOnar(s.slice(start));
|
||||
if (onarilmis) return onarilmis;
|
||||
if (onarilmis) return {...onarilmis,_outputRepaired:true};
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -352,7 +353,9 @@ export async function analyzeSpec({ text, fileName, tender, chain, systemOverrid
|
||||
} else {
|
||||
raw = await callOpenAiStyle(p, route.key, route.model, userPrompt, ac.signal, sistem, maxTokens);
|
||||
}
|
||||
const result = extractJson(raw);
|
||||
const parsed = extractJson(raw);
|
||||
const result = qualifyAnalysis(parsed,{text,fileName,limit:derin ? 90000 : 60000,
|
||||
expectedItems:tender?.items?.length || 0,repaired:parsed._outputRepaired === true});
|
||||
attempts.push({ id, ok: true, yol: route.yol });
|
||||
return {
|
||||
result,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {createHash} from 'node:crypto';
|
||||
export function qualifyAnalysis(result,{text='',limit=60000,fileName='',expectedItems=0,repaired=false}={}) {
|
||||
if(!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Analiz nesne biçiminde değil.');
|
||||
const clean={...result};delete clean._outputRepaired;
|
||||
const sections={};
|
||||
for(const key of ['itemsAndSpecs','documentChecklist','qualificationCriteria','risksAndPenalties','roadmap']) {
|
||||
if(clean[key] != null && !Array.isArray(clean[key])) throw new Error(`${key} liste biçiminde değil.`);
|
||||
sections[key]=clean[key]?.length ? 'extracted' : 'not_extracted';
|
||||
}
|
||||
const source=String(text),issues=[];
|
||||
if(source.length>limit)issues.push('Kaynak metnin tamamı analiz edilmedi.');
|
||||
if(repaired)issues.push('Analiz yanıtı kesildi; yalnız kurtarılabilen alanlar gösteriliyor.');
|
||||
if(expectedItems>0 && (clean.itemsAndSpecs?.length || 0)<expectedItems)issues.push(`Kaynakta ${expectedItems} kalem var; analiz listesi daha kısa.`);
|
||||
const version=createHash('sha256').update(source).digest('hex');
|
||||
return {...clean,documentVersion:version,sourceReview:{status:issues.length?'partial':'review_required',
|
||||
fileName,sourceCharacters:source.length,analyzedCharacters:Math.min(source.length,limit),
|
||||
outputRepaired:repaired,sections,issues}};
|
||||
}
|
||||
Reference in New Issue
Block a user