Validation: Schedule image digestion: Claude Vision OCR to WorkQueueItems

validation-204-2026-06-13 Doc

validation partial

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.