Skip to content

fix(kobo): ignore no-progress KOReader sessions - #25

Open
blinkidy wants to merge 4 commits into
mainfrom
codex/ignore-no-progress-koreader-sessions
Open

fix(kobo): ignore no-progress KOReader sessions#25
blinkidy wants to merge 4 commits into
mainfrom
codex/ignore-no-progress-koreader-sessions

Conversation

@blinkidy

@blinkidy blinkidy commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Follow-up to #24.

  • Type: bug fix
  • UI changed: no
  • How I ran this: targeted server tests and static checks
  • Evidence captured at commit: 62c0ffc9

What changed

Automatic KOReader sessions are now recorded only when forward reading progress increases by at least 0.1 percent. Existing KOReader sessions below that threshold are excluded from per-book reading-log rows and aggregate totals, while manual and audiobook sessions remain unchanged.

This prevents briefly opening a book or looking at a page without reading from creating a 0.0 percent reading session.

Verification

Commands run.

corepack pnpm exec vitest run src/modules/koreader/koreader-stats.util.test.ts src/modules/reading-session/reading-session.repository.test.ts
Test Files  2 passed (2)
Tests  45 passed (45)

corepack pnpm --filter server type-check
server@0.0.1 type-check
tsc --noEmit -p tsconfig.build.json

corepack pnpm exec eslint src/common/constants/reading-session.constants.ts src/modules/koreader/koreader-stats.util.ts src/modules/koreader/koreader-stats.util.test.ts src/modules/reading-session/reading-session.repository.ts src/modules/reading-session/reading-session.repository.test.ts
Process completed with exit code 0.

corepack pnpm exec prettier --check src/common/constants/reading-session.constants.ts src/modules/koreader/koreader-stats.util.ts src/modules/koreader/koreader-stats.util.test.ts src/modules/reading-session/reading-session.repository.ts src/modules/reading-session/reading-session.repository.test.ts
All matched files use Prettier code style!

Manual testing. Not run against a live KOReader device or populated production database. Unit coverage verifies the 0.0, sub-0.1, negative, and exactly-0.1 percent boundaries and verifies that every per-book reading-log aggregate applies the KOReader-only filter.

Anything you could not test: Live KOReader sync behavior and visual confirmation against the user's existing reading log.

Evidence

No UI changes.

Authorship and review

  • AI tools used: Codex
  • Extent (what they wrote, and what you wrote): Codex implemented and verified the requested change from the user's requirements.
  • How you verified their output yourself: Automated tests, typecheck, ESLint, and Prettier were run on the final branch.
  • I can explain any line of this diff on request
Contributor checklist
  • One focused change
  • I read every line of the final diff
  • pnpm verify passes against the final diff. Targeted verification is documented above.
  • Tests added or updated per the testing expectations
  • No user-facing text or localization changes
  • No documentation update is needed
  • No unintended files, secrets, build artifacts, or personal configuration included
  • No new dependencies
  • I followed the contribution and commit guidelines

Require automatic KOReader sessions to advance reading progress by at least 0.1 percent. Exclude older sessions below the threshold from reading-log rows and aggregate totals.
@github-actions github-actions Bot added the server label Aug 9, 2026
@blinkidy
blinkidy marked this pull request as ready for review August 10, 2026 02:28
@blinkidy

Copy link
Copy Markdown
Owner Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62c0ffc988

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +186 to +190
or(
isNull(readingSessions.source),
ne(readingSessions.source, 'koreader'),
gte(readingSessions.progressDelta, MIN_LOGGED_READING_PROGRESS_DELTA),
)!,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply the historical-session filter to global aggregates

For users with KOReader sessions recorded before this change, this predicate hides those sessions only from the per-book log. The same rows remain in reading_sessions and in the already-populated user_reading_daily_stats, while user statistics, dashboards, achievements, and shared insights continue querying those sources without this predicate, such as UserStatisticsRepository.getDailyReadingStats() and getPeakReadingHours(). Consequently, a book log can report zero reading while daily totals, heatmaps, and other analytics still count the ignored session indefinitely unless a later sync happens to reconcile its cluster. Apply the exclusion consistently to raw-session aggregates and rebuild or migrate the persisted daily aggregates.

Useful? React with 👍 / 👎.

