pre torch.compile -chkpoint made

This commit is contained in:
2026-06-16 10:40:38 -05:00
parent 34855eff55
commit 6650d3c5ea
19 changed files with 2519 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
# 01. Extend Types and Add Feedback DB Schema
meta:
id: multi-image-user-feedback-01
feature: multi-image-user-feedback
priority: P0
depends_on: []
tags: [types, schema, database]
objective:
- Update shared TypeScript types to support multi-image requests, species-constrained inference, top-5 combo predictions, and post-evaluation feedback.
- Add a new database table for storing feedback entries.
deliverables:
- `src/lib/types.ts` — updated with new interfaces
- `src/lib/db/schema.ts` — updated with `diagnosisFeedback` table
steps:
1. Add these new types to `src/lib/types.ts`:
- `IdentifyOptions` — optional fields sent in the identify request: `secondImageId?: string`, `userSpecies?: string`, `useForTraining?: boolean`
- `IdentifyRequest` — extend to include `options?: IdentifyOptions`
- `TopPrediction` — a prediction with both species and disease info: `{ speciesName: string, diseaseName: string, diseaseId: string, confidence: ConfidenceResult, rank: number }`
- `IdentifyResponse` — extend to include `topSpeciesDisease?: TopPrediction[]`, `speciesConfidence?: ConfidenceResult`, `infoProvided: string[]` (which optional inputs the user gave)
- `AccuracyRating``"correct" | "incorrect" | "unsure"`
- `DiagnosisFeedback` — full feedback shape: `{ sessionId: string, imageIds: string[], userSpecies?: string, predictedDiseaseId: string, accuracyRating: AccuracyRating, consentToStoreImages: boolean, userCorrectedSpecies?: string, notes?: string, createdAt: string }`
- `FeedbackRequest` — POST body for the feedback endpoint
- `FeedbackResponse` — confirmation response
2. Add a `diagnosisFeedback` table to `src/lib/db/schema.ts`:
- `id` — text primary key (UUID v4)
- `sessionId` — text, session identifier for grouping
- `imageIds` — JSON text array of stored image IDs
- `userSpecies` — text, nullable
- `predictedDiseaseId` — text, the top model prediction
- `accuracyRating` — text enum: `"correct" | "incorrect" | "unsure"`
- `consentToStoreImages` — integer (boolean)
- `userCorrectedSpecies` — text, nullable (only when accuracy=incorrect or unsure)
- `notes` — text, nullable
- `modelVersion` — text, the model version used
- `createdAt` — text, auto timestamp
- Add indexes on `sessionId`, `accuracyRating`, `createdAt`
3. Export `DiagnosisFeedbackRow` and `DiagnosisFeedbackInsert` type helpers.
tests:
- Unit: verify new types compile correctly
- Unit: verify schema migration produces correct table DDL
- Unit: verify INSERT and SELECT on feedback table through Drizzle
acceptance_criteria:
- All new types are exported from `src/lib/types.ts`
- `diagnosisFeedback` table exists in schema with all required columns
- `DiagnosisFeedbackRow` and `DiagnosisFeedbackInsert` are exported
validation:
- `npx tsc --noEmit` passes
- Drizzle Kit (`npx drizzle-kit generate`) produces valid migration SQL
notes:
- The `sessionId` ties together the upload, identify, and feedback flow
- Image storage consent is a boolean to comply with data privacy requirements

View File

