const fs = require("node:fs"); const path = require("node:path"); const EVAL_FIELDS = new Set(["name", "slot", "level", "mainStat", "mainValue", "setName", "equipped", "substats"]); function argValue(name, fallback = "") { const prefix = `--${name}=`; const match = process.argv.find((entry) => entry.startsWith(prefix)); return match ? match.slice(prefix.length) : fallback; } function parseExpectedFields() { const inlineJson = argValue("expect-json"); const expectFile = argValue("expect-file"); if (!inlineJson && !expectFile) { throw new Error("Missing expected labels. Pass --expect-json=... or --expect-file=..."); } const raw = (inlineJson || fs.readFileSync(path.resolve(expectFile), "utf8")).replace(/^\uFEFF/, ""); const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("Expected labels must be a JSON object."); } const expect = {}; for (const [field, value] of Object.entries(parsed)) { if (!EVAL_FIELDS.has(field)) throw new Error(`Unknown expected field: ${field}`); if (field === "level") { if (!Number.isInteger(value) || value < 0) throw new Error("Expected level must be a non-negative integer."); expect[field] = value; continue; } if (field === "substats") { if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string" && entry.trim())) { throw new Error("Expected substats must be a non-empty string array."); } expect[field] = value; continue; } if (typeof value !== "string" || !value.trim()) throw new Error(`Expected ${field} must be a non-empty string.`); expect[field] = value; } if (Object.keys(expect).length === 0) throw new Error("Expected labels must include at least one field."); return expect; } function loadCandidate(inputPath, candidateId) { const payload = JSON.parse(fs.readFileSync(path.resolve(inputPath), "utf8")); const candidates = Array.isArray(payload?.candidates) ? payload.candidates : []; const candidate = candidates.find((entry) => entry.id === candidateId); if (!candidate) throw new Error(`Candidate not found: ${candidateId}`); if (!candidate.ocr || typeof candidate.ocr !== "object" || Object.keys(candidate.ocr).length === 0) { throw new Error(`Candidate has no OCR payload: ${candidateId}`); } return candidate; } function stableId(value) { return String(value || "") .replace(/[^0-9A-Za-z]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 80) || "review-case"; } function objectLiteral(value, indent = 2) { return JSON.stringify(value, null, indent).replace(/"([A-Za-z_$][0-9A-Za-z_$]*)":/g, "$1:"); } function snippetFor(candidate, expect) { const id = stableId(argValue("id", `confirmed-${candidate.id}`)); const entry = { id, confirmed: true, ocr: candidate.ocr, expect, meta: { source: "review-sample", resolution: candidate.resolution || undefined, note: `${candidate.reason || "review-sample"} | savedAt=${candidate.savedAt || "unknown"} | sourceCandidate=${candidate.id}`, }, }; return `${objectLiteral(entry, 2)},\n`; } function defaultInputPath() { return path.join(process.cwd(), "outputs", "review-eval-candidates", "review-eval-candidates.json"); } function main() { const inputPath = argValue("input", defaultInputPath()); const candidateId = argValue("candidate"); if (!candidateId) throw new Error("Missing candidate id. Pass --candidate=."); const outputPath = path.resolve(argValue("out", path.join(process.cwd(), "outputs", "review-eval-candidates", `${stableId(candidateId)}.confirmed.ts`))); const candidate = loadCandidate(inputPath, candidateId); const expect = parseExpectedFields(); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); const snippet = snippetFor(candidate, expect); fs.writeFileSync(outputPath, snippet, "utf8"); console.log(JSON.stringify({ ok: true, candidateId, outputPath, labeledFields: Object.keys(expect) }, null, 2)); } if (require.main === module) { main(); } module.exports = { loadCandidate, parseExpectedFields, snippetFor, stableId, };