68 lines
4.0 KiB
JavaScript
68 lines
4.0 KiB
JavaScript
import {qualifyAnalysis} from './analysis-quality.mjs';
|
||
import {analysisArrayFields,validateAnalysisShape} from './analysis-schema.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=analysisArrayFields;
|
||
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 {
|
||
const out=await runPart(part);
|
||
validateAnalysisShape(out?.result);
|
||
results.push({part,out});
|
||
}
|
||
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.`);
|
||
const pathMap=new Map();
|
||
for(const field of arrayFields) {
|
||
merged[field]=[];
|
||
const entries=new Map();
|
||
for(const {part,out} of results) for(const [i,value] of (out.result[field] || []).entries()) {
|
||
const key=JSON.stringify(value);
|
||
let index=field==='itemsAndSpecs'?undefined:entries.get(key);
|
||
if(index===undefined) {
|
||
index=merged[field].length;entries.set(key,index);
|
||
merged[field].push(field==='itemsAndSpecs'?{...value,sourcePart:part.index+1,sourceRange:{start:part.start,end:part.end}}:value);
|
||
}
|
||
pathMap.set(`${part.index}:${field}.${i}`,`${field}.${index}`);
|
||
}
|
||
}
|
||
merged.citations=results.flatMap(({part,out})=>(out.result.citations || []).flatMap(c=>{
|
||
const path=pathMap.get(`${part.index}:${c.path}`);return path?[{path,quote:c.quote}]:[];
|
||
}));
|
||
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}))};
|
||
}
|