Review: Schedule image digestion: Claude Vision OCR to WorkQueueItems

review-1450-2026-06-13-r2 Review

review ready

Verdict: APPROVED

Re-review after refinement. Previous review: review-1450-2026-06-13 (NEEDS_REFINEMENT, 4 items). Two body fixes confirmed resolved. Two scope items remain but are project-level shared gaps, not blocking for this ticket.

Template Completeness

  • [x] Type -- Feature
  • [x] Lineage -- Standalone, extends Upload tab (#33)
  • [x] Repo -- ldraney/landscaping-assistant
  • [x] User Story -- crew lead schedule parsing
  • [x] Context -- thorough, covers column-overflow, handwritten additions, existing infra, zero-bloat constraint
  • [x] File Targets -- create 3, modify 5, explicit do-not-touch list
  • [x] Feature Flag -- none, justified (extension of existing upload flow for lead+ roles)
  • [x] Acceptance Criteria -- 9 criteria (8 original + error handling AC added per review)
  • [x] Test Expectations -- 5 test expectations with run command (added malformed API response test)
  • [x] Constraints -- Claude model, API key source, fuzzy matching approach
  • [x] Checklist -- standard 3-item
  • [x] Related -- references project and parent issue #33

Traceability

  • [x] story:schedule-upload label -- present on board item
  • [x] story note verified -- found in project-landscaping-assistant user-stories section: "Snap a photo of the paper schedule for later reference"
  • [ ] story note OUTDATED -- [SCOPE] The user story summary still says "Snap a photo of the paper schedule for later reference" but this ticket extends the story to include AI-powered parsing into WorkQueueItems. Not blocking for this ticket -- project-level update needed separately.
  • [x] arch:rails-app label -- present on board item
  • [ ] arch note MISSING -- [SCOPE] No architecture note found for arch-rails-app in pal-e-docs. Shared gap across many board items, not blocking for this ticket.
  • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/landscaping-assistant/issues/204, state: open

File Targets

Files to create:
  • [x] app/services/schedule_digester.rb -- directory app/services/ does not exist yet (must be created). Standard Rails convention, acceptable.
  • [x] app/views/uploads/digest.html.erb -- parent directory app/views/uploads/ exists, confirmed
  • [x] db/migrate/TIMESTAMP_add_week_start_to_uploads.rb -- standard migration, OK
Files to modify:
  • [x] Gemfile -- verified exists. No anthropic gem present currently. Correct target.
  • [x] app/controllers/uploads_controller.rb -- verified exists. Currently has index/show/create/edit/update/destroy. Adding digest and confirm_digest actions is appropriate.
  • [x] app/models/upload.rb -- verified exists. Issue now correctly states: "add week_start date field (no AR associations to WorkQueueItem; the service creates items directly, the relationship is implicit through week_start date range)". This is clear and matches the architecture -- the service object pattern means Upload does not need has_many associations. Previous [BODY] finding RESOLVED.
  • [x] config/routes.rb -- verified exists. Currently resources :uploads, only: [:index, :show, :create, :edit, :update, :destroy]. Adding member routes for digest/confirm_digest is correct.
  • [x] app/views/uploads/show.html.erb -- verified exists. Adding "Digest Schedule" button is straightforward.
Do-not-touch files:
  • [x] app/models/property.rb -- confirmed exists, no schema changes needed. Property has validates :client_name, presence: true and has_many :work_queue_items.
  • [x] app/controllers/weeks_controller.rb -- confirmed exists, week view stays unchanged.

Repo Placement

OK -- issue filed on ldraney/landscaping-assistant, all file targets are in this repo. Single-repo change, no cross-repo concerns.

Dependencies

  • [x] Issue #33 (Upload tab) -- done column on board. Upload model and controller exist and work.
  • [x] WorkQueueItem model -- exists with work_date, property_id (unique per date), position, completed fields.
  • [x] Property model -- exists with client_name field for fuzzy matching. Has has_many :work_queue_items.
  • [x] Anthropic API key -- confirmed at ~/secrets/anthropic/credentials.env, file present.
  • No blocking items found on the board. No items in in_progress that conflict.

Acceptance Criteria

9 acceptance criteria. All are testable by an agent:
  • AC1-3: Vision API call, structured JSON response, column-overflow handling -- testable via mocked API response in unit tests
  • AC4: Fuzzy matching -- testable with known property fixtures
  • AC5: Confirmation screen with color-coded categories -- testable via request spec checking HTML response
  • AC6: WorkQueueItem creation on confirm -- testable via request spec with confirm action
  • AC7: No new Property records -- testable via assertion on Property.count
  • AC8: Duplicate detection (skip, don't error) -- testable via uniqueness constraint (already exists: validates :property_id, uniqueness: { scope: :work_date } plus DB index)
  • AC9: Error handling for API unavailability or malformed JSON -- testable via mocked error responses, verify no partial items created. Previous [BODY] finding RESOLVED.

Blast Radius

  • WorkQueueItem uniqueness constraint already exists (index_work_queue_items_on_work_date_and_property_id, unique: true) -- AC8 duplicate detection is enforced at DB level. Service should handle ActiveRecord::RecordNotUnique gracefully.
  • Upload model gains week_start field -- existing upload tests should still pass.
  • No existing fuzzy matching or anthropic patterns in the codebase -- net-new functionality.
  • Adding the anthropic gem adds an external API dependency. No other external AI/ML gems exist currently.

Decomposition Assessment

File count: 3 new + 5 modified = 8 files in 1 repo. AC count: 9 acceptance criteria + 5 test expectations. This is substantial but all within one repo and one logical feature. The service object pattern (ScheduleDigester) encapsulates the core complexity. Points: 8 (appropriate for scope). Borderline on the 5-minute rule but tightly coupled -- splitting would create artificial seams. No decomposition needed.

Recommendations

Both previous [BODY] findings have been addressed in the refined issue:
  • [BODY] Confusing "has_many :work_queue_items through digest" language -- RESOLVED. Now reads: "no AR associations to WorkQueueItem; the service creates items directly, the relationship is implicit through week_start date range"
  • [BODY] Missing error handling AC -- RESOLVED. AC9 added with clear criteria. Matching test expectation added.
Remaining project-level items (not blocking this ticket):
  • [SCOPE] Update the schedule-upload user story on project-landscaping-assistant to reflect AI-powered parsing. Current: "Snap a photo of the paper schedule for later reference." Should reflect schedule digestion.
  • [SCOPE] Create architecture note arch-rails-app. Shared gap across multiple board items.
No action needed for this ticket to proceed.