@@ -0,0 +1,65 @@
# 02. Multi-Image Ensemble & Species-Constrained Inference
meta:
id: multi-image-user-feedback-02
feature: multi-image-user-feedback
priority: P1
depends_on: [multi-image-user-feedback-01]
tags: [inference, ml]
objective:
- Extend the inference pipeline to support multi-image ensemble inference (averaging features or logits from 2+ images).
- Add species-constrained softmax that renormalizes probabilities over only the disease classes belonging to a known species.
deliverables:
- `src/lib/ml/inference.ts` — updated with ensemble and constrained inference functions
- `src/lib/ml/confidence.ts` — updated with species-aware confidence calibration
steps:
1. In `src/lib/ml/inference.ts`, add:
- `runEnsembleInference(tensors: Float32Array[], topK?: number): Promise<InferenceResult>` — runs multiple images through the model, averages their logits, and returns top-K predictions. Averaging logits (before softmax) is preferred over averaging probabilities since it preserves confidence structure.
- `speciesConstrainedSoftmax(logits: Float32Array, speciesClasses: number[]): Float32Array` — given the full 11,818-class logits and a list of class indices belonging to the user-specified species, compute softmax over only those indices and return a renormalized probability vector (zero everywhere else). The model output dimension (11,818) should be a configurable constant.
- `runSpeciesConstrainedInference(tensor: Float32Array, speciesClassIndices: number[], topK?: number): Promise<InferenceResult>` — run inference then apply species-constrained softmax before extracting top-K.
- `runEnsembleSpeciesConstrained(tensors: Float32Array[], speciesClassIndices: number[], topK?: number): Promise<InferenceResult>` — ensemble then constrain.
2. Export `CLASSIFIER_NUM_CLASSES` constant (11,818) and `SPECIES_CLASS_RANGES` (a map from species name → [startIndex, endIndex] in the model output) from a new constants file or from labels.ts.
3. In `src/lib/ml/confidence.ts`, add:
- `calibrateSpeciesConfidence(rawProb: number, numDiseaseClasses: number): ConfidenceResult` — adjusts calibration factor based on how many disease classes the species has (fewer classes = higher effective confidence).
- `getEnsembleConfidence(predictions: RawPrediction[][]): ConfidenceResult` — aggregate confidence from multiple images.
4. Create `src/lib/ml/species-class-ranges.ts` containing the mapping from species name → [class start index, class end index] in the 11,818-class model output. This is derived from the training dataset's `species_index.json` or `class_hierarchy.json`.
5. Handle edge cases:
- If tensors array is empty → throw
- If tensor length doesn't match expected model input → throw validation error
- If species name not found in `SPECIES_CLASS_RANGES` → fall back to full softmax
tests:
- Unit: test logit averaging with 2 identical tensors → results should be identical to single inference
- Unit: test logit averaging with 2 different tensors → verify averaged output
- Unit: test species-constrained softmax — verify probabilities are zero outside the constrained indices
- Unit: test constrained softmax sums to ~1.0 within the species class range
- Unit: test ensemble + constrained combined pipeline
acceptance_criteria:
- `runEnsembleInference` accepts multiple tensors and returns averaged top-K predictions
- `speciesConstrainedSoftmax` zeros out all classes outside the species range
- `runSpeciesConstrainedInference` and `runEnsembleSpeciesConstrained` produce constrained results
- Confidence calibration accounts for number of disease classes in the species
validation:
- `npx tsc --noEmit` passes
- Unit tests pass with `npx vitest run src/lib/ml/ --reporter=verbose`
notes:
- The current mock model outputs 38 classes. These new functions target the 11,818-class model.
- Until the real model loads, ensemble/constrained functions should still work with mock data (just with fewer classes).
- The species-ranges file should be auto-generated from `data/organized/class_hierarchy.json` and checked into version control.

View File

