| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { createWriteStream } from "node:fs"; |
| import { mkdir, writeFile } from "node:fs/promises"; |
| import path from "node:path"; |
| import archiver from "archiver"; |
|
|
| export interface ResultRow { |
| taskId: string; |
| ligandId: string; |
| score: number; |
| } |
|
|
| export interface TaskSummary { |
| taskId: string; |
| numLigands: number; |
| topScore: number; |
| evaluatorMetric?: number | null; |
| notes?: string; |
| } |
|
|
| export interface PackageResultInput { |
| outDir: string; |
| |
| zipName: string; |
| blueprintId: string; |
| networkName: string; |
| variantId: string; |
| rows: ResultRow[]; |
| taskSummaries: TaskSummary[]; |
| |
| aggregateMetric?: number | null; |
| |
| extraLogLines?: string[]; |
| } |
|
|
| function csvEscape(v: string | number): string { |
| const s = String(v); |
| if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`; |
| return s; |
| } |
| |
| export async function packageResult( |
| input: PackageResultInput, |
| ): Promise<{ zipPath: string; csvBytes: number; logBytes: number }> { |
| await mkdir(input.outDir, { recursive: true }); |
| const stage = path.join(input.outDir, `_stage_${input.blueprintId}`); |
| await mkdir(stage, { recursive: true }); |
| |
| // result.csv |
| const csvLines: string[] = ["task_id,ligand_id,score"]; |
| for (const r of input.rows) { |
| csvLines.push( |
| [csvEscape(r.taskId), csvEscape(r.ligandId), r.score.toFixed(6)].join(","), |
| ); |
| } |
| const csvBody = csvLines.join("\n") + "\n"; |
| await writeFile(path.join(stage, "result.csv"), csvBody, "utf8"); |
| |
| // result.log |
| const logLines: string[] = []; |
| logLines.push(`# DoAtlas benchmark result`); |
| logLines.push(`blueprint_id: ${input.blueprintId}`); |
| logLines.push(`network: ${input.networkName}`); |
| logLines.push(`variant_id: ${input.variantId}`); |
| logLines.push(`generated_at: ${new Date().toISOString()}`); |
| logLines.push(`total_tasks: ${input.taskSummaries.length}`); |
| logLines.push(`total_rows: ${input.rows.length}`); |
| if (input.aggregateMetric != null) { |
| logLines.push(`aggregate_metric: ${input.aggregateMetric.toFixed(6)}`); |
| } |
| logLines.push(``); |
| logLines.push(`## per-task summary`); |
| for (const s of input.taskSummaries) { |
| logLines.push( |
| `- ${s.taskId} ligands=${s.numLigands} top=${s.topScore.toFixed(6)}` + |
| (s.evaluatorMetric != null |
| ? ` metric=${s.evaluatorMetric.toFixed(6)}` |
| : "") + |
| (s.notes ? ` notes=${s.notes}` : ""), |
| ); |
| } |
| if (input.extraLogLines && input.extraLogLines.length > 0) { |
| logLines.push(``); |
| logLines.push(`## notes`); |
| for (const ln of input.extraLogLines) logLines.push(ln); |
| } |
| const logBody = logLines.join("\n") + "\n"; |
| await writeFile(path.join(stage, "result.log"), logBody, "utf8"); |
| |
| // manifest.json |
| const manifest = { |
| blueprintId: input.blueprintId, |
| networkName: input.networkName, |
| variantId: input.variantId, |
| generatedAt: new Date().toISOString(), |
| totals: { |
| tasks: input.taskSummaries.length, |
| rows: input.rows.length, |
| }, |
| aggregateMetric: input.aggregateMetric ?? null, |
| perTask: input.taskSummaries, |
| }; |
| await writeFile( |
| path.join(stage, "manifest.json"), |
| JSON.stringify(manifest, null, 2), |
| "utf8", |
| ); |
| |
| // zip |
| const zipPath = path.join(input.outDir, input.zipName); |
| await new Promise<void>((resolve, reject) => { |
| const out = createWriteStream(zipPath); |
| const arch = archiver("zip", { zlib: { level: 9 } }); |
| out.on("close", () => resolve()); |
| out.on("error", reject); |
| arch.on("error", reject); |
| arch.pipe(out); |
| arch.file(path.join(stage, "result.csv"), { name: "result.csv" }); |
| arch.file(path.join(stage, "result.log"), { name: "result.log" }); |
| arch.file(path.join(stage, "manifest.json"), { name: "manifest.json" }); |
| void arch.finalize(); |
| }); |
| |
| return { |
| zipPath, |
| csvBytes: Buffer.byteLength(csvBody, "utf8"), |
| logBytes: Buffer.byteLength(logBody, "utf8"), |
| }; |
| } |
| |