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; }