@@ -0,0 +1,73 @@
# 03. Load Trained Swin-Tiny Model with Species/Disease Routing
meta:
id: multi-image-user-feedback-03
feature: multi-image-user-feedback
priority: P2
depends_on: []
tags: [ml, model-loader, inference]
objective:
- Create a new model loader backend that loads the trained Swin-Tiny checkpoint (`species_final_final.pt`) and routes through the species head and disease heads to produce 11,818-class logits.
- This task requires the PyTorch model to finish training on the Strix Halo machine and must be exported to the correct format before implementation.
deliverables:
- `src/lib/ml/hierarchical-model.ts` — new PlantDiseaseModel implementation for the Swin-Tiny model
- `scripts/export-model.js` — script to export the PyTorch checkpoint to TF.js format
- `public/models/plant-disease-classifier-v2/` — exported model directory (TF.js or ONNX)
steps:
1. Create `scripts/export-model.js`:
- Load the PyTorch checkpoint from `checkpoints/species_final/species_final_final.pt`
- Export to ONNX format with NCHW input shape [1, 3, 224, 224]
- Also export `species_index.json` and `class_hierarchy.json` alongside the model
- Output to `public/models/plant-disease-classifier-v2/`
2. Create `src/lib/ml/hierarchical-model.ts`:
- Implement the `PlantDiseaseModel` interface
- Load the ONNX model via `onnxruntime-node`
- Load species/disease index files
- Implement `predict()`:
- Preprocess to 224×224 (Swin-Tiny input size, not 160)
- Run forward pass → get [1, 768] features → species logits → disease routing
- The model checkpoint is a single forward pass that already produces 11,818 logits from the combined species + disease heads
- Return the full 11,818-dimension logits array
- Implement `getStatus()` returning model metadata with `numClasses: 11818`
3. Update `src/lib/ml/model-loader.ts`:
- Add detection for v2 model directory (`model-v2.json` or similar)
- Try loading v2 model first (if available), fall back to v1 then mock
- Export `MODEL_NUM_CLASSES` constant for use by other modules
- Export `getModelVersion()` to distinguish v1 (38-class) from v2 (11,818-class)
4. Handle edge cases:
- No model checkpoint available → fall back through v1 → mock
- CUDA/ROCm not available for ONNX → use CPU backend
- Model version mismatch → clear error message
tests:
- Integration: export model from checkpoint and verify output shape is [1, 11818]
- Integration: load exported model and run inference on a test image
- Unit: model loader graceful fallback chain (v2 → v1 → mock)
acceptance_criteria:
- Exported model produces 11,818 logits from a 224×224 image
- Model loader loads v2 model when available, falls back gracefully when not
- All existing v1 model consumers continue to work unmodified (via version detection)
validation:
- `node scripts/export-model.js` produces model files
- `npx tsc --noEmit` passes
- POST to `/api/identify` returns predictions (may be limited if species→disease label mapping not yet complete)
notes:
- This task is **blocked on model training completion**. The task file is the implementation spec; actual work begins after `species_final_final.pt` exists.
- The ONNX export path is preferred for server-side inference (no Python runtime needed once exported).
- If ONNX export quality degrades the output, export to TF.js SavedModel format instead.

View File

@@ -0,0 +1,80 @@
# 04. Enhanced API Route for Multi-Image, Species-Aware Identification
meta:
id: multi-image-user-feedback-04
feature: multi-image-user-feedback
priority: P1
depends_on: [multi-image-user-feedback-01, multi-image-user-feedback-02, multi-image-user-feedback-03]
tags: [api, backend]
objective:
- Update the `/api/identify` route to accept optional `options` (secondImageId, userSpecies), run ensemble inference when multiple images provided, apply species-constrained softmax when species is known, and return top-5 species+disease combo predictions with confidence metadata.
deliverables:
- `src/app/api/identify/route.ts` — updated route handler
steps:
1. Update the request parsing to accept `options?: IdentifyOptions` alongside `imageId`:
```typescript
const { imageId, options } = body;
const secondImageId = options?.secondImageId;
const userSpecies = options?.userSpecies;
```
2. Update image loading to support optional second image:
- Load first image tensor (existing logic)
- If `secondImageId` provided, load and preprocess that image too
- Validate both images exist before inference
3. Update inference logic to use the new pipeline:
- If 2 images provided → call `runEnsembleInference(tensors, topK=10)`
- If `userSpecies` provided → get species class range, call `runSpeciesConstrainedInference` or `runEnsembleSpeciesConstrained`
- If 2 images + species → use full ensemble+constrained
- If 1 image + no species → use existing single-inference path (backward compatible)
- Pass the `infoProvided` list to the response (which optional inputs were used)
4. Generate top-5 species+disease combo predictions:
- After enrichment, construct `TopPrediction[]` from the top enriched predictions
- Each entry: `{ speciesName, diseaseName, diseaseId, confidence, rank }`
- Include species confidence when `userSpecies` was provided
5. Add `speciesConfidence` to response:
- When user provides species, compute how much the constraint improves confidence vs unconstrained
- Return both constrained and unconstrained confidence for comparison
6. Handle demo/mock mode:
- When no real model loaded, return mock top-5 combos with appropriate demo_mode flag
- Mock combos should be realistic (use knowledge base to generate plausible species/disease pairs)
tests:
- Integration: single image + no options → existing behavior unchanged
- Integration: single image + species → constrained results, all predictions belong to that species
- Integration: 2 images → ensemble results, confidence should differ from single image
- Integration: 2 images + species → fully constrained ensemble
- Integration: missing secondImageId returns 400 error
- Integration: demo mode returns mock data
acceptance_criteria:
- Existing single-image identify flow works unchanged when `options` omitted
- When secondImageId is provided, inference runs on both images
- When userSpecies is provided, only diseases of that species are returned
- Top-5 species/disease combos are included in response
- Confidence reflects whether 1 or 2 images were used
- 400 error for invalid/missing second image
validation:
- `npx tsc --noEmit` passes
- Existing identify API tests pass: `npx vitest run src/app/api/identify/`
- Manual test with curl sending multi-image request
notes:
- The `infoProvided` array helps the UI show what data was used for the diagnosis
- When userSpecies is given but no model can restrict to that species (e.g., mock mode), fall back to filtering results client-side by the plant name

View File

@@ -0,0 +1,99 @@
# 05. Upload Page with Optional Second Image and Species Selector
meta:
id: multi-image-user-feedback-05
feature: multi-image-user-feedback
priority: P1
depends_on: [multi-image-user-feedback-01]
tags: [ui, frontend, upload]
objective:
- Enhance the upload page to support an optional second image upload and a species search/select field.
- Show live confidence indicators that update as the user provides more information.
deliverables:
- `src/app/upload/page.tsx` — updated upload flow
- `src/components/ImageUpload.tsx` — updated to support multiple uploads
- `src/components/SpeciesSelector.tsx` — new species search/dropdown component
- `src/components/ConfidencePreview.tsx` — new inline confidence indicator
steps:
1. Create `src/components/SpeciesSelector.tsx`:
- Searchable dropdown with all 531 plant species from the knowledge base
- Uses `fuse.js` for fuzzy matching (lightweight, fast client-side search)
- Props: `value: string | null`, `onChange: (species: string | null) => void`, `disabled?: boolean`
- States: empty, searching, selected, clearable
- Shows common name and scientific name in results
- Keyboard-navigable: up/down arrows, enter to select, escape to close
- Mobile-friendly with full-screen overlay on small screens
2. Create `src/components/ConfidencePreview.tsx`:
- Small inline bar showing estimated confidence based on info provided
- Props: `numImages: number`, `speciesProvided: boolean`, `className?: string`
- Dynamics:
- 1 image, no species → "Low confidence — add another photo or identify the plant"
- 1 image + species → "Medium confidence"
- 2 images, no species → "Medium confidence — getting clearer"
- 2 images + species → "High confidence — good data for diagnosis"
- Animated transitions between states
- Uses the same color scheme as ConfidenceBadge (green/amber/red)
3. Update `src/components/ImageUpload.tsx`:
- Change from single-image to multi-image upload flow
- After first successful upload, show a "Add another photo (optional)" button
- Second upload uses the same ImageUpload internals but is secondary in visual weight
- Store both `imageId` responses for the identify request
- Add `uploadedImages: UploadResponse[]` tracking
- Expose method to clear all images
4. Update `src/app/upload/page.tsx`:
- Add state for: firstImageId, secondImageId, selectedSpecies
- Add `SpeciesSelector` component below the upload zone(s)
- Add `ConfidencePreview` component showing live confidence estimate
- Add a "Continue to Diagnosis" button that becomes more prominent as more info is provided
- On submit:
- Build `IdentifyOptions` with secondImageId and/or userSpecies if provided
- Pass `options` in the identify API call (or query params to results page)
- Navigate to `/results/{firstImageId}?options={encodedOptions}`
5. Handle edge cases:
- User adds second image, then removes it → gracefully falls back to single-image
- User selects species, then wants to change it → searchable select supports re-selection
- No species match found → user can type free-form (stored as-is)
- Upload of second image fails → component shows inline error but allows retry without blocking the first image
6. Optimistic UI guidance:
- Add a small info panel below the confidence preview explaining _why_ more info helps
- Text: "Adding a second photo from a different angle helps our AI make a more accurate diagnosis. Identifying the plant species narrows down the possible diseases."
tests:
- Unit: SpeciesSelector renders, searches, selects, clears
- Unit: SpeciesSelector keyboard navigation works
- Unit: ConfidencePreview renders correct messages for each combination
- Unit: ImageUpload supports 2-image flow
- Integration: Full upload flow with 2 images + species → verify all data in request
- A11y: verify aria-labels, roles, keyboard navigation
acceptance_criteria:
- User can upload a second image (optional, after first succeeds)
- User can search and select a plant species from a dropdown
- Confidence preview bar updates dynamically as info is added
- "Continue to Diagnosis" button is prominent once at least 1 image is uploaded
- Navigate to results page with all options encoded
validation:
- `npx tsc --noEmit` passes
- Manual test: upload flow with 0/1/2 images and with/without species
- Responsive test: works on mobile viewport (375px width)
notes:
- The species list comes from the knowledge base (`/api/plants` endpoint)
- `fuse.js` is already lightweight (~15KB gzipped) and can be client-imported
- The options are passed as URL query params to the results page since we navigate before the identify API call

View File

@@ -0,0 +1,94 @@
# 06. Results Dashboard with Dynamic Confidence and Top-5 Display
meta:
id: multi-image-user-feedback-06
feature: multi-image-user-feedback
priority: P1
depends_on: [multi-image-user-feedback-01, multi-image-user-feedback-04, multi-image-user-feedback-05]
tags: [ui, frontend, results]
objective:
- Enhance the results dashboard to display an info panel showing what data the user provided (1/2 images, species) and how it affected confidence.
- Show top-5 species/disease combination predictions as a compact card stack.
- Animate confidence transitions when the user lands on results.
deliverables:
- `src/components/ResultsDashboard.tsx` — updated dashboard
- `src/components/InfoProvidedBanner.tsx` — new component showing what info was used
- `src/components/TopCombinationsCard.tsx` — new component for top-5 species/disease combo list
steps:
1. Create `src/components/InfoProvidedBanner.tsx`:
- Display a banner/panel at the top of results showing:
- Number of images analyzed (1 or 2)
- Whether user identified the plant species (yes/no, with species name if yes)
- Icons/checkmarks for each piece of info
- Show a compact breakdown: "You provided: 📸 2 images · 🌿 Species: Tomato"
- Props: `{ numImages: number, userSpecies?: string | null }`
- Style: subtle background, small text, positioned between page header and results
- Animate in with a fade-slide effect
2. Create `src/components/TopCombinationsCard.tsx`:
- Display the top-5 species/disease combination predictions from the API response
- Each row: rank badge, disease name, plant name, confidence bar
- Clicking a row expands it to show full disease info (reuses DiseaseCard internals)
- Props: `{ predictions: TopPrediction[], onSelect: (diseaseId: string) => void }`
- States:
- Loading: skeleton rows
- Empty: no combinations available
- Error: graceful message
- Populated: ranked list with horizontal confidence bars
- Confidence bar: colored (green/amber/red) horizontal bar with percentage label
- The top-5 is filterable: user can toggle between "All diseases" and "Constrained to your species" (when species was provided)
3. Update `src/components/ResultsDashboard.tsx`:
- Accept new props: `numImages: number`, `infoProvided: string[]`, `userSpecies?: string`, `topCombinations?: TopPrediction[]`
- Add `InfoProvidedBanner` at the top of the results area
- Add `TopCombinationsCard` in the right sidebar (below image preview on desktop)
- When `infoProvided` includes species, show a tag/badge: "Species identified: Tomato" with a lock icon (implying the results are constrained to that species)
- When the response contains a species confidence, show a "How confidence changes with more info" mini-accordion:
- "With 1 image: 65% confidence"
- "With 2 images: 72% confidence"
- "With 2 images + species: 88% confidence"
- This educates the user on the value of providing more info
4. Animate confidence transitions:
- When results load, confidence badges count up from 0 to their final percentage
- Use CSS `@keyframes` for the count-up animation
- Duration: ~600ms with ease-out curve
- Only animate on initial load, not on re-renders
5. Handle edge cases:
- No top combinations (no species match) → show message: "No common patterns found"
- Single image, no species → hide the "how confidence changes" section (nothing to compare)
- Single image with species → show comparison vs without species (estimate)
- Demo mode → show realistic mock combos
tests:
- Unit: InfoProvidedBanner renders correct icons for 1/2 images and species presence
- Unit: TopCombinationsCard renders ranked list and toggles between constrained/all
- Unit: confidence count-up animation triggers on mount
- Integration: full results page with all new sections renders correctly
acceptance_criteria:
- InfoProvidedBanner shows how many images and whether species was identified
- TopCombinationsCard shows top-5 predictions with confidence bars
- Confidence values count up on page load
- When species info is available, a "how confidence changes" section is visible
- All existing results functionality (DiseaseCard, SymptomChecker, etc.) still works
validation:
- `npx tsc --noEmit` passes
- Manual test: navigate to results with 2 images + species → verify all UI sections
- Manual test: navigate with 1 image no species → verify simplified UI
notes:
- The top-5 combos come from the identify API response's `topSpeciesDisease` field
- Confidence comparison values are estimated when the model hasn't been run with/without the constraint — the API provides both constrained and unconstrained confidence

