Files
ihaleist/server/analysis-parts.mjs
T

63 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.`);
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}))};
}