feat: analyze complete long documents in bounded source parts
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import {qualifyAnalysis} from './analysis-quality.mjs';
|
||||
export function splitAnalysisText(text,max=24000) {
|
||||
if(!Number.isInteger(max) || max<100)throw new Error('Geçersiz parça boyutu.');
|
||||
const source=String(text || ''),parts=[];
|
||||
for(let start=0;start<source.length;) {
|
||||
let end=Math.min(start+max,source.length);
|
||||
if(end<source.length){const newline=source.lastIndexOf('\n',end-1);if(newline>start+max/2)end=newline+1;}
|
||||
// Do not split a UTF-16 surrogate pair.
|
||||
if(end<source.length && /[\uD800-\uDBFF]/.test(source[end-1]))end--;
|
||||
parts.push({index:parts.length,start,end,text:source.slice(start,end)});start=end;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
const arrayFields=['qualificationCriteria','criticalRequirements','risksAndPenalties','disqualificationRisks','criticalDetails','itemsAndSpecs','documentChecklist','deadlines','costDrivers','roadmap','questionsForAdministration'];
|
||||
export async function analyzeInParts(input,runPart) {
|
||||
const parts=splitAnalysisText(input.text),results=[],failures=[];
|
||||
// Two requests at a time bounds provider pressure while processing every part.
|
||||
for(let i=0;i<parts.length;i+=2) {
|
||||
await Promise.all(parts.slice(i,i+2).map(async part=>{
|
||||
try {results.push({part,out:await runPart(part)});}
|
||||
catch(error){failures.push({part,error:String(error.message || error)});}
|
||||
}));
|
||||
}
|
||||
results.sort((a,b)=>a.part.index-b.part.index);
|
||||
if(!results.length)throw new Error('Dokümanın hiçbir parçası analiz edilemedi.');
|
||||
const merged={},issues=failures.map(f=>`${f.part.index+1}. parça analiz edilemedi.`);
|
||||
for(const field of arrayFields) {
|
||||
if(field==='itemsAndSpecs') {
|
||||
// Identical rows may be separate official items; retain them with origin.
|
||||
merged[field]=results.flatMap(({part,out})=>(out.result[field] || []).map(row=>({...row,sourcePart:part.index+1,sourceRange:{start:part.start,end:part.end}})));
|
||||
continue;
|
||||
}
|
||||
const entries=new Map();
|
||||
for(const {out} of results) for(const value of out.result[field] || [])entries.set(JSON.stringify(value),value);
|
||||
merged[field]=[...entries.values()];
|
||||
}
|
||||
for(const field of ['summary','scopeOfWork'])merged[field]=results.map(({part,out})=>out.result[field] ? `Bölüm ${part.index+1}: ${out.result[field]}` : '').filter(Boolean).join('\n\n');
|
||||
// Different part-level prices/strategies cannot be treated as a global bid.
|
||||
merged.bidAnalysis=null;
|
||||
const quantities=new Map();
|
||||
for(const row of merged.itemsAndSpecs) {
|
||||
const key=JSON.stringify([row.itemNumber ?? row.no ?? null,row.kisim ?? null,row.name]);
|
||||
if(quantities.has(key) && quantities.get(key)!==row.quantity)issues.push(`${row.name}: bölümler arasında farklı miktar/birim bulundu.`);
|
||||
quantities.set(key,row.quantity);
|
||||
}
|
||||
for(const {part,out} of results) for(const issue of out.result.sourceReview?.issues || [])issues.push(`Bölüm ${part.index+1}: ${issue}`);
|
||||
const result=qualifyAnalysis(merged,{text:input.text,limit:String(input.text).length,fileName:input.fileName,expectedItems:input.tender?.items?.length || 0});
|
||||
const ownIssues=[...result.sourceReview.issues,...issues];
|
||||
result.sourceReview={...result.sourceReview,status:ownIssues.length?'partial':'review_required',issues:ownIssues,
|
||||
analyzedCharacters:results.reduce((sum,{part})=>sum+part.end-part.start,0),
|
||||
parts:parts.map(part=>({index:part.index+1,start:part.start,end:part.end,status:failures.some(f=>f.part.index===part.index)?'failed':'processed'}))};
|
||||
return {result,mode:input.mode==='derin'?'derin':'standart',model:{id:'document-parts',label:'Doküman analizi',provider:'',model:'document-parts'},
|
||||
attempts:results.flatMap(({part,out})=>(out.attempts || []).map(a=>({...a,part:part.index+1}))),
|
||||
partFailures:failures.map(({part})=>({part:part.index+1}))};
|
||||
}
|
||||
Reference in New Issue
Block a user