Files
2026-07-09 08:44:50 +02:00

87 lines
3.0 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import type { ScannerLearningLoadResult, ScannerLearningRepositoryPort, ScannerLearningRules, ScannerLearningSaveResult } from "./contracts.js";
export class ScannerLearningRepository implements ScannerLearningRepositoryPort {
private readonly filePath: string;
constructor(userDataPath: string, fileName = "scanner-learning.json") {
this.filePath = path.join(userDataPath, fileName);
}
async load(): Promise<ScannerLearningLoadResult> {
try {
const raw = await fs.readFile(this.filePath, "utf8");
const parsed = JSON.parse(raw) as ScannerLearningRules;
return {
ok: true,
path: this.filePath,
rules: parsed && typeof parsed === "object" ? parsed : emptyRules(),
};
} catch {
return { ok: true, path: this.filePath, rules: emptyRules() };
}
}
async save(rules: ScannerLearningRules): Promise<ScannerLearningSaveResult> {
const current = await this.load();
const payload: ScannerLearningRules = mergeRules(current.rules, rules);
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
await fs.writeFile(this.filePath, JSON.stringify(payload, null, 2), "utf8");
return { ok: true, path: this.filePath, rules: payload, total: countRules(payload) };
}
}
function emptyRules(): ScannerLearningRules {
return {
textReplacements: {},
fieldAliases: {},
constrainedFixes: {},
cropAdjustments: {},
uiProfileAdjustments: {},
};
}
function mergeRules(current: ScannerLearningRules, incoming: ScannerLearningRules): ScannerLearningRules {
return {
textReplacements: {
...(current.textReplacements ?? {}),
...(incoming.textReplacements ?? {}),
},
fieldAliases: mergeNested(current.fieldAliases, incoming.fieldAliases),
constrainedFixes: {
...(current.constrainedFixes ?? {}),
...(incoming.constrainedFixes ?? {}),
},
cropAdjustments: {
...(current.cropAdjustments ?? {}),
...(incoming.cropAdjustments ?? {}),
},
uiProfileAdjustments: {
...(current.uiProfileAdjustments ?? {}),
...(incoming.uiProfileAdjustments ?? {}),
},
};
}
function mergeNested(
current: Record<string, Record<string, string>> | undefined,
incoming: Record<string, Record<string, string>> | undefined,
) {
const merged: Record<string, Record<string, string>> = {};
for (const [field, values] of Object.entries(current ?? {})) merged[field] = { ...(values ?? {}) };
for (const [field, values] of Object.entries(incoming ?? {})) merged[field] = { ...(merged[field] ?? {}), ...(values ?? {}) };
return merged;
}
function countRules(rules: ScannerLearningRules) {
const fieldAliases = Object.values(rules.fieldAliases ?? {}).reduce((sum, aliases) => sum + Object.keys(aliases ?? {}).length, 0);
return (
Object.keys(rules.textReplacements ?? {}).length
+ fieldAliases
+ Object.keys(rules.constrainedFixes ?? {}).length
+ Object.keys(rules.cropAdjustments ?? {}).length
+ Object.keys(rules.uiProfileAdjustments ?? {}).length
);
}