View File

@@ -0,0 +1,111 @@
# 07. Post-Diagnosis Feedback Component (Accuracy / Unsure / Store Consent)
meta:
id: multi-image-user-feedback-07
feature: multi-image-user-feedback
priority: P1
depends_on: [multi-image-user-feedback-01, multi-image-user-feedback-06]
tags: [ui, frontend, feedback]
objective:
- Create a feedback panel that appears after the diagnosis results, asking the user to rate accuracy (✓ / ✗ / ?) and optionally consent to storing their images for model retraining.
deliverables:
- `src/components/PostDiagnosisFeedback.tsx` — new feedback component
- `src/components/ResultsDashboard.tsx` — updated to include feedback panel
steps:
1. Create `src/components/PostDiagnosisFeedback.tsx`:
Component structure (vertically stacked in a card):
```
┌─────────────────────────────────────────────┐
│ 💬 How accurate was this diagnosis? │
│ │
│ [ ✅ Correct ] [ ❌ Incorrect ] [ ❓ Unsure ] │
│ │
│ ── (if Incorrect or Unsure selected) ── │
│ What did you expect? (optional) │
│ [_____________________________] text input │
│ │
│ ──────────────────────────────────────────── │
│ │
│ ☐ Allow us to store these images to │
│ improve future diagnoses? │
│ (Your privacy matters — images stored │
│ securely and never shared) │
│ │
│ [ Submit Feedback ] → sent to /api/feedback │
│ │
│ ───── (after submission) ───── │
│ ✓ Thank you! Your feedback helps us improve. │
└─────────────────────────────────────────────┘
```
Props: `{ sessionId: string, imageIds: string[], predictedDiseaseId: string, userSpecies?: string, modelVersion: string, onSubmit?: () => void }`
States:
- **Pending**: not yet rated, three large buttons (✓/✗/?)
- **Rated**: accuracy selected, showing optional text input + consent checkbox
- **Submitting**: loading spinner on submit button
- **Submitted**: success message with thank-you text
- **Error**: submission failed, retry button
Implementation details:
- Accuracy buttons are large and touch-friendly (min 48px tap target)
- Selected button fills with its color: green (✓), red (✗), amber (?)
- Text input is an optional free-text field for user comments
- Consent checkbox has a brief privacy notice below it
- Submit button disabled until accuracy is rated
- On submit, POST to `/api/feedback` with `DiagnosisFeedback` body
- Animated transitions between states
2. Update `src/components/ResultsDashboard.tsx`:
- Import and render `PostDiagnosisFeedback` at the bottom of the results area
- Pass sessionId (generated from first imageId), imageIds, predictedDiseaseId, userSpecies
- Show feedback panel after all prediction cards
- If no predictions at all, still show feedback (they may want to tell us the model was wrong)
3. Handle edge cases:
- Feedback submission fails → show inline error with retry
- User refreshes page → already-submitted state persists if submission completed (could use sessionStorage)
- Consent unchecked → still submit feedback (just with consent=false)
- No predictions returned → show feedback anyway with "No disease identified" context
tests:
- Unit: all four states render correctly (pending/rated/submitting/submitted)
- Unit: accuracy selection toggles correctly (only one selected at a time)
- Unit: submit button disabled until accuracy is rated
- Unit: consent checkbox unchecked by default
- Unit: text input only shown when accuracy is "incorrect" or "unsure"
- Unit: submission calls /api/feedback with correct payload shape
- Integration: feedback flow from rating to submission to success
acceptance_criteria:
- Three accuracy rating buttons are always visible after results
- Rating is required before submission
- Optional text input appears for "Incorrect" or "Unsure" ratings
- Consent checkbox allows opting in to image storage
- Submit sends correct payload to /api/feedback
- Success message shown after submission
- Error state with retry if submission fails
validation:
- `npx tsc --noEmit` passes
- Manual test: rate accuracy, type notes, toggle consent, submit
- Manual test: verify API receives correct data
- A11y: verify all interactive elements have accessible labels
notes:
- The `sessionId` ties together upload → identify → feedback for the same session
- Privacy notice text should be reviewed for legal compliance
- Consider adding a "Share with the community" option in a future iteration
- Debounce the submit button to prevent double-submission