Apply the shared KOReader progress threshold to dashboards, achievements, statistics, lifecycle dates, and shared insights. Rebuild affected persisted daily summaries at startup without deleting historical session rows.
@blinkidy

Copy link
Copy Markdown
Owner Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eeb207059d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return or(
isNull(readingSessions.source),
ne(readingSessions.source, 'koreader'),
gte(readingSessions.progressDelta, MIN_LOGGED_READING_PROGRESS_DELTA),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply the filter in every daily-stat recomputation

When a later KOReader upload recomputes a day containing a historical no-progress row outside the upload's affected cluster, KoreaderPluginRepository.recomputeDailyStats() at lines 322-338 selects that row without loggedReadingSessionFilter() and writes it back into user_reading_daily_stats; MigrationImportRepository.recomputeReadingDailyStats() at lines 862-878 does the same during imports. The bootstrap cleanup is therefore not durable, so dashboards can start counting ignored reading time again. Fresh evidence after the earlier review is that these two persisted-stat writers still omit the newly added predicate.

Useful? React with 👍 / 👎.

constructor(private readonly userStatisticsService: UserStatisticsService) {}

async onApplicationBootstrap() {
await this.rebuildNoProgressKoreaderDailyStats();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the historical rebuild out of application bootstrap

On every process restart, this awaited hook starts again at afterId = 0 and scans every retained no-progress KOReader session, then performs sequential day recomputations before the application can become ready. Because the historical rows are deliberately retained and there is no completion marker, large installations repeatedly pay the full backfill cost and can remain unavailable for an extended period after every deploy or crash; run this once through a persisted migration/checkpoint or move it to resumable background work.

AGENTS.md reference: AGENTS.md:L17-L21

Useful? React with 👍 / 👎.

Apply the reading-session threshold to KOReader and migration daily-stat recomputations. Process historical repairs in checkpointed background batches without blocking application startup.
@blinkidy

Copy link
Copy Markdown
Owner Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd45f335dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return or(
isNull(readingSessions.source),
ne(readingSessions.source, 'koreader'),
gte(readingSessions.progressDelta, MIN_LOGGED_READING_PROGRESS_DELTA),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude ignored sessions from reading-attempt totals

For historical KOReader rows already attached to a reading attempt, ReadingAttemptRepository.list() still counts every joined readingSessions row in totalSessions and totalSeconds. The reading-log table now hides these rows, but ReadingAttemptHistory.vue continues displaying totals that include them, so the same book tab reports contradictory session counts. Apply this predicate to the attempt aggregation join as well.

Useful? React with 👍 / 👎.

Comment on lines +49 to +54
} catch (error) {
const errorClass = error instanceof Error ? error.name : 'UnknownError';
const message = error instanceof Error ? error.message : String(error);
this.logger.warn(
`[reading_session.rebuild_no_progress_stats] [fail] durationMs=${Date.now() - startedAt} errorClass=${errorClass} error="${sanitizeLogValue(message)}" - historical stats rebuild failed`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry an interrupted historical rebuild

If any batch rejects, this catch logs the error and permanently ends the only rebuild invocation for the lifetime of the process. The hourly cron only calls recomputeRecent(), so older polluted aggregates remain incorrect until the application happens to restart; this can also follow a transient database error or deadlock while the concurrently started recent recomputation is running. Reschedule the checkpointed rebuild with bounded backoff until it completes.

AGENTS.md reference: AGENTS.md:L21-L21

Useful? React with 👍 / 👎.

Comment on lines +919 to +920
const timeZone = resolveTimeZone((row.settings as { timezone?: unknown } | undefined)?.timezone, 'UTC');
for (const day of getReadingSessionDayKeys(row, timeZone)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear aggregates written under a previous timezone

For a user who changed timezone after these historical sessions were aggregated, deriving affected days solely with the current setting can target a different date from the persisted contaminated row. For example, a session at 00:30 UTC previously stored on the UTC date moves to the preceding date in an American timezone, so the rebuild recomputes that preceding date while leaving the old UTC-date total untouched and then marks the checkpoint complete. Rebuild all daily rows for each affected user and library, or otherwise include day keys produced under the prior aggregation timezone.

Useful? React with 👍 / 👎.

Exclude ignored sessions from reading-attempt totals. Retry historical repairs with capped backoff and clear daily keys that may have been written under a previous timezone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant