feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144) - #298

Merged
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent
Jul 2, 2026
Merged

feat(calendar): syllabus extraction via agent, retire raw-Gemini fallback (#144)#298
AndresL230 merged 2 commits into
mainfrom
feat/144-calendar-agent

Conversation

@Jose-Gael-Cruz-Lopez

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez commented Jul 1, 2026

Copy link
Copy Markdown
Member

Closes#144 (Agent-migration epic #152, milestone #2).

What

calendar_service.py already extracted syllabus assignments through syllabus_extraction_agent as the primary path — the only remaining raw-Gemini seam was the legacy parse_syllabus / call_gemini_json fallback. This removes it:

  • Deletes parse_syllabus, the call_gemini_json import, and the now-unused PROMPT_PATH.
  • On agent guardrail-trip (UsageLimitExceeded/UnexpectedModelBehavior) or unexpected failure, extract_assignments_from_file now 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

Tests

  • Rewrote the parse_syllabus-based fallback tests in test_ocr_pipeline.py to assert the graceful degrade (empty + warning, agent awaited once, no legacy path).
  • Repointed the live @_requires_gemini fixtures/tests off parse_syllabus onto the agent.
  • Added TestNotesEncryptedAtWrite::test_notes_encrypted_on_insert.
  • Full backend suite: 831 passed (the 2 test_storage_service failures are pre-existing on main — missing SUPABASE env). ruff clean.

Out of scope: quiz (#145), flashcards (#146), and the services/gemini_service.py deletion (#151 final cutover).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Syllabus-to-assignments extraction now fails more gracefully, returning a usable result with a clear warning instead of falling back to a legacy path.
    • Empty or problematic uploads now preserve the extracted text and return an empty assignment list rather than breaking the flow.
    • Assignment notes continue to be stored securely without changing visible behavior.
  • Tests

    • Updated automated checks to cover the new graceful-degradation behavior and ensure response fields stay consistent.

…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>
@coderabbitai

coderabbitaiBot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The legacy Gemini-based parse_syllabus fallback in calendar_service.py is removed and replaced with a _degraded_result helper returning an empty-assignments payload with a warning on agent failures. Tests are updated to assert this degrade contract, and a spec document is added.

Changes

Agent-only extraction with graceful degrade

Layer / File(s)Summary
Degrade helper and exception handling
backend/services/calendar_service.py
Removes parse_syllabus and PROMPT_PATH, adds _degraded_result(text) returning empty assignments plus a warning, updates docstrings, and changes exception handling so guardrail and unexpected exceptions degrade instead of falling back to legacy parsing.
Agent-first parsing and degrade tests
backend/tests/test_ocr_pipeline.py
Replaces legacy-fallback tests with agent-first equivalents (test_agent_parse, degrade tests for usage limit/exception/empty text), adds a shared required-keys contract test, adds a notes-encryption regression test, and updates the __main__ runner.
Migration spec
specs/144-calendar-agent.md
Adds a new spec describing the migration goal, requirements, acceptance criteria, and out-of-scope items.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • SaplingLearn/Sapling#79: Refactors the same extract_assignments_from_file function to route through the agent using overlapping _extract_via_agent and fallback/degrade test coverage concepts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the agent migration and Gemini fallback removal.
Description check✅ PassedThe PR description covers the change summary, tests, and linked issue, though it does not use every template heading exactly.
Linked Issues check✅ PassedThe changes satisfy #144: no raw Gemini fallback remains, notes encryption is preserved, and tests cover the agent path.
Out of Scope Changes check✅ PassedThe diff stays focused on calendar extraction migration and related tests, with no clear unrelated code changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/144-calendar-agent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging562c000Commit 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>

@coderabbitaicoderabbitaiBot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5de742e and 562c000.

📒 Files selected for processing (3)
  • backend/services/calendar_service.py
  • backend/tests/test_ocr_pipeline.py
  • specs/144-calendar-agent.md

Comment on lines +47 to +73
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Suggested change
- 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

@AndresL230
AndresL230 merged commit e0d91de into mainJul 2, 2026
6 checks passed
@AndresL230
AndresL230 deleted the feat/144-calendar-agent branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Agent migration: calendar assignment extraction → agent

2 participants

@Jose-Gael-Cruz-Lopez@AndresL230