View File

@@ -0,0 +1,107 @@
# 08. Feedback API Endpoint for Accuracy Ratings and Storage Consent
meta:
id: multi-image-user-feedback-08
feature: multi-image-user-feedback
priority: P1
depends_on: [multi-image-user-feedback-01]
tags: [api, backend, database]
objective:
- Create a POST endpoint at `/api/feedback` that accepts diagnosis feedback submissions (accuracy rating, notes, image storage consent) and persists them to the database.
deliverables:
- `src/app/api/feedback/route.ts` — new API route
- `src/app/api/feedback/feedback.test.ts` — test file
steps:
1. Create `src/app/api/feedback/route.ts`:
Route: `POST /api/feedback`
Accepts JSON body matching `FeedbackRequest`:
```typescript
{
sessionId: string;
imageIds: string[];
userSpecies?: string;
predictedDiseaseId: string;
accuracyRating: "correct" | "incorrect" | "unsure";
consentToStoreImages: boolean;
userCorrectedSpecies?: string;
notes?: string;
}
```
Handler logic:
- Parse and validate request body
- Generate UUID for `id`
- Get `modelVersion` from model loader's `getStatus()`
- Set `createdAt` to current timestamp
- Insert into `diagnosisFeedback` table via Drizzle
- Return `FeedbackResponse`: `{ success: true, id: string }`
- Handle validation errors with 400 status
- Handle DB errors with 500 status
Validation rules:
- `sessionId` — required, non-empty string
- `imageIds` — required, array of non-empty strings, min length 1
- `accuracyRating` — required, must be one of "correct", "incorrect", "unsure"
- `consentToStoreImages` — required, boolean
- `userSpecies` — optional string
- `userCorrectedSpecies` — optional string, only meaningful when accuracy is not "correct"
- `notes` — optional string, max 500 characters (with error message if exceeded)
2. Create `src/lib/api/feedback.ts` — client-side helper:
- `submitFeedback(data: FeedbackRequest): Promise<FeedbackResponse>`
- POST to `/api/feedback` with JSON body
- 15-second timeout
- Handle network errors gracefully
3. Handle edge cases:
- Invalid JSON body → 400 with descriptive error
- Missing required fields → 400 listing missing fields
- Invalid accuracyRating value → 400 with allowed values
- Database unreachable → 500 with error message
- Duplicate sessionId → allowed (user can submit multiple times for different predictions)
4. CORS and caching:
- Add `Cache-Control: no-store` header
- No authentication required (public endpoint for feedback)
tests:
- Unit: valid feedback submission returns 200 with success
- Unit: missing required fields return 400
- Unit: invalid accuracyRating returns 400
- Unit: notes over 500 chars returns 400
- Unit: empty imageIds array returns 400
- Unit: client helper `submitFeedback()` makes correct fetch call
- Unit: client helper handles network error gracefully
- Integration: submit feedback and verify it exists in database
acceptance_criteria:
- POST /api/feedback accepts valid feedback and stores it
- Invalid requests return appropriate 400 errors with descriptive messages
- Database stores all fields correctly
- Client helper function is usable from any feedback component
- Endpoint returns `{ success: true, id }` on success
validation:
- `npx tsc --noEmit` passes
- Unit tests pass: `npx vitest run src/app/api/feedback/`
- Manual test: `curl -X POST http://localhost:3000/api/feedback -H 'Content-Type: application/json' -d '{"sessionId":"test","imageIds":["img1"],"predictedDiseaseId":"early-blight","accuracyRating":"correct","consentToStoreImages":false}'`
- Verify stored data with direct DB query
notes:
- No auth needed for MVP — feedback is public and anonymous
- imageIds reference images in the uploads directory; no automatic cleanup
- A future task could add a review/admin dashboard for browsing feedback entries
- Rate limiting could be added later if needed (by sessionId or IP)

