Uh oh!
There was an error while loading. Please reload this page.
feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298
Conversation
…mini fallback (#144) calendar_service already extracted via syllabus_extraction_agent as the primary path; the raw-Gemini `parse_syllabus`/`call_gemini_json` fallback was the only remaining seam. Remove it and degrade gracefully instead: - Delete parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH. On agent guardrail-trip or unexpected failure, extract_assignments_from_file now returns _degraded_result (empty assignments + a user-facing warning, no second LLM call) — resilience without a raw Gemini call. - Response contract preserved: {assignments, warnings, raw_text} on every path (success / degrade / empty-text). - notes stay encrypted at the write boundary (insert_new_assignments, encrypt_if_present) per #126 — added a regression test. Tests: rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade; repointed the live fixtures off parse_syllabus onto the agent; added a notes-encryption test. Full suite green (2 pre-existing storage-env failures unrelated). ruff clean. Spec: specs/144-calendar-agent.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe legacy Gemini-based ChangesAgent-only extraction with graceful degrade
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with |
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs | frontend-staging | 562c000 | Commit Preview URL Branch Preview URL | Jul 02 2026, 05:43 AM |
The direct-run harness (python3 tests/test_ocr_pipeline.py, documented in the module docstring) had lost its sys.exit(1) and traceback on failure, so a failed run exited 0 and silently reported success. Restore both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@specs/144-calendar-agent.md`:
- Around line 47-73: The acceptance criteria and verification commands reference
test locations that don’t match the current backend layout, so update the spec
to use the actual backend test paths. Replace mentions of
tests/test_ocr_pipeline.py, tests/test_calendar_routes.py, and tests/ with the
corresponding backend/tests/... locations, and keep the rest of the requirements
tied to the same test symbols and behaviors so contributors run the right suite.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8e40e07c-35fd-45c9-b82b-e33e99b6afcc
📒 Files selected for processing (3)
backend/services/calendar_service.pybackend/tests/test_ocr_pipeline.pyspecs/144-calendar-agent.md
| - Update `tests/test_ocr_pipeline.py`: | ||
| - Remove `parse_syllabus`-dependent legacy paths: drop `test_gemini_parse` and repoint the | ||
| `parsed_assignments` fixture + the `__main__` block off `parse_syllabus` (use the agent path or | ||
| remove), since the function no longer exists. | ||
| - Rewrite the two `test_falls_back_to_legacy_*` tests to assert the **graceful degrade**: on agent | ||
| `UsageLimitExceeded` and on a bare `Exception`, the result has `assignments == []`, a non-empty | ||
| `warnings`, `raw_text == <text>`, and `parse_syllabus` is never referenced. | ||
| - `test_returns_agent_assignments` and `test_agent_and_legacy_paths_share_required_keys`: drop the | ||
| `patch.object(calendar_service, "parse_syllabus", ...)`; keep the agent-path and required-keys | ||
| assertions (the required-keys check now covers success + degrade paths). | ||
| - `test_legacy_path_still_works_when_text_empty`: drop the `parse_syllabus` patch; keep the | ||
| empty-text-shortcut assertions. | ||
| - Add a regression test that `insert_new_assignments` encrypts `notes` at write (asserts the inserted | ||
| row's `notes` is the `encrypt_if_present` output, not plaintext) — locks in R4 / #126. | ||
| - All agent runs are mocked (`AsyncMock`); no live Gemini/Supabase in the default run, consistent with | ||
| the existing `TestExtractAssignmentsViaAgent` mocking and `_requires_gemini` gating. | ||
| ## Acceptance criteria (verifiable) | ||
| 1. `grep -rn "call_gemini\|parse_syllabus\|PROMPT_PATH" backend/services/calendar_service.py` → no matches. | ||
| 2. No test in `tests/test_ocr_pipeline.py` references `parse_syllabus`. | ||
| 3. `extract_assignments_from_file` returns `{assignments, warnings, raw_text}` (superset ok) on all | ||
| three paths — success, agent-failure degrade, empty-text — verified by tests. | ||
| 4. Agent-failure degrade returns `assignments == []` + a warning + `raw_text`, with no second LLM call. | ||
| 5. `insert_new_assignments` encrypts `notes` at write (regression test passes). | ||
| 6. `python -m pytest tests/test_ocr_pipeline.py tests/test_calendar_routes.py -q` passes; the broader | ||
| `python -m pytest tests/ -q` shows no new failures vs. `main`. | ||
| 7. `ruff check .` passes for changed files. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Align the test paths with the repo layout.
The spec still points at tests/test_ocr_pipeline.py, tests/test_calendar_routes.py, and tests/, but the supplied context shows these backend tests live under backend/tests/.... As written, the acceptance criteria and verification commands will send people to the wrong paths.
♻️ Suggested path updates
- Update `tests/test_ocr_pipeline.py`:+ Update `backend/tests/test_ocr_pipeline.py`:- `python -m pytest tests/test_ocr_pipeline.py tests/test_calendar_routes.py -q`+ `python -m pytest backend/tests/test_ocr_pipeline.py backend/tests/test_calendar_routes.py -q`- `python -m pytest tests/ -q`+ `python -m pytest backend/tests/ -q`📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - Update `tests/test_ocr_pipeline.py`: | |
| - Remove `parse_syllabus`-dependent legacy paths: drop `test_gemini_parse` and repoint the | |
| `parsed_assignments` fixture + the `__main__` block off `parse_syllabus` (use the agent path or | |
| remove), since the function no longer exists. | |
| - Rewrite the two `test_falls_back_to_legacy_*` tests to assert the **graceful degrade**: on agent | |
| `UsageLimitExceeded` and on a bare `Exception`, the result has `assignments == []`, a non-empty | |
| `warnings`, `raw_text == <text>`, and `parse_syllabus` is never referenced. | |
| -`test_returns_agent_assignments` and `test_agent_and_legacy_paths_share_required_keys`: drop the | |
| `patch.object(calendar_service, "parse_syllabus", ...)`; keep the agent-path and required-keys | |
| assertions (the required-keys check now covers success + degrade paths). | |
| -`test_legacy_path_still_works_when_text_empty`: drop the `parse_syllabus` patch; keep the | |
| empty-text-shortcut assertions. | |
| - Add a regression test that `insert_new_assignments` encrypts `notes` at write (asserts the inserted | |
| row's `notes` is the `encrypt_if_present` output, not plaintext) — locks in R4 / #126. | |
| - All agent runs are mocked (`AsyncMock`); no live Gemini/Supabase in the default run, consistent with | |
| the existing `TestExtractAssignmentsViaAgent` mocking and `_requires_gemini` gating. | |
| ## Acceptance criteria (verifiable) | |
| 1.`grep -rn "call_gemini\|parse_syllabus\|PROMPT_PATH" backend/services/calendar_service.py` → no matches. | |
| 2. No test in `tests/test_ocr_pipeline.py` references `parse_syllabus`. | |
| 3.`extract_assignments_from_file` returns `{assignments, warnings, raw_text}` (superset ok) on all | |
| three paths — success, agent-failure degrade, empty-text — verified by tests. | |
| 4. Agent-failure degrade returns `assignments == []` + a warning + `raw_text`, with no second LLM call. | |
| 5.`insert_new_assignments` encrypts `notes` at write (regression test passes). | |
| 6.`python -m pytest tests/test_ocr_pipeline.py tests/test_calendar_routes.py -q` passes; the broader | |
| `python -m pytest tests/ -q` shows no new failures vs. `main`. | |
| 7.`ruff check .` passes for changed files. | |
| - Update `backend/tests/test_ocr_pipeline.py`: | |
| - Remove `parse_syllabus`-dependent legacy paths: drop `test_gemini_parse` and repoint the | |
| `parsed_assignments` fixture + the `__main__` block off `parse_syllabus` (use the agent path or | |
| remove), since the function no longer exists. | |
| - Rewrite the two `test_falls_back_to_legacy_*` tests to assert the **graceful degrade**: on agent | |
| `UsageLimitExceeded` and on a bare `Exception`, the result has `assignments == []`, a non-empty | |
| `warnings`, `raw_text == <text>`, and `parse_syllabus` is never referenced. | |
| -`test_returns_agent_assignments` and `test_agent_and_legacy_paths_share_required_keys`: drop the | |
| `patch.object(calendar_service, "parse_syllabus", ...)`; keep the agent-path and required-keys | |
| assertions (the required-keys check now covers success + degrade paths). | |
| -`test_legacy_path_still_works_when_text_empty`: drop the `parse_syllabus` patch; keep the | |
| empty-text-shortcut assertions. | |
| - Add a regression test that `insert_new_assignments` encrypts `notes` at write (asserts the inserted | |
| row's `notes` is the `encrypt_if_present` output, not plaintext) — locks in R4 / `#126`. | |
| - All agent runs are mocked (`AsyncMock`); no live Gemini/Supabase in the default run, consistent with | |
| the existing `TestExtractAssignmentsViaAgent` mocking and `_requires_gemini` gating. | |
| ## Acceptance criteria (verifiable) | |
| 1.`grep -rn "call_gemini\|parse_syllabus\|PROMPT_PATH" backend/services/calendar_service.py` → no matches. | |
| 2. No test in `backend/tests/test_ocr_pipeline.py` references `parse_syllabus`. | |
| 3.`extract_assignments_from_file` returns `{assignments, warnings, raw_text}` (superset ok) on all | |
| three paths — success, agent-failure degrade, empty-text — verified by tests. | |
| 4. Agent-failure degrade returns `assignments == []` + a warning + `raw_text`, with no second LLM call. | |
| 5.`insert_new_assignments` encrypts `notes` at write (regression test passes). | |
| 6.`python -m pytest backend/tests/test_ocr_pipeline.py backend/tests/test_calendar_routes.py -q` passes; the broader | |
| `python -m pytest backend/tests/ -q` shows no new failures vs. `main`. | |
| 7.`ruff check .` passes for changed files. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/144-calendar-agent.md` around lines 47 - 73, The acceptance criteria
and verification commands reference test locations that don’t match the current
backend layout, so update the spec to use the actual backend test paths. Replace
mentions of tests/test_ocr_pipeline.py, tests/test_calendar_routes.py, and
tests/ with the corresponding backend/tests/... locations, and keep the rest of
the requirements tied to the same test symbols and behaviors so contributors run
the right suite.
Source: Path instructions
Closes#144 (Agent-migration epic #152, milestone #2).
What
calendar_service.pyalready extracted syllabus assignments throughsyllabus_extraction_agentas the primary path — the only remaining raw-Gemini seam was the legacyparse_syllabus/call_gemini_jsonfallback. This removes it:parse_syllabus, thecall_gemini_jsonimport, and the now-unusedPROMPT_PATH.UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure,extract_assignments_from_filenow returns_degraded_result— empty assignments + a user-facing warning, no second LLM call. Resilience is preserved (a single agent failure can't take syllabus upload down) without a raw Gemini call.Contract & security preserved
extract_assignments_from_filestill returns{assignments, warnings, raw_text}(superset ok) on every path — success, degrade, and empty-text — soroutes/calendar.py::extractandprocess_and_save_syllabusare unaffected.notesstay encrypted at the write boundary (insert_new_assignments→encrypt_if_present) per [P1] Encryption boundary leaks: unencrypted assignment-notes write + ciphertext in API responses #126 — added a regression test that locks this in.Tests
parse_syllabus-based fallback tests intest_ocr_pipeline.pyto assert the graceful degrade (empty + warning, agent awaited once, no legacy path).@_requires_geminifixtures/tests offparse_syllabusonto the agent.TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.test_storage_servicefailures are pre-existing onmain— missing SUPABASE env).ruffclean.Out of scope: quiz (#145), flashcards (#146), and the
services/gemini_service.pydeletion (#151 final cutover).🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests