From 6527b8b0f59e54557c42d10b0a92ca0f948b3f3d Mon Sep 17 00:00:00 2001 From: korjik Date: Tue, 21 Apr 2026 20:39:32 -0700 Subject: [PATCH] update --- server/providers/OpenAI.js | 38 ++++++++++++++++++++++++++-- test/server/providers/OpenAI.test.js | 35 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/server/providers/OpenAI.js b/server/providers/OpenAI.js index 570c3e91..bd75bdf2 100644 --- a/server/providers/OpenAI.js +++ b/server/providers/OpenAI.js @@ -208,6 +208,41 @@ class OpenAI { }) } + normalizeDetectionResultBooks(resultBooks, books) { + if (!Array.isArray(resultBooks)) { + throw new Error('OpenAI returned an invalid books payload') + } + + const expectedIds = new Set(books.map((book) => book.id)) + const resultBooksById = new Map() + + resultBooks.forEach((book) => { + if (!expectedIds.has(book?.id)) { + Logger.warn(`[OpenAI] Ignoring unknown book id "${book?.id}" in series-detection response`) + return + } + if (resultBooksById.has(book.id)) { + Logger.warn(`[OpenAI] Ignoring duplicate book id "${book.id}" in series-detection response`) + return + } + resultBooksById.set(book.id, book) + }) + + return books.map((book) => { + if (resultBooksById.has(book.id)) { + return resultBooksById.get(book.id) + } + + Logger.warn(`[OpenAI] Missing series-detection result for book "${book.id}" - skipping assignment`) + return { + id: book.id, + seriesName: null, + sequence: null, + reason: 'Skipped because OpenAI omitted this book from the response' + } + }) + } + validateSeriesOrderPayload(payload, books) { const resultBooks = payload?.books this.validateBookIds(resultBooks, books) @@ -233,8 +268,7 @@ class OpenAI { } validateSeriesDetectionPayload(payload, books) { - const resultBooks = payload?.books - this.validateBookIds(resultBooks, books) + const resultBooks = this.normalizeDetectionResultBooks(payload?.books, books) const seriesSequences = new Map() return resultBooks.map((book) => { diff --git a/test/server/providers/OpenAI.test.js b/test/server/providers/OpenAI.test.js index 30ead530..bba8164c 100644 --- a/test/server/providers/OpenAI.test.js +++ b/test/server/providers/OpenAI.test.js @@ -96,5 +96,40 @@ describe('OpenAI', () => { expect(result[0].sequence).to.equal(null) expect(result[0].reason).to.contain('skipped due to missing series name') }) + + it('ignores unknown ids and backfills missing ids', () => { + const result = openAI.validateSeriesDetectionPayload( + { + books: [ + { id: 'z', seriesName: 'Wrong Series', sequence: '9' }, + { id: 'a', seriesName: 'Series Name', sequence: '1' } + ] + }, + [{ id: 'a' }, { id: 'b' }] + ) + + expect(result).to.have.length(2) + expect(result[0].id).to.equal('a') + expect(result[0].seriesName).to.equal('Series Name') + expect(result[1].id).to.equal('b') + expect(result[1].seriesName).to.equal(null) + expect(result[1].reason).to.contain('omitted this book') + }) + + it('ignores duplicate ids in detection payload', () => { + const result = openAI.validateSeriesDetectionPayload( + { + books: [ + { id: 'a', seriesName: 'Series Name', sequence: '1' }, + { id: 'a', seriesName: 'Other Series', sequence: '2' } + ] + }, + [{ id: 'a' }] + ) + + expect(result).to.have.length(1) + expect(result[0].seriesName).to.equal('Series Name') + expect(result[0].sequence).to.equal('1') + }) }) })