View File

@@ -0,0 +1,40 @@
# Multi-Image Upload & User Feedback
Objective: Allow users to upload a second optional image, manually identify the plant species, see dynamic confidence updates, top-5 predictions, and provide post-diagnosis feedback (accuracy rating + storage consent).
Status legend: [ ] todo, [~] in-progress, [x] done
## Tasks
- [ ] 01 — Extend types and add feedback DB schema → `01-api-types-and-schema.md`
- [ ] 02 — Multi-image ensemble & species-constrained inference → `02-multi-image-inference-pipeline.md`
- [ ] 03 — Load trained Swin-Tiny model with species/disease routing → `03-hierarchical-model-loader.md`
- [ ] 04 — Enhanced API route for multi-image, species-aware identification → `04-enhanced-identify-api-route.md`
- [ ] 05 — Upload page with optional second image and species selector → `05-upload-page-second-image-species.md`
- [ ] 06 — Results dashboard with dynamic confidence and top-5 display → `06-dynamic-results-dashboard.md`
- [ ] 07 — Post-diagnosis feedback component (accuracy / unsure / store consent) → `07-post-evaluation-feedback-component.md`
- [ ] 08 — Feedback API endpoint for accuracy ratings and storage consent → `08-feedback-api-endpoint.md`
## Dependencies
```
01 ← 02 (types needed by inference)
01 ← 04 (types needed by API route)
01 ← 05 (types needed by upload page)
01 ← 06 (types needed by results dashboard)
01 ← 07 (types needed by feedback component)
01 ← 08 (schema needed by feedback API)
02 ← 04 (inference pipeline needed by API route)
03 ← 04 (model loader needed by API route)
05 ← 06 (upload outcomes fed into results)
06 ← 07 (results shown before post-eval)
04 ← 06 (API response needed by results dashboard)
```
## Exit Criteria
- User can optionally upload a second image and/or enter a species name
- Confidence scores dynamically reflect the amount of information provided
- Top-5 species/disease combo predictions are displayed
- After diagnosis, user can rate accuracy (✓/✗/?) and opt in to image storage for training
- Feedback is persisted to the database