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 safeProvenanceValue(value, fallback = "unknown") { const text = typeof value === "string" ? value.trim() : ""; if (!text || text.length > 200 || /[\r\n]/.test(text)) return fallback; if (path.isAbsolute(text) || /[A-Za-z]:[\\/]/.test(text) || text.startsWith("\\\\")) return fallback; return text; } function visualEvidenceNote(candidate) { const status = candidate?.visualEvidence?.status === "available" ? "available" : "unavailable"; const parts = [`visualEvidence=${status}`]; const captureId = safeProvenanceValue(candidate?.captureId, ""); const capturedAt = safeProvenanceValue(candidate?.capturedAt, ""); if (captureId) parts.push(`captureId=${captureId}`); if (capturedAt) parts.push(`capturedAt=${capturedAt}`); return parts.join(" | "); } 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 reason = safeProvenanceValue(candidate.reason, "review-sample"); const savedAt = safeProvenanceValue(candidate.savedAt, "unknown"); const resolution = safeProvenanceValue(candidate.resolution, ""); const sourceCandidate = stableId(candidate.id); const entry = { id, confirmed: true, ocr: candidate.ocr, expect, meta: { source: "review-sample", resolution: resolution || undefined, note: `${reason} | savedAt=${savedAt} | sourceCandidate=${sourceCandidate} | ${visualEvidenceNote(candidate)}`, }, }; 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); if (candidate?.visualEvidence?.status !== "available") { throw new Error("Candidate has no retrievable visual evidence. Re-export after retaining a local PNG before preparing a confirmed corpus case."); } 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, visualEvidenceNote, };