Skip to content

Isolate per-file extraction failures in canvas and edX content syncs - #3753

Merged
mbertrand merged 13 commits into
mainfrom
mb/cf_per_file_error
Aug 11, 2026
Merged

Isolate per-file extraction failures in canvas and edX content syncs#3753
mbertrand merged 13 commits into
mainfrom
mb/cf_per_file_error

Conversation

@mbertrand

@mbertrand mbertrand commented Aug 11, 2026

Copy link
Copy Markdown
Member

What are the relevant tickets?

Closes #3750

Description (What does it do?)

One PDF that crashes the OCR converter currently aborts an entire canvas course sync before any contentfiles load — course 33842-7.06 is stuck at 0 contentfiles on production because a single answer-key PDF crashes opendataloader (RasterFormatException). The same per-file loop (process_olx_path) serves edX archive ingestion and canvas tutor problem files, so any extractor exception kills those syncs the same way.

This branch makes a failing file cost exactly that one file:

  • An OCR crash falls back to tika (logged at warning — the fallback succeeded, so no Sentry error).
  • Any remaining per-file exception — including pypdf-invalid/encrypted PDFs, which previously took a silent drop path — is logged to Sentry with the source path and run id, recorded, and skipped.
  • Skipped files are excluded from every "not seen this run" cleanup, so their existing records survive published with their previous content: canvas's hard-delete set, load_content_files' staleness/unpublish pass (canvas and edX), and the tutor problem orphan delete. New files that fail are simply absent this run — no empty placeholder rows.
  • Checksum gating: a partial failure still stamps run.checksum, so a deterministic crash isn't re-downloaded and re-OCR'd weekly (failed files retry when the archive next changes, or via --overwrite). A total failure does not stamp, so the course retries next sync instead of looking legitimately empty. Same on edX: an all-failed archive is no longer mistaken for an empty one.

One deliberate boundary: this covers extractors that blow up, not extractors that produce nothing. Files where Tika returns no response or empty content keep today's drop/unpublish semantics for all sources, same as main — they aren't recorded as failures.

No new database queries — the exclusions chain onto existing lazy querysets, and with no failures the generated SQL is identical to main.

How can this be tested?

Use the course from the issue — its current archive contains the PDF that crashes the converter, so it exercises the real failure path. With env pointed at the canvas course bucket (CANVAS_COURSE_BUCKET_NAME etc.) and OCR enabled:

  1. On main, run ./manage.py backpopulate_canvas_courses --canvas-ids 33842 and watch it die on 7.06_Fall2025_Exam1_answers.pdf (FileNotFoundError for the converter output JSON) with the course left at 0 contentfiles.
  2. On this branch, run the same command. Expect the sync to complete: an OCR extraction failed ... falling back to tika warning for the answer-key PDF(s), the course going from 0 to ~158 contentfiles (the fallback text may be sparse), and run.checksum set.
  3. Run it a second time without --overwrite — it should log Checksums match ... skipping load instead of re-downloading and re-extracting.
  4. Retention check: pick a course with existing contentfiles (e.g. 39194 from the same incident) and sync it — no existing contentfiles should be deleted or flipped to published=False because of extraction failures.

🤖 Generated with Claude Code

mbertrand and others added 12 commits August 10, 2026 16:24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d archives empty

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
load_content_files no-ops for non-course resources, so the randomized
factory type made the test order-dependent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
content_files_loaded_actions fires search indexing gated by randomized
resource fields; when the draw enables it, the eager task hits the
nonexistent test opensearch and the resulting Retry is swallowed by
process_course_archive's bare except, leaving the checksum unstamped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@mbertrand
mbertrand marked this pull request as ready for review August 11, 2026 11:33
Copilot AI balanced review requested due to automatic review settings August 11, 2026 11:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Isolates Canvas and edX per-file extraction failures so healthy content continues syncing while failed records are retained.

Changes:

  • Falls back from OCR failures to Tika.
  • Tracks failed files and excludes them from cleanup.
  • Adds checksum gating for partial and total failures.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
learning_resources/etl/utils.py Adds extraction fallback and failure tracking.
learning_resources/etl/utils_test.py Tests extraction failure handling.
learning_resources/etl/loaders.py Preserves failed content and problem files.
learning_resources/etl/loaders_test.py Tests failed-file retention.
learning_resources/etl/edx_shared.py Integrates failure tracking into edX syncs.
learning_resources/etl/edx_shared_test.py Tests partial and total edX failures.
learning_resources/etl/canvas.py Integrates failure retention and checksum gating.
learning_resources/etl/canvas_test.py Tests Canvas retention and retry behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread learning_resources/etl/utils.py
Comment thread learning_resources/etl/canvas.py Outdated
Comment thread learning_resources/etl/utils.py
…d PDF

Review fixes: a course whose only tutor problem file fails no longer
blocks the checksum when content loaded (weekly re-extraction churn),
and pdf_is_valid's internal pypdf log drops to warning so the outer
process_olx_path report is the one Sentry event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mbertrand mbertrand added the Needs Review An open Pull Request that is ready for review label Aug 11, 2026
@shanbady shanbady self-assigned this Aug 11, 2026

@shanbady shanbady left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

@shanbady shanbady assigned mbertrand and unassigned shanbady Aug 11, 2026
@shanbady shanbady added Waiting on author and removed Needs Review An open Pull Request that is ready for review labels Aug 11, 2026
@mbertrand
mbertrand merged commit 67866ca into main Aug 11, 2026
13 checks passed
@mbertrand
mbertrand deleted the mb/cf_per_file_error branch August 11, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Canvas ingest: one bad PDF crashes the OCR converter and kills the whole course sync

3 participants