Files
genshin-assistant/scripts/prepare-confirmed-review-case.cjs
T
2026-07-09 08:44:50 +02:00

110 lines
4.1 KiB
JavaScript

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=<id>.");
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,
};