fix: validate nested analysis fields before merging and preparation
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
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=[];
|
||||
@@ -11,13 +12,17 @@ export function splitAnalysisText(text,max=24000) {
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
const arrayFields=['qualificationCriteria','criticalRequirements','risksAndPenalties','disqualificationRisks','criticalDetails','itemsAndSpecs','documentChecklist','deadlines','costDrivers','roadmap','questionsForAdministration'];
|
||||
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 {results.push({part,out:await runPart(part)});}
|
||||
try {
|
||||
const out=await runPart(part);
|
||||
validateAnalysisShape(out?.result);
|
||||
results.push({part,out});
|
||||
}
|
||||
catch(error){failures.push({part,error:String(error.message || error)});}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { verifyAnalysisCitations } from './analysis-citations.mjs';
|
||||
import { validateAnalysisShape } from './analysis-schema.mjs';
|
||||
import {createHash} from 'node:crypto';
|
||||
export function qualifyAnalysis(result,{text='',limit=60000,fileName='',expectedItems=0,repaired=false}={}) {
|
||||
if(!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Analiz nesne biçiminde değil.');
|
||||
@@ -9,7 +10,7 @@ export function qualifyAnalysis(result,{text='',limit=60000,fileName='',expected
|
||||
if(clean[key] != null && !Array.isArray(clean[key])) throw new Error(`${key} liste biçiminde değil.`);
|
||||
sections[key]=clean[key]?.length ? 'extracted' : 'not_extracted';
|
||||
}
|
||||
const source=String(text),issues=[];
|
||||
const source=String(text),issues=validateAnalysisShape(clean);
|
||||
if(source.length>limit)issues.push('Kaynak metnin tamamı analiz edilmedi.');
|
||||
if(repaired)issues.push('Analiz yanıtı kesildi; yalnız kurtarılabilen alanlar gösteriliyor.');
|
||||
if(expectedItems>0 && (clean.itemsAndSpecs?.length || 0)<expectedItems)issues.push(`Kaynakta ${expectedItems} kalem var; analiz listesi daha kısa.`);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
export const analysisArrayFields = ['qualificationCriteria','criticalRequirements','risksAndPenalties','disqualificationRisks','criticalDetails','itemsAndSpecs','documentChecklist','deadlines','costDrivers','roadmap','questionsForAdministration'];
|
||||
const stringLists = new Set(['qualificationCriteria','criticalRequirements','disqualificationRisks','costDrivers','questionsForAdministration']);
|
||||
const shapes = {
|
||||
itemsAndSpecs: ['name','quantity','note'], documentChecklist: ['document','note'],
|
||||
risksAndPenalties: ['level','clause','text'], criticalDetails: ['level','title','where','detail','whyItMatters'],
|
||||
deadlines: ['label','value','note'], roadmap: ['phase','action','owner','timing','output'],
|
||||
};
|
||||
const object = v => v && typeof v === 'object' && !Array.isArray(v);
|
||||
const fail = path => { throw new Error(`Analiz alanının biçimi geçersiz: ${path}`); };
|
||||
const text = (v,path) => { if (v != null && typeof v !== 'string') fail(path); };
|
||||
const number = (v,path) => { if (v != null && (typeof v !== 'number' || !Number.isFinite(v) || v < 0)) fail(path); };
|
||||
|
||||
export function validateAnalysisShape(result) {
|
||||
if (!object(result)) fail('result');
|
||||
const issues = [];
|
||||
for (const field of ['summary','scopeOfWork']) text(result[field],field);
|
||||
for (const field of analysisArrayFields) {
|
||||
if (result[field] == null) continue;
|
||||
if (!Array.isArray(result[field])) fail(field);
|
||||
result[field].forEach((row,i) => {
|
||||
const path = `${field}.${i}`;
|
||||
if (stringLists.has(field)) { if (typeof row !== 'string' || !row.trim()) fail(path); return; }
|
||||
if (!object(row)) fail(path);
|
||||
for (const key of shapes[field]) text(row[key],`${path}.${key}`);
|
||||
if (field === 'itemsAndSpecs') {
|
||||
if (!row.name?.trim() || !row.quantity?.trim()) issues.push(`${path}: kalem adı veya miktar/birim eksik.`);
|
||||
if (row.keySpecs != null && (!Array.isArray(row.keySpecs) || row.keySpecs.some(v => typeof v !== 'string'))) fail(`${path}.keySpecs`);
|
||||
for (const key of ['estimatedUnitPrice','estimatedTotal']) number(row[key],`${path}.${key}`);
|
||||
}
|
||||
if (field === 'documentChecklist') {
|
||||
if (!row.document?.trim() || row.mandatory == null) issues.push(`${path}: belge adı veya zorunluluk bilgisi eksik.`);
|
||||
if (row.mandatory != null && typeof row.mandatory !== 'boolean') fail(`${path}.mandatory`);
|
||||
}
|
||||
if (field === 'roadmap' && row.step != null && (!Number.isInteger(row.step) || row.step < 1)) fail(`${path}.step`);
|
||||
});
|
||||
}
|
||||
if (result.bidAnalysis != null) {
|
||||
if (!object(result.bidAnalysis)) fail('bidAnalysis');
|
||||
for (const key of ['estimatedCost','recommendedBid','minBidLimit','maxBidLimit','winProbability']) number(result.bidAnalysis[key],`bidAnalysis.${key}`);
|
||||
if (result.bidAnalysis.winProbability > 100) fail('bidAnalysis.winProbability');
|
||||
for (const key of ['competitionLevel','notes']) text(result.bidAnalysis[key],`bidAnalysis.${key}`);
|
||||
}
|
||||
if (result.citations != null) {
|
||||
if (!Array.isArray(result.citations)) fail('citations');
|
||||
result.citations.forEach((c,i) => {
|
||||
if (!object(c) || typeof c.path !== 'string' || typeof c.quote !== 'string') fail(`citations.${i}`);
|
||||
});
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
Reference in New Issue
Block a user