Carry OCR and missing-source warnings into analysis readiness
This commit is contained in:
@@ -59,12 +59,13 @@ export const AiSpecAnalyzerModal = () => {
|
||||
try {
|
||||
setProgress('Belge metni çıkarılıyor…');
|
||||
const { extractText } = await import('../services/pdfText');
|
||||
const { text, usedPages, pageCount } = await extractText(file);
|
||||
const reading = await extractText(file);
|
||||
const { text, usedPages, pageCount } = reading;
|
||||
if (!text || text.length < 40) {
|
||||
throw new Error('Belgeden metin çıkarılamadı. Taranmış (görüntü) PDF olabilir — metin tabanlı bir dosya deneyin.');
|
||||
}
|
||||
setProgress(`${usedPages}/${pageCount} sayfa okundu, ${derin ? 'derin analiz üretiliyor (1-2 dk sürebilir)…' : 'model analiz ediyor…'}`);
|
||||
const r = await analyzeSpecPdfWithGemini({ text, fileName: file.name, tender: activeTenderDetail, mode: derin ? 'derin' : undefined });
|
||||
const r = await analyzeSpecPdfWithGemini({ text, fileName: file.name, extraction:{ocr:reading.ocr,pageCount,usedPages,complete:reading.complete,fileName:file.name}, tender: activeTenderDetail, mode: derin ? 'derin' : undefined });
|
||||
if (typeof r?.bidAnalysis?.recommendedBid === 'number') setCustomBid(r.bidAnalysis.recommendedBid);
|
||||
confetti({ particleCount: 80, spread: 70 });
|
||||
} catch (e) {
|
||||
|
||||
@@ -245,7 +245,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {
|
||||
{kaynakGecerlilik.status === 'conflict' && <p role="alert" className="text-red-700">Kaynaklarda farklı süreler var. Güncel resmi dokümanı belirleyip analizi yenileyin; çelişki çözülmeden çıktı alınamaz.</p>}
|
||||
{[...new Set(kaynakGecerlilik.evidence.map(e=>e.quote))].map(quote=><blockquote key={quote}>{quote}</blockquote>)}
|
||||
</div>}
|
||||
{analizEksik && <p role="alert" className="text-xs text-red-700">Analiz kısmi: kaynak veya kalem listesi eksik. Tam dokümanı ve resmi cetveli kontrol ederek analizi tamamlayın.</p>}
|
||||
{analizEksik && <p role="alert" className="text-xs text-red-700">Analiz kısmi: kaynak, OCR veya kalem bilgisi kontrol gerektiriyor. {(effectiveResults.sourceReview.issues || []).join(' ')} Tam ve doğrulanabilir kaynakla analizi tamamlayın.</p>}
|
||||
{/* Sizden beklenen bilgiler */}
|
||||
{eksikler.length > 0 && (
|
||||
<div className="bg-amber-50 border border-amber-300 rounded-xl p-3.5 flex gap-2.5">
|
||||
|
||||
@@ -120,10 +120,11 @@ export const TenderDocuments = ({ tender }) => {
|
||||
setBusy('belge'); setError(null);
|
||||
try {
|
||||
if (!okunabilirMi(b.ad)) throw new Error('Bu dosya türünden metin çıkarılamıyor (PDF, DOCX, XLSX, ZIP ve TXT destekleniyor).');
|
||||
const metin = await belgeMetni(b);
|
||||
const reading = await inspectStoredDocument(b);
|
||||
const metin = reading.text;
|
||||
if (!metin || metin.length < 100) throw new Error('Belgeden analiz için yeterli metin çıkarılamadı (taranmış PDF olabilir).');
|
||||
setIsAiSpecAnalyzerOpen(true);
|
||||
await analyzeSpecPdfWithGemini({ text: metin, fileName: b.ad, tender, mode: mod });
|
||||
await analyzeSpecPdfWithGemini({ text: metin, fileName: b.ad, extraction:{ocr:reading.ocr,pageCount:reading.pageCount,usedPages:reading.usedPages,complete:reading.complete,fileName:b.ad}, tender, mode: mod });
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
} finally { setBusy(null); }
|
||||
@@ -188,6 +189,7 @@ export const TenderDocuments = ({ tender }) => {
|
||||
const tumEvrakiTopla = async () => {
|
||||
const parcalar = [];
|
||||
const atlanan = [];
|
||||
const extraction = [];
|
||||
|
||||
if (ekapIhalesi) {
|
||||
setIlerleme('EKAP ilan metni alınıyor…');
|
||||
@@ -198,28 +200,32 @@ export const TenderDocuments = ({ tender }) => {
|
||||
}
|
||||
} catch {
|
||||
atlanan.push('EKAP ilan metni (alınamadı)');
|
||||
extraction.push({fileName:'EKAP ilan metni',complete:false});
|
||||
}
|
||||
}
|
||||
|
||||
for (const b of belgeler) {
|
||||
if (!okunabilirMi(b.ad)) { atlanan.push(`${b.ad} (metin çıkarılamıyor)`); continue; }
|
||||
if (!okunabilirMi(b.ad)) { atlanan.push(`${b.ad} (metin çıkarılamıyor)`); extraction.push({fileName:b.ad,complete:false}); continue; }
|
||||
setIlerleme(`${b.ad} okunuyor…`);
|
||||
try {
|
||||
const t = await belgeMetni(b);
|
||||
const reading = await inspectStoredDocument(b);
|
||||
const t = reading.text;
|
||||
extraction.push({ocr:reading.ocr,pageCount:reading.pageCount,usedPages:reading.usedPages,complete:reading.complete,fileName:b.ad});
|
||||
if (t && t.length >= 200) parcalar.push(`===== BELGE: ${b.ad} =====\n${t}`);
|
||||
else atlanan.push(`${b.ad} (yeterli metin yok — taranmış olabilir)`);
|
||||
else { atlanan.push(`${b.ad} (yeterli metin yok — taranmış olabilir)`); extraction.push({fileName:b.ad,complete:false}); }
|
||||
} catch (e) {
|
||||
atlanan.push(`${b.ad} (${e.message})`);
|
||||
extraction.push({fileName:b.ad,complete:false});
|
||||
}
|
||||
}
|
||||
|
||||
return { metin: parcalar.join('\n\n'), parcaSayisi: parcalar.length, atlanan };
|
||||
return { metin: parcalar.join('\n\n'), parcaSayisi: parcalar.length, atlanan, extraction };
|
||||
};
|
||||
|
||||
const derinAnalizEt = async () => {
|
||||
setBusy('derin'); setError(null); setBilgi(null);
|
||||
try {
|
||||
const { metin, parcaSayisi, atlanan } = await tumEvrakiTopla();
|
||||
const { metin, parcaSayisi, atlanan, extraction } = await tumEvrakiTopla();
|
||||
if (!metin || metin.length < 200) {
|
||||
throw new Error('Derin analiz için yeterli metin toplanamadı. EKAP dokümanını indirip PDF olarak yükleyin.');
|
||||
}
|
||||
@@ -228,6 +234,7 @@ export const TenderDocuments = ({ tender }) => {
|
||||
setIsAiSpecAnalyzerOpen(true);
|
||||
await analyzeSpecPdfWithGemini({
|
||||
text: metin,
|
||||
extraction,
|
||||
fileName: `${ikn || 'ihale'} — ${parcaSayisi} evrak birlikte`,
|
||||
tender,
|
||||
mode: 'derin',
|
||||
|
||||
@@ -428,13 +428,13 @@ export const AppProvider = ({ children }) => {
|
||||
// Gerçek AI analizi — backend /api/ai/analyze üzerinden, admin panelde
|
||||
// seçilen model zinciri (fallback) ile.
|
||||
// mode='derin' → ince detaylar, belge kontrol listesi ve yol haritası da üretilir
|
||||
const analyzeSpecPdfWithGemini = async ({ text, fileName, tender, mode } = {}) => {
|
||||
const analyzeSpecPdfWithGemini = async ({ text, fileName, tender, mode, extraction } = {}) => {
|
||||
const session = getToken();
|
||||
setAiAnalyzing(true);
|
||||
setAiAnalysisResults(null);
|
||||
try {
|
||||
const { analyzeSpecText } = await import('../services/ai');
|
||||
const data = await analyzeSpecText({ text, fileName, tender, chain: aiModelConfig.chain, mode });
|
||||
const data = await analyzeSpecText({ text, fileName, tender, chain: aiModelConfig.chain, mode, extraction });
|
||||
const result = {
|
||||
...data.result,
|
||||
tenderKey: tender?.ikn || tender?.id || null,
|
||||
|
||||
+2
-2
@@ -13,11 +13,11 @@ export async function getAiConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function analyzeSpecText({ text, fileName, tender, chain, mode }) {
|
||||
export async function analyzeSpecText({ text, fileName, tender, chain, mode, extraction }) {
|
||||
const res = await fetch('/api/ai/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` },
|
||||
body: JSON.stringify({ text, fileName, tender, chain, mode }),
|
||||
body: JSON.stringify({ text, fileName, tender, chain, mode, extraction }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!data.success) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/** Only extraction provenance crosses the AI boundary, never a client approval flag. */
|
||||
export function extractionReview(input) {
|
||||
const sources=(Array.isArray(input) ? input : input ? [input] : []).map(source=>({
|
||||
fileName:typeof source.fileName === 'string' ? source.fileName.slice(0,200) : '',
|
||||
ocr:source.ocr === true,
|
||||
pageCount:Number.isInteger(source.pageCount) && source.pageCount>0 ? source.pageCount : null,
|
||||
usedPages:Number.isInteger(source.usedPages) && source.usedPages>=0 ? source.usedPages : null,
|
||||
complete:source.complete !== false,
|
||||
}));
|
||||
const issues=[];
|
||||
for(const source of sources) {
|
||||
const name=source.fileName || 'Belge';
|
||||
if(source.ocr) issues.push(`${name}: OCR metni resmi belgeyle karşılaştırılmalı; karakter ve miktar hataları olabilir.`);
|
||||
if(!source.complete || (source.pageCount != null && source.usedPages != null && source.usedPages<source.pageCount))
|
||||
issues.push(`${name}: belgenin bütün sayfaları okunmadı.`);
|
||||
}
|
||||
return {sources,issues};
|
||||
}
|
||||
Reference in New Issue
Block a user