(dialect: string, dialectResults: DialectReultsByModel, evaluatedSweeps: string[], modelFilter?: string, mergeModels?: boolean)
| 1154 | } |
| 1155 | |
| 1156 | function saveDialectResults(dialect: string, dialectResults: DialectReultsByModel, evaluatedSweeps: string[], modelFilter?: string, mergeModels?: boolean): void { |
| 1157 | const filePath = getResultsFilePath(dialect); |
| 1158 | |
| 1159 | // When --merge-models, shallow-merge new model entries into existing file |
| 1160 | if (mergeModels) { |
| 1161 | const existing = loadExistingDialectResults(filePath); |
| 1162 | if (existing) { |
| 1163 | const newModelIds = Object.keys(dialectResults); |
| 1164 | const updatedCount = newModelIds.filter(id => id in existing).length; |
| 1165 | const addedCount = newModelIds.length - updatedCount; |
| 1166 | // Merge preserving scan order: new models first (in scan order), then remaining old models |
| 1167 | const merged: DialectReultsByModel = {}; |
| 1168 | for (const id of newModelIds) |
| 1169 | merged[id] = dialectResults[id]; |
| 1170 | for (const id of Object.keys(existing)) { |
| 1171 | if (!(id in merged)) |
| 1172 | merged[id] = existing[id]; |
| 1173 | } |
| 1174 | dialectResults = merged; |
| 1175 | console.log(`${COLORS.dim}Merging into existing file (${Object.keys(existing).length} existing, ${updatedCount} updated, ${addedCount} added -> ${Object.keys(dialectResults).length} total)${COLORS.reset}`); |
| 1176 | } |
| 1177 | } |
| 1178 | |
| 1179 | // Preserve scan order for model keys; sort sweep keys alphabetically within each model |
| 1180 | const sorted: DialectReultsByModel = {}; |
| 1181 | for (const model of Object.keys(dialectResults)) { |
| 1182 | sorted[model] = {}; |
| 1183 | for (const sweep of Object.keys(dialectResults[model]).sort()) { |
| 1184 | sorted[model][sweep] = dialectResults[model][sweep]; |
| 1185 | } |
| 1186 | } |
| 1187 | // Custom JSON formatting: keep value arrays on single lines |
| 1188 | const placeholder = '___ARRAY___'; |
| 1189 | const arrays: string[] = []; |
| 1190 | const withPlaceholders = JSON.stringify(sorted, (_, value) => { |
| 1191 | // Collapse arrays of primitives (typeof null === 'object', so check explicitly) |
| 1192 | if (Array.isArray(value) && (value.length === 0 || value[0] === null || typeof value[0] !== 'object')) { |
| 1193 | arrays.push(JSON.stringify(value)); |
| 1194 | return placeholder + (arrays.length - 1); |
| 1195 | } |
| 1196 | return value; |
| 1197 | }, 2); |
| 1198 | const jsonBody = withPlaceholders.replace(/"___ARRAY___(\d+)"/g, (_, idx) => arrays[parseInt(idx)]); |
| 1199 | // Insert header comments after opening brace |
| 1200 | const comment1 = '"_comment": "API-validated parameter values. null=undefined/missing. Values are tested and working. Note: temperature is continuous, not discrete.",'; |
| 1201 | const comment2 = `"_evaluated": "Evaluated: ${evaluatedSweeps.sort().join(', ')}. If missing, the parameter is not supported by that model.",`; |
| 1202 | const comment3 = modelFilter ? `"_modelFilter": "${modelFilter}",` : ''; |
| 1203 | const comments = [comment1, comment2, comment3].filter(Boolean).join('\n '); |
| 1204 | const json = jsonBody.replace(/^\{\n {2}/, '{\n ' + comments + '\n '); |
| 1205 | fs.writeFileSync(filePath, json + '\n', 'utf-8'); |
| 1206 | console.log(`${COLORS.dim}Results saved to: ${filePath}${COLORS.reset}`); |
| 1207 | } |
| 1208 | |
| 1209 | function vendorResultToDialectResults(vendorResult: VendorSweepResult): DialectReultsByModel { |
| 1210 | const dialectResults: DialectReultsByModel = {}; |
no test coverage detected