fix: expose incomplete AI analysis and track source versions

This commit is contained in:
2026-09-06 08:57:32 +03:00
parent ca92bebb45
commit 90d85060f2
7 changed files with 60 additions and 7 deletions
+6 -3
View File
@@ -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,
+18
View File
@@ -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}};
}