Skip to content

KAN-294: ops docs → Drive/NotebookLM (+ staff offboarding fix, gemini-flash-latest) - #489

Merged
corrin merged 11 commits into
mainfrom
feat/KAN-294-ops-manual-notebooklm
Jul 23, 2026
Merged

KAN-294: ops docs → Drive/NotebookLM (+ staff offboarding fix, gemini-flash-latest)#489
corrin merged 11 commits into
mainfrom
feat/KAN-294-ops-manual-notebooklm

Conversation

@corrin

@corrin corrin commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Bundles this weekend's work on branch feat/KAN-294-ops-manual-notebooklm (one PR, per the weekend-PR model). Five commits, net −1000 lines (mostly the removed VitePress manual).

KAN-294 — Operations docs → Google Drive + NotebookLM

The app stops being a documentation silo. The in-repo VitePress manual is retired and its content migrated into MSM's Operations Manual in Drive (01–03 SOPs), retrieved via a NotebookLM notebook.

  • Retire frontend/manual/; remove the nginx /manual/ block, the Vite dev proxy, manual:build, and the manual VS Code task.
  • Repoint the "App Training" navbar link at the Staff Operations NotebookLM notebook.
  • Salvage the durable Drive tooling into tracked scripts/:
    • explore_google_drive.py — browse the MSM Shared Drive (the old raw-service-account version only ever saw an empty My Drive).
    • set_doc_screenshot.py — push a captured PNG into a Doc at its {{screenshot:id}} marker (push half of the screenshot pipeline; capture half is frontend/scripts/capture-screenshots.ts, now fixed to work behind the ngrok tunnel).
    • Both authenticate via GCP_CREDENTIALS + CompanyDefaults.company_email (app convention), with a GCP_DELEGATED_SUBJECT override — no machine-specific literals.

Remaining KAN-294 scope (notebooks creation, Estimator-fork resolution, screenshot-marker expansion) is tracked on the ticket.

Staff offboarding bug fix

Offboarding an employee had no working path. Offboard via Staff.date_left (now surfaced in the UI); the broken hard-delete endpoint is removed — delete was blocked by on_delete=PROTECT on real staff anyway.

Gemini model migration

Migrate AIProvider rows off deprecated pinned model names onto gemini-flash-latest (data migration 0012 + test); update the model help_text, providers, fixtures, templates, docs, and frontend examples.

Repo hygiene

  • CLAUDE.md: docs/plans/ is ephemeral scratch — delete a plan at PR-open, migrate anything durable first (Jira / scripts/ / ADR), never leave a non-plan artifact there.
  • mypy-baseline.txt shrunk by 3 (resolved violations, per the ratchet).
  • Includes a small pdf-goldens test fixture (main-line phone).

🤖 Generated with Claude Code

https://claude.ai/code/session_01DDssHZ17GwD2wtPyac7tEm

Summary by CodeRabbit

  • New Features
    • Added NotebookLM links support, including a “Resources” menu with staff chatbots and an admin page to manage links.
    • Staff offboarding now supports a “Date Left” workflow (instead of deletion).
    • Updated AI provider configuration to use the latest Gemini Flash model by default.
  • Bug Fixes
    • Improved screenshot capture reliability when encountering the ngrok browser warning.
  • Documentation
    • Retired the built-in Training Manual pages and removed related manual development commands.

corrin and others added 5 commits July 21, 2026 22:28
KAN-297 relocated the "Main line" PhoneEndpoint out of the
company_defaults fixture (to satisfy the instance.sh config validator)
into initial_data.json. The PDF golden tests load only company_defaults,
so the test DB lost the endpoint and the delivery-docket letterhead
rendered without the shop phone (+6496365131) — 11 fewer bytes than the
committed golden, failing test_delivery_docket_matches_golden in CI.

The golden is correct; the test's fixture set had drifted. build_golden_job
documents itself as the single source of truth for every field that
influences PDF output, so create the MAIN_LINE endpoint there rather than
leaning on an external seed fixture. Restores byte-identity with no golden
regeneration and decouples the golden tests from seed-fixture churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3hi37oW5tWkjGjtZV1Jbz
Deleting a staff member was a hard delete the DB blocks (CostLine.staff
is on_delete=PROTECT) for anyone who has logged time, so it failed for
every real employee -- and the staff form exposed no way to set
date_left, the actual offboarding mechanism. There was no working way to
offboard someone.

- StaffFormModal: add a Date Left field (always sent, so clearing it
  reinstates an employee)
- AdminStaffView/useStaffApi: remove the Delete button and its
  accounts_staff_destroy caller
- staff_api: StaffRetrieveUpdateDestroyAPIView -> StaffRetrieveUpdateAPIView
  (retrieve + update only); regenerate schema.yml and api.ts
- test: DELETE on a staff member returns 405 and the record survives

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The manual content now lives in the Operations Manual on Google Drive,
surfaced to staff via a NotebookLM notebook. Remove the in-repo VitePress
manual and its build/serve wiring, and repoint the navbar at the notebook.

- delete frontend/manual/ (VitePress site) and dist-manual/
- drop manual:dev/build/preview npm scripts (keep manual:screenshots,
  the screenshot tool, to be repointed at the Docs separately)
- remove the /manual vite dev proxy and the nginx /manual/ location
- drop `npm run manual:build` from the release build
- remove the "Frontend Manual Dev Server" VS Code task
- repoint the "App Training" navbar links at the NotebookLM notebook
  (relabelled "Training Manual")
- prune dead references (frontend README manual section, eslint globs)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
capture-screenshots.ts navigates to APP_DOMAIN, which in dev is an
ngrok-free tunnel. ngrok's browser-warning interstitial hid the login
page, so Playwright timed out waiting for #username. Send the
ngrok-skip-browser-warning header on the browser context (harmless on
non-ngrok hosts) so capture reaches the real app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h-latest

Two settled bodies of work, committed together per the all-or-nothing rule.

KAN-294 ops-manual tooling:
- explore_google_drive.py: browse the MSM Shared Drive via delegated auth
  (GCP_CREDENTIALS + CompanyDefaults.company_email, GCP_DELEGATED_SUBJECT
  override); the old raw-SA version only ever saw an empty My Drive.
- set_doc_screenshot.py: salvaged from throwaway migration scratch into a
  tracked tool — the push half of the screenshot pipeline (capture half is
  frontend/scripts/capture-screenshots.ts).
- CLAUDE.md: docs/plans is ephemeral scratch — delete a plan at PR-open,
  migrate anything durable first, never leave a non-plan artifact there.

Gemini model migration:
- Migrate AIProvider rows off deprecated pinned model names onto
  gemini-flash-latest (migration 0012 + test), update the model help_text,
  providers, fixtures, templates, docs, and frontend examples.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDssHZ17GwD2wtPyac7tEm
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@corrin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aa2e61b6-fe82-4265-8738-eeba9c476222

📥 Commits

Reviewing files that changed from the base of the PR and between 7d30914 and cd9a1cb.

📒 Files selected for processing (8)
  • .gitignore
  • apps/quoting/services/pdf_data_validation.py
  • apps/quoting/services/providers/mistral_provider.py
  • apps/quoting/tests/test_ocr_fixtures.py
  • mypy-baseline.txt
  • scripts/read_google_doc.py
  • scripts/set_doc_screenshot.py
  • scripts/write_google_doc.py
📝 Walkthrough

Walkthrough

The PR disables staff deletion, standardizes Gemini model configuration, removes the local VitePress manual, adds NotebookLM link management and Google Drive/Docs tooling, and updates deployment, fixture, documentation, and workflow guidance.

Changes

Staff lifecycle

