Validation: Schedule image digestion: Claude Vision OCR to WorkQueueItems
Verdict: PARTIAL
Ticket
ldraney/landscaping-assistant#204 — Schedule image digestion via Claude Vision API. Merged via PR #208. Adds ScheduleDigester service that uses Claude Vision to OCR photographed weekly schedules, fuzzy-matches extracted names against existing Property records, and creates WorkQueueItems on confirmation.
Environment
Dev server:
https://landscaping-dev.tail5b443a.ts.net (Tailscale). Woodpecker CI pipeline #515 (push to main). Local checkout at commit 058fa49.Checks
| # | Criterion | How Verified | Result | Evidence |
|---|---|---|---|---|
| 1 | anthropic gem installed | <code>git show HEAD:Gemfile</code> + <code>Gemfile.lock</code> | PASS | Line 59: <code>gem "anthropic"</code>; locked at v1.48.1 |
| 2 | ScheduleDigester service exists and is loadable | <code>git show HEAD:app/services/schedule_digester.rb</code> | PASS | 267-line service with Claude Vision API call, Levenshtein fuzzy matching, JSON parsing, error handling (ApiError, ParseError) |
| 3 | week_start migration exists and in schema | <code>git ls-tree HEAD db/migrate/</code> + <code>git show HEAD:db/schema.rb</code> | PASS | Migration <code>20260613000000_add_week_start_to_uploads.rb</code> exists; schema line 128: <code>t.date "week_start"</code> |
| 4 | digest and confirm_digest routes exist | <code>git show HEAD:config/routes.rb</code> | PASS | Lines 41-42: <code>post :digest</code>, <code>post :confirm_digest</code> under uploads member routes |
| 5 | "Digest Schedule" button on upload show page | <code>git show HEAD:app/views/uploads/show.html.erb</code> | PASS | Lines 13-21: form with <code>digest_upload_path</code>, button text "Digest Schedule" |
| 6 | Confirmation screen with matched/unmatched/ambiguous | <code>git show HEAD:app/views/uploads/digest.html.erb</code> | PASS | Full confirmation UI: exact matches (green, checked), fuzzy matches (yellow, with candidate selection), unmatched (gray, "No match -- skipped") |
| 7 | Controller actions with error handling | <code>git show HEAD:app/controllers/uploads_controller.rb</code> | PASS | Lines 42-86: digest action rescues ApiError and ParseError with user-friendly flash messages; confirm_digest creates WQIs via ScheduleDigester.create_work_queue_items |
| 8 | Duplicate detection (skip existing WQI for property+date) | <code>git show HEAD:app/services/schedule_digester.rb</code> lines 38-66 | PASS | <code>WorkQueueItem.find_by(work_date:, property_id:)</code> -- skips if exists, counts as <code>skipped</code> |
| 9 | No new Property records created | Code review of ScheduleDigester + confirm_digest | PASS | Only <code>WorkQueueItem.create!</code> is called; unmatched names display as informational only with "No match -- skipped" |
| 10 | Test files exist | <code>git ls-tree HEAD spec/</code> | PASS | <code>spec/services/schedule_digester_spec.rb</code> and <code>spec/requests/uploads_spec.rb</code> both exist |
| 11 | Woodpecker pipeline green (CI tests pass) | <code>mcp__woodpecker__get_pipeline_status(515)</code> | PASS | Pipeline #515: all 6 steps success (clone, database, bundle-install, lint, test, build-and-push) |
| 12 | Dev server: routes accessible | <code>curl https://landscaping-dev.tail5b443a.ts.net/uploads</code> | FAIL | HTTP 500: <code>ActiveRecord::PendingMigrationError</code>. Two pending migrations: <code>20260612000000_add_completed_by_other_to_work_queue_items</code> (from another PR) and <code>20260613000000_add_week_start_to_uploads</code> (this PR). ALL routes are blocked until <code>bin/rails db:migrate</code> runs on the dev server. |
Regression Check
Routes file reviewed: all existing routes (properties, work_queue_items/today, weeks, uploads CRUD, crew, profile, platform) are intact. The digest routes are additive (member routes on existing uploads resource). No models were modified beyond Upload gaining a
week_start column. No existing tests were altered.Discovered Issues
Dev server migrations not applied. The dev server at
landscaping-dev.tail5b443a.ts.net shows ActiveRecord::PendingMigrationError for two migrations. This is not specific to PR #208 -- the other pending migration (add_completed_by_other_to_work_queue_items) is from a different PR. Running bin/rails db:migrate on the dev server will unblock all routes. This is a deployment operations gap, not a code defect.