feat: verify analysis citations against source text

This commit is contained in:
2026-09-06 09:00:16 +03:00
parent 0ae828163a
commit a9036cf909
7 changed files with 70 additions and 11 deletions
+14 -7
View File
@@ -24,16 +24,23 @@ export async function analyzeInParts(input,runPart) {
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) {
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;
}
merged[field]=[];
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 {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;