Layer / File(s) Summary
Staff API deletion removal
apps/accounts/views/*, apps/accounts/urls.py, apps/accounts/tests/*, frontend/schema.yml, docs/urls/accounts.md
Staff detail routes now support retrieval and updates only; deletion returns 405, with offboarding represented by date_left.
Staff offboarding form and actions
frontend/src/components/StaffFormModal.vue, frontend/src/composables/useStaffApi.ts, frontend/src/views/AdminStaffView.vue
The form submits nullable date_left values, while delete actions and confirmation UI are removed.

Gemini model standardization

Layer / File(s) Summary
Provider model selection
apps/quoting/services/*, apps/quoting/tests/*
Gemini and Mistral providers use configurable model defaults, shared provider contracts, and dynamic extraction metadata.
Stored model migration and configuration
apps/workflow/*, frontend/schema.yml, docs/client_onboarding.md, scripts/server/templates/*, frontend/src/components/*
Deprecated Gemini identifiers are migrated and configuration examples use the rolling alias.

Training manual removal

Layer / File(s) Summary
Remove local manual runtime
.vscode/tasks.json, frontend/package.json, frontend/vite.config.ts, scripts/server/*, frontend/eslint.config.ts
Local manual tasks, scripts, proxying, nginx routing, and release builds are removed.
Remove manual content and navigation
frontend/manual/*, frontend/README.md, frontend/src/components/AppNavbar.vue
Local manual pages and training instructions are removed.
Screenshot host access
frontend/scripts/capture-screenshots.ts
Screenshot contexts send the ngrok warning bypass header.

NotebookLM link management

Layer / File(s) Summary
NotebookLM data and API
apps/workflow/*, frontend/schema.yml
NotebookLM links gain persisted fields, restriction filtering, CRUD/menu endpoints, serializers, migrations, and API tests.
NotebookLM frontend management
frontend/src/App.vue, frontend/src/components/*, frontend/src/services/*, frontend/src/stores/*, frontend/src/views/*
Links load at startup, appear under Resources, and can be managed through a validated admin interface.

Google Drive and Docs tooling

Layer / File(s) Summary
Shared Drive exploration
scripts/explore_google_drive.py, scripts/README.md
The explorer uses delegated credentials, lists Shared Drives, and recursively prints selected drive contents.
Google Docs document utilities
scripts/read_google_doc.py, scripts/set_doc_screenshot.py, scripts/write_google_doc.py
New CLIs read Google Docs, insert screenshots at markers, and manage Markdown imports with revision-based overwrite checks.

Supporting repository updates

Layer / File(s) Summary
Repository workflow, release, and fixture support
CLAUDE.md, .gitignore, apps/job/tests/*, frontend/scripts/*, scripts/server/*
Commit guidance, plan/runtime ignores, golden fixtures, screenshot access, and frontend release installation are updated.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly captures the main changes: ops docs migration, staff offboarding, and Gemini model updates.
Description check ✅ Passed The description covers the main work and Jira key, but it doesn't follow the template sections or include the checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/KAN-294-ops-manual-notebooklm

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
apps/accounts/tests/test_staff_api.py (1)

55-80: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding a companion offboarding test.

This test class only exercises the DELETE-rejection path. Given the PR's stated goal of enabling offboarding via date_left, a test that PATCHes date_left on the detail endpoint and asserts it persists would directly cover the new intended workflow this endpoint change enables.

🤖 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 `@apps/accounts/tests/test_staff_api.py` around lines 55 - 80, Add a companion
test to StaffDetailAPIViewTests that authenticates an office staff user, PATCHes
the target staff member’s date_left through the detail endpoint, and asserts a
successful response plus the persisted date_left value. Reuse the existing Staff
and APIClient setup from test_staff_cannot_be_deleted_via_api to cover the
intended offboarding workflow.
🤖 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 `@apps/job/tests/_pdf_golden_fixtures.py`:
- Around line 77-86: The golden fixture builder must make the rendered main-line
endpoint deterministic despite pre-existing MAIN_LINE fixtures. Update
build_golden_job’s setup to deactivate or otherwise exclude existing MAIN_LINE
rows and ensure the builder-created endpoint is active with number +6496365131,
or explicitly pin _primary_company_endpoint_number to that unique endpoint;
preserve the expected golden PDF output.

In `@apps/quoting/services/ai_price_extraction.py`:
- Line 22: Update MistralPriceExtractionProvider and the create_provider() path
so every returned provider satisfies the PriceExtractionProvider.model_name
contract. Store an appropriate Mistral model name on the Mistral instance, or
consistently narrow the shared contract to exclude provider-scoped model_name;
preserve the existing provider selection behavior.

In `@CLAUDE.md`:
- Around line 127-129: Reconcile the `docs/plans/` guidance in `CLAUDE.md` with
the root `.gitignore`: preserve the `_template.md` exception by adding an
explicit `!docs/plans/_template.md` negation after the `docs/plans/` ignore
rule, or remove the exception from the documentation.

In `@scripts/explore_google_drive.py`:
- Around line 41-44: Remove the unused Google Docs URL from the SCOPES list,
leaving only the Drive scope required by the Drive client construction in this
script.

In `@scripts/set_doc_screenshot.py`:
- Around line 91-104: The upload_png function leaves the publicly shared
temporary Drive image undeleted. Ensure the temporary file identified by fid is
deleted with drive.files().delete(...).execute() after the Docs image insertion
completes, while preserving the existing sharing setup needed for that
insertion.

---

Nitpick comments:
In `@apps/accounts/tests/test_staff_api.py`:
- Around line 55-80: Add a companion test to StaffDetailAPIViewTests that
authenticates an office staff user, PATCHes the target staff member’s date_left
through the detail endpoint, and asserts a successful response plus the
persisted date_left value. Reuse the existing Staff and APIClient setup from
test_staff_cannot_be_deleted_via_api to cover the intended offboarding workflow.
🪄 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: dc1aa5be-7c62-4522-a793-c5673ff2d679

📥 Commits

Reviewing files that changed from the base of the PR and between 70ee021 and f76dec6.

⛔ Files ignored due to path filters (1)
  • frontend/src/api/generated/api.ts is excluded by !**/generated/**
📒 Files selected for processing (51)
  • .vscode/tasks.json
  • CLAUDE.md
  • apps/accounts/tests/test_staff_api.py
  • apps/accounts/urls.py
  • apps/accounts/views/__init__.py
  • apps/accounts/views/staff_api.py
  • apps/job/tests/_pdf_golden_fixtures.py
  • apps/quoting/services/ai_price_extraction.py
  • apps/quoting/services/providers/gemini_provider.py
  • apps/quoting/tests/test_ai_price_extraction.py
  • apps/workflow/fixtures/ai_providers.json.example
  • apps/workflow/migrations/0012_use_latest_gemini_flash_model.py
  • apps/workflow/models/ai_provider.py
  • apps/workflow/tests/test_latest_gemini_model_migration.py
  • docs/client_onboarding.md
  • docs/urls/accounts.md
  • frontend/README.md
  • frontend/eslint.config.ts
  • frontend/manual/.vitepress/config.ts
  • frontend/manual/admin/manage-staff.md
  • frontend/manual/admin/run-payroll.md
  • frontend/manual/end-of-week/weekly-checklist.md
  • frontend/manual/enquiries/new-customer-call.md
  • frontend/manual/fieldwork/complete-a-job.md
  • frontend/manual/index.md
  • frontend/manual/invoicing/invoice-a-job.md
  • frontend/manual/jobs/attach-files.md
  • frontend/manual/jobs/understanding-job-finances.md
  • frontend/manual/management/run-reports.md
  • frontend/manual/purchasing/create-purchase-order.md
  • frontend/manual/quoting/assess-and-price.md
  • frontend/manual/quoting/send-quote.md
  • frontend/manual/scheduling/schedule-a-job.md
  • frontend/manual/timesheets/end-of-day-entry.md
  • frontend/package.json
  • frontend/schema.yml
  • frontend/scripts/capture-screenshots.ts
  • frontend/src/components/AppNavbar.vue
  • frontend/src/components/StaffFormModal.vue
  • frontend/src/components/admin/AIProviderFormModal.vue
  • frontend/src/components/chat/README.md
  • frontend/src/composables/useStaffApi.ts
  • frontend/src/views/AdminStaffView.vue
  • frontend/vite.config.ts
  • mypy-baseline.txt
  • scripts/README.md
  • scripts/explore_google_drive.py
  • scripts/server/release-utils.sh
  • scripts/server/templates/ai-providers.json.template
  • scripts/server/templates/nginx-instance.conf.template
  • scripts/set_doc_screenshot.py
💤 Files with no reviewable changes (25)
  • frontend/manual/index.md
  • frontend/manual/jobs/attach-files.md
  • frontend/manual/fieldwork/complete-a-job.md
  • frontend/manual/enquiries/new-customer-call.md
  • frontend/manual/quoting/assess-and-price.md
  • frontend/manual/.vitepress/config.ts
  • frontend/manual/management/run-reports.md
  • frontend/manual/quoting/send-quote.md
  • frontend/manual/end-of-week/weekly-checklist.md
  • frontend/manual/purchasing/create-purchase-order.md
  • frontend/manual/jobs/understanding-job-finances.md
  • frontend/manual/scheduling/schedule-a-job.md
  • frontend/manual/invoicing/invoice-a-job.md
  • frontend/README.md
  • frontend/eslint.config.ts
  • frontend/manual/admin/run-payroll.md
  • frontend/manual/timesheets/end-of-day-entry.md
  • frontend/vite.config.ts
  • scripts/server/release-utils.sh
  • frontend/src/composables/useStaffApi.ts
  • frontend/manual/admin/manage-staff.md
  • frontend/package.json
  • scripts/server/templates/nginx-instance.conf.template
  • .vscode/tasks.json
  • mypy-baseline.txt

Comment thread apps/job/tests/_pdf_golden_fixtures.py
Comment thread apps/quoting/services/ai_price_extraction.py Outdated
Comment thread CLAUDE.md
Comment thread scripts/explore_google_drive.py Outdated
Comment thread scripts/set_doc_screenshot.py
The Training Manual navbar link hardcoded MSM's NotebookLM URL in the shared
frontend, so every client (second customer onboarding next week) would ship
pointing at MSM's notebook. Replace it with a per-instance managed table.

Backend:
- NotebookLmLink model (name / url / enabled / restriction / order) +
  migration 0013; restriction is a NotebookLmRestriction choice (all staff vs
  superusers only).
- NotebookLmLinkViewSet: office-staff-gated CRUD for the admin UI, plus an
  all-staff 'menu' action that server-side-filters to the enabled links the
  caller may see (excludes superuser-restricted rows for non-superusers).
- Tests: menu filtering by restriction/enabled/auth; CRUD permission gating.

Frontend:
- notebookLmLinks store loads the menu at startup; the navbar renders the links
  under a new Resources dropdown reorganised into Chatbots / Forms / Procedures.
- AdminNotebookLmLinksView + NotebookLmLinkFormModal + service: full CRUD admin
  page (auto-routed under the superuser-gated /admin section).

Table ships empty (no data-migration seed) so no client inherits another's URL;
each client's superuser adds their rows via /admin/notebooklm-links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDssHZ17GwD2wtPyac7tEm

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@apps/workflow/__init__.py`:
- Line 4: Regenerate apps/workflow/__init__.py using python
scripts/update_init.py after adding the NotebookLM module, rather than manually
editing its exports. Commit the generated changes, including all affected export
entries such as AIProviderTypes and NotebookLmRestriction.

In `@frontend/src/components/AppNavbar.vue`:
- Around line 145-148: Update the Resources dropdown visibility around the
NotebookLM menu to allow all authenticated staff when NotebookLmRestriction.NONE
applies, rather than gating it with userInfo.is_office_staff. Keep Forms and
Procedures navigation restricted to office staff, and separate their visibility
conditions from the unrestricted NotebookLM link.

In `@frontend/src/stores/notebookLmLinks.ts`:
- Around line 1-28: Update loadLinks in useNotebookLmLinksStore to call a
menu-list method on notebookLmLinkService instead of invoking
api.workflow_notebook_lm_links_menu_list directly. Add or expose the
corresponding service method that delegates to the API, then use it in the store
while preserving the existing loading, error, and state-management behavior.

In `@frontend/src/views/AdminNotebookLmLinksView.vue`:
- Around line 181-185: Update the catch blocks in the save-link error handling
to narrow caught values with an instanceof Error check before reading message,
replacing the prohibited error as Error cast. Preserve the existing fallback
message for non-Error throws and apply the same change to both affected
handlers.
- Around line 163-180: Reload the shared useNotebookLmLinksStore menu after
successful mutations so navbar links stay current: in handleSave, refresh the
store after create or update and before closing or refreshing the local list; in
the deletion handler at frontend/src/views/AdminNotebookLmLinksView.vue lines
194-199, perform the same store reload after successful deletion.
🪄 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: 4d577129-3ce0-446d-bdcb-83a0e12d91d2

📥 Commits

Reviewing files that changed from the base of the PR and between f76dec6 and f084e19.

⛔ Files ignored due to path filters (1)
  • frontend/src/api/generated/api.ts is excluded by !**/generated/**
📒 Files selected for processing (18)
  • apps/workflow/__init__.py
  • apps/workflow/enums.py
  • apps/workflow/migrations/0013_notebooklmlink.py
  • apps/workflow/models/__init__.py
  • apps/workflow/models/notebook_lm_link.py
  • apps/workflow/serializers.py
  • apps/workflow/tests/test_notebook_lm_link_api.py
  • apps/workflow/urls.py
  • apps/workflow/views/notebook_lm_link_viewset.py
  • frontend/schema.yml
  • frontend/src/App.vue
  • frontend/src/components/AppNavbar.vue
  • frontend/src/components/__tests__/AppNavbar.test.ts
  • frontend/src/components/admin/NotebookLmLinkFormModal.vue
  • frontend/src/config/adminPages.ts
  • frontend/src/services/notebookLmLinkService.ts
  • frontend/src/stores/notebookLmLinks.ts
  • frontend/src/views/AdminNotebookLmLinksView.vue

Comment thread apps/workflow/__init__.py
Comment thread frontend/src/components/AppNavbar.vue
Comment thread frontend/src/stores/notebookLmLinks.ts
Comment thread frontend/src/views/AdminNotebookLmLinksView.vue
Comment thread frontend/src/views/AdminNotebookLmLinksView.vue
corrin and others added 2 commits July 23, 2026 13:55
Rebuild the manual-maintenance capability deleted with the KAN-294 migration
scratch (only the screenshot pusher had been salvaged):
- read_google_doc.py: export a Google Doc as Markdown.
- write_google_doc.py: import Markdown as a Doc with a revisionId safety net
  that refuses to overwrite a human-edited doc (seed/import/trash/status);
  per-instance manifest is gitignored runtime state.
Both use the app auth convention (GCP_CREDENTIALS + CompanyDefaults.company_email,
GCP_DELEGATED_SUBJECT override), matching explore_google_drive.py. Supports the
Operations Manual cleanup (KAN-302).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDssHZ17GwD2wtPyac7tEm
Triaged all ten CodeRabbit comments; seven were valid.

Resources menu is no longer office-staff gated. The `menu` endpoint serves
any authenticated staff member and NotebookLmRestriction.NONE means "all
staff", but the navbar hid the whole dropdown behind is_office_staff, so
shop-floor staff could never reach an unrestricted link. The process read
endpoints are already IsAuthenticated and the routes are requiresAuth only,
so procedures and forms open up with it — that is the point of the ops
manual. Covered by a navbar test that fails if the gate returns.

NotebookLM frontend: the store called the generated client directly, so
menu loading bypassed the service layer; and the navbar's shared store was
loaded once at startup and never refreshed, leaving admin creates, edits and
deletes invisible until a page reload.

Price extraction: neither provider actually inherited PriceExtractionProvider,
so the factory's return type was wrong for both and Mistral had no model_name
at all. Both now inherit the ABC, Mistral's hardcoded "mistral-ocr-latest"
becomes a constant the factory can override like Gemini's, and the two
resolved violations are dropped from the mypy baseline.

Scripts: explore_google_drive.py no longer requests the unused `documents`
scope, and set_doc_screenshot.py deletes the temp upload rather than trashing
it — trashing left the "anyone/reader" grant live.

.gitignore now matches docs/plans/* so the !_template.md negation is
reachable; git never descends into an excluded directory.

Rejected two: the PDF golden fixture is already deterministic (BaseTestCase
loads no PhoneEndpoint rows and normalized_number is unique), and
apps/workflow/__init__.py was generated — re-running update_init.py leaves
it unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wHxbSZWq7V7jMkBi3Lq1N

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
apps/quoting/tests/test_ai_price_extraction.py (1)

47-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the exact model selected by the factory.

assertTrue(provider.model_name) would pass for any non-empty model and will not catch a regression in the Mistral default wiring. Assert GEMINI_FLASH_MODEL and MISTRAL_OCR_MODEL explicitly for their respective factory branches.

Proposed test shape
+        expected_models = {
+            AIProviderTypes.GOOGLE: GEMINI_FLASH_MODEL,
+            AIProviderTypes.MISTRAL: MISTRAL_OCR_MODEL,
+        }
         for provider_type in (AIProviderTypes.GOOGLE, AIProviderTypes.MISTRAL):
             with self.subTest(provider_type=provider_type):
                 provider = PriceExtractionFactory.create_provider(
                     provider_type,
                     "test-api-key",
                     "",
                 )

                 self.assertIsInstance(provider, PriceExtractionProvider)
                 self.assertTrue(provider.provider_name)
-                self.assertTrue(provider.model_name)
+                self.assertEqual(
+                    provider.model_name,
+                    expected_models[provider_type],
+                )
🤖 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 `@apps/quoting/tests/test_ai_price_extraction.py` around lines 47 - 58, Update
test_factory_returns_a_provider_with_a_model_name to assert the exact model_name
returned by PriceExtractionFactory for each provider_type: GEMINI_FLASH_MODEL
for GOOGLE and MISTRAL_OCR_MODEL for MISTRAL. Replace the generic truthiness
assertion while retaining the existing provider type and provider_name checks.
apps/quoting/services/providers/mistral_provider.py (1)

313-313: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a boundary test for the Mistral OCR model override.

The existing Mistral OCR tests only check provider.model_name; mock client.ocr.process and assert that the configured model=self.model_name value is forwarded to the OCR call so a regression to a hardcoded model would fail.

🤖 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 `@apps/quoting/services/providers/mistral_provider.py` at line 313, Extend the
Mistral OCR tests around the OCR invocation to mock client.ocr.process and
assert it receives the configured model_name value as model. Keep the assertion
focused on the forwarded argument so a hardcoded model override is detected,
while preserving the existing provider.model_name checks.
🤖 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 `@apps/quoting/services/providers/base.py`:
- Around line 1-2: Update the shared extraction result contract used by
extract_price_data() to replace Optional[Dict[str, Any]] with a named,
explicitly typed result type or TypedDict, and remove the Any import. Apply the
same named type consistently across the base interface and all concrete provider
overrides while preserving the existing tuple and optional-result semantics.
- Around line 11-24: Update the abstract method extract_price_data in the base
provider class so its body is explicitly non-returning after the docstring,
using raise NotImplementedError or an abstract ellipsis, while preserving its
current signature and documentation.

In `@scripts/read_google_doc.py`:
- Around line 26-29: Update the SCOPES configuration used by read_doc() to
request only the read-only Drive scope, replacing the broader Drive scope and
removing the unnecessary Docs API scope. Preserve the existing document export
behavior.
- Around line 32-56: Make the Google utilities strict-MyPy clean by adding typed
imports, a manifest TypedDict, parameterized containers, and explicit types for
Drive/Docs API and JSON responses. In scripts/read_google_doc.py lines 32-56,
update build_drive and read_doc plus related response handling; apply the
corresponding typing changes throughout scripts/write_google_doc.py lines
58-238, including its manifest and document response paths. Rerun bash
scripts/check_mypy.sh and resolve all reported typing errors.

In `@scripts/write_google_doc.py`:
- Around line 210-214: Update scripts/write_google_doc.py lines 210-214 in
status() so only the expected missing/trashed-document condition maps to
MISSING/TRASHED; let permission, network, and other API failures follow the
required AlreadyLoggedException/persist_app_error deduplicated persistence flow
before re-raising. Update lines 220-230 in main() so OverwriteRefused is
represented as a non-exception result, or is persisted exactly once through that
same two-arm flow before re-raising, preventing operational failures from being
silently reported as success.
- Around line 104-118: Update find_in_folder to escape both the CLI-controlled
title and folder_id as Google Drive query string literals before interpolating
them into q, including single quotes and backslashes. Use the escaped values
consistently in the name and parents predicates while preserving the existing
search filters and return behavior.
- Around line 138-171: Update do_import at scripts/write_google_doc.py lines
138-171 and 176-184 to make the trash/create workflow revision-safe: retain the
revision validated by check_unedited, revalidate or otherwise guard the remote
mutation against intervening edits, and persist a durable pending operation
before mutation with recovery or compensation if any Drive or save step fails.
Ensure the committed manifest state is saved only after the operation completes,
while pending state allows recovery so the newly created document cannot remain
unmanaged when save fails.

---

Nitpick comments:
In `@apps/quoting/services/providers/mistral_provider.py`:
- Line 313: Extend the Mistral OCR tests around the OCR invocation to mock
client.ocr.process and assert it receives the configured model_name value as
model. Keep the assertion focused on the forwarded argument so a hardcoded model
override is detected, while preserving the existing provider.model_name checks.

In `@apps/quoting/tests/test_ai_price_extraction.py`:
- Around line 47-58: Update test_factory_returns_a_provider_with_a_model_name to
assert the exact model_name returned by PriceExtractionFactory for each
provider_type: GEMINI_FLASH_MODEL for GOOGLE and MISTRAL_OCR_MODEL for MISTRAL.
Replace the generic truthiness assertion while retaining the existing provider
type and provider_name checks.
🪄 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: 021a7440-f543-45fe-af20-76cef898c91a

📥 Commits

Reviewing files that changed from the base of the PR and between f084e19 and 7d30914.

📒 Files selected for processing (18)
  • .gitignore
  • apps/quoting/services/ai_price_extraction.py
  • apps/quoting/services/providers/base.py
  • apps/quoting/services/providers/gemini_provider.py
  • apps/quoting/services/providers/mistral_provider.py
  • apps/quoting/tests/test_ai_price_extraction.py
  • frontend/src/components/AppNavbar.vue
  • frontend/src/components/__tests__/AppNavbar.test.ts
  • frontend/src/services/notebookLmLinkService.ts
  • frontend/src/stores/__tests__/notebookLmLinks.test.ts
  • frontend/src/stores/notebookLmLinks.ts
  • frontend/src/views/AdminNotebookLmLinksView.vue
  • mypy-baseline.txt
  • scripts/README.md
  • scripts/explore_google_drive.py
  • scripts/read_google_doc.py
  • scripts/set_doc_screenshot.py
  • scripts/write_google_doc.py
💤 Files with no reviewable changes (1)
  • mypy-baseline.txt
🚧 Files skipped from review as they are similar to previous changes (6)
  • scripts/README.md
  • frontend/src/stores/notebookLmLinks.ts
  • frontend/src/views/AdminNotebookLmLinksView.vue
  • frontend/src/services/notebookLmLinkService.ts
  • scripts/set_doc_screenshot.py
  • scripts/explore_google_drive.py

Comment thread apps/quoting/services/providers/base.py
Comment thread apps/quoting/services/providers/base.py
Comment thread scripts/read_google_doc.py Outdated
Comment thread scripts/read_google_doc.py
Comment thread scripts/write_google_doc.py Outdated
Comment thread scripts/write_google_doc.py
Comment thread scripts/write_google_doc.py
corrin and others added 3 commits July 23, 2026 15:08
find_in_folder() interpolated the title and folder id straight into a Drive
query, so an ordinary title like "Driver's Handbook" terminated the string
literal early and made the whole query invalid. Escape both through a
q_literal() helper.

status() reported every failure as MISSING/TRASHED, so a 403 or a network
blip looked like a deleted document. Only a 404 is a real "it isn't there";
anything else now propagates rather than being reported as a state we did
not actually observe.

read_google_doc.py no longer requests the `documents` scope — it exports
through Drive and never touches a Docs API resource.

Also parameterised the manifest containers with a ManifestEntry TypedDict
instead of bare dict/list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wHxbSZWq7V7jMkBi3Lq1N
The scope, screenshot-cleanup and gitignore comments were narrating the edit
that introduced them rather than explaining the code a later reader will
find.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wHxbSZWq7V7jMkBi3Lq1N
MistralPriceExtractionProvider emitted `supplier_item_code` and a dict
`dimensions`. PDFDataValidationService._sanitize_single_product reads
`item_no` and runs `dimensions` through _clean_text, which stringifies
whatever it gets. So on the Mistral path every imported product lost its
supplier item code, and the dimensions column received a Python repr:

    item_no    : ''
    dimensions : "{'width': '1200', 'length': '2400', 'thickness': '1.2mm', ...}"

Mistral is priority 1 in get_prioritized_active_providers and every
instance template provisions a Mistral row, so this affects any client
using it. Gemini was unaffected — it already emits the importer's names.

The provider is the producer, so the fix goes there rather than teaching
the sanitiser a second field name (ADR 0015). `dimensions` is now rendered
to the display string the importer stores, and `price_unit` is stated
explicitly at the value the sanitiser already defaulted it to.

The existing OCR test asserted the provider's own dict, which is why this
survived: rename the field on both sides and it stays green. Added a test
that crosses into the sanitiser, where the contract actually lives.

Annotating PDFDataValidationService.__init__ to call it from a typed test
resolved 4 baselined violations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wHxbSZWq7V7jMkBi3Lq1N
@corrin
corrin merged commit f8c7d40 into main Jul 23, 2026
17 checks passed
@corrin
corrin deleted the feat/KAN-294-ops-manual-notebooklm branch July 23, 2026 03:59
Sign up for free to 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.

1 participant