fix(plugins): unique csv row keys, per-file default mediaType, scale-aware zero rating
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
a540c7d34d
commit
4c08c16e48
+17
-10
@@ -1,9 +1,9 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { parseCsv } from "@sttracker/plugin-sdk/csv";
|
||||
import { definePlugin } from "@sttracker/plugin-sdk/types";
|
||||
import type { EngagementFields, EntryFields, SourceRecord, WorkHint } from "@sttracker/plugin-sdk/types";
|
||||
import type { EngagementFields, MediaType, EntryFields, SourceRecord, WorkHint } from "@sttracker/plugin-sdk/types";
|
||||
import {
|
||||
cleanId, defaultScale, normalizeRating, parseDate, parseMapping, parseMediaType, parseStatus, parseTags, rowsToFieldRows,
|
||||
cleanId, defaultScale, guessMapping, normalizeRating, parseDate, parseMapping, parseMediaType, parseStatus, parseTags, rowsToFieldRows,
|
||||
type FieldRow,
|
||||
} from "./src/mapping.ts";
|
||||
|
||||
@@ -29,17 +29,19 @@ function jsonRows(text: string): FieldRow[] {
|
||||
});
|
||||
}
|
||||
|
||||
function* toRecords(rows: FieldRow[], rawRows: unknown[]): Generator<SourceRecord> {
|
||||
function* toRecords(rows: FieldRow[], rawRows: unknown[], defaultMedia: MediaType): Generator<SourceRecord> {
|
||||
const scale = defaultScale(rows);
|
||||
const goodreadsLike = (r: FieldRow) => r.isbn13 !== undefined;
|
||||
const seen = new Map<string, number>();
|
||||
for (const [i, r] of rows.entries()) {
|
||||
const title = str(r.title);
|
||||
if (!title) continue;
|
||||
const mediaType = parseMediaType(str(r.mediaType), goodreadsLike(r) || r.author !== undefined ? "book" : "other");
|
||||
const mediaType = parseMediaType(str(r.mediaType), defaultMedia);
|
||||
const finished = parseDate(str(r.finishedAt));
|
||||
const started = parseDate(str(r.startedAt));
|
||||
const hash = createHash("sha1").update([title, mediaType, finished?.value ?? ""].join("\u0000")).digest("hex");
|
||||
const key = `row:${hash}`;
|
||||
const n = (seen.get(hash) ?? 0) + 1;
|
||||
seen.set(hash, n);
|
||||
const key = n === 1 ? `row:${hash}` : `row:${hash}#${n}`;
|
||||
|
||||
const externalIds: Record<string, string> = {};
|
||||
for (const f of ["isbn13", "imdb", "tmdb_movie", "igdb"] as const) {
|
||||
@@ -93,13 +95,18 @@ export default definePlugin({
|
||||
const text = new TextDecoder("utf-8").decode(file.bytes);
|
||||
const lower = file.name.toLowerCase();
|
||||
if (lower.endsWith(".json")) {
|
||||
const rows = jsonRows(text.replace(/^/, ""));
|
||||
yield* toRecords(rows, JSON.parse(text.replace(/^/, "")) as unknown[]);
|
||||
const clean = text.replace(/^\uFEFF/, "");
|
||||
const rows = jsonRows(clean);
|
||||
const raw = JSON.parse(clean) as unknown[];
|
||||
const bookish = raw.some((o) => typeof o === "object" && o !== null && ("author" in o || "isbn13" in o));
|
||||
yield* toRecords(rows, raw, bookish ? "book" : "other");
|
||||
} else if (lower.endsWith(".csv")) {
|
||||
const table = parseCsv(text);
|
||||
const rows = rowsToFieldRows(table, parseMapping(ctx.config.mapping));
|
||||
const header = table[0] ?? [];
|
||||
yield* toRecords(rows, table.slice(1).map((cells) => Object.fromEntries(header.map((h, i) => [h, cells[i] ?? ""]))));
|
||||
const mapping = parseMapping(ctx.config.mapping) ?? guessMapping(header);
|
||||
const rows = rowsToFieldRows(table, mapping);
|
||||
const bookish = header.some((h) => mapping[h] === "author" || mapping[h] === "isbn13");
|
||||
yield* toRecords(rows, table.slice(1).map((cells) => Object.fromEntries(header.map((h, i) => [h, cells[i] ?? ""]))), bookish ? "book" : "other");
|
||||
} else {
|
||||
throw new Error(`csv plugin: unsupported file "${file.name}" (expected .csv or .json)`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user