I now have complete results for all tested endpoints. Here is the comprehensive report:

---

## COMPLETE LIS BACKEND API TEST RESULTS

### ALL ENUMS AND TRANSITIONS

**ResultStatus**: `pending` -> `entered` -> `validated` -> `approved` -> `released`
- `return`: goes to previous status (released->approved, approved->validated, validated->entered, entered->pending)
- `reset`: only from `pending` or `entered` -> resets to `pending`, clears value + entered_by/at

**SampleStatus**: `pending` -> `collected` -> `delivered` -> `ready` -> `in_progress` -> `completed` -> `ready_for_result`
- Also: `received` (backward compat alias for delivered), `rejected`, `sent_out`
- Reject allowed from: collected, delivered, received
- Accept (delivered->ready) allowed from: delivered, received

**RequestStatus**: `pending` -> `sample_collected` -> `in_progress` -> `partial_result` -> `completed` | `cancelled`
- Cancel only from: pending, sample_collected

**InvestigationStatus**: `pending` -> `collected` -> `received` -> `in_progress` -> `work_completed` -> `results_added` -> `reviewed` -> `approved` -> `published`
- Also: `rejected` (from received or in_progress)
- Published and Rejected are terminal states

**ProcessingStatus (Kanban)**: `received` -> `in_processing` -> `completed`

**ReferralStatus**: `pending` -> `sent` -> `in_progress` -> `resulted` -> `completed` | `cancelled`

---

### GROUP A: Results Workflow

| # | Endpoint | Status | Works? | Notes |
|---|----------|--------|--------|-------|
| A1 | `GET /lis/results?lab_section_id=1` | 200 | YES | Returns results with investigation + patient. Does NOT include request or sample objects directly (only IDs). `per_page` ignored -- always 25. |
| A2 | `POST /lis/results/{id}/enter` | 200 | YES | Body: `{"result_value": "10"}`. Optional: `comment`. Triggers delta check, formula calculations, abnormal flag. |
| A3 | `POST /lis/results/{id}/validate` | 200 | YES | No body needed. Moves entered->validated. |
| A4 | `POST /lis/results/{id}/approve` | 200 | YES | No body needed. Moves validated->approved. |
| A5 | `POST /lis/results/{id}/release` | 200 | YES | No body needed. Moves approved->released. Fires LabResultReleased event. |
| A6 | `POST /lis/results/{id}/return` | 200 | YES | Body: `{"reason": "..."}`. Goes to previous status. Keeps the result value. |
| A7 | `POST /lis/results/{id}/reset` | 200 | PARTIAL | Body: `{"reason": "..."}`. Only works from pending or entered. Clears value, entered_by, entered_at. Returns 422 if validated/approved/released. |
| A8 | `POST /lis/results/bulk-release` | 405 | MISSING | Route does NOT exist. The POST is intercepted by `results/{result}` show route. No bulk operations on results. |

---

### GROUP B: Sample Workflow

| # | Endpoint | Status | Works? | Notes |
|---|----------|--------|--------|-------|
| B1 | `POST /lis/samples` | 201 | YES | Requires: `lab_request_id`, `patient_id`. `auto_split` with `investigation_ids` splits internal vs outsourced. Without `investigation_ids`, auto_split does nothing. |
| B2 | `POST /lis/samples/{id}/collect` | 200 | YES | pending->collected. Fires LabSampleCollected event (updates request status). |
| B3 | `POST /lis/samples/{id}/deliver` | 200 | YES | collected->delivered. **TRIGGERS 3 THINGS**: 1) Auto-aliquoting by section (creates child samples), 2) Result creation for all non-outsourced investigations, 3) Kanban entry creation. |
| B4 | `POST /lis/samples/{id}/reject` | 200 | YES | Body: `{"reason": "..."}`. Marks rejected. Does NOT create replacement sample. Parent rejection cascades to children. |
| B5 | `POST /lis/samples/{id}/aliquot` | 422 | CONDITIONAL | Only works if not already aliquoted. Returns "already aliquoted" if children exist. Requires multi-section investigations. |
| B-extra | `POST /lis/samples/{id}/accept` | 200 | YES | delivered->ready |
| B-extra | `POST /lis/samples/{id}/start-processing` | 200 | YES | ready->in_progress |
| B-extra | `POST /lis/samples/{id}/complete-processing` | 200 | YES | in_progress->completed |
| B-extra | `POST /lis/samples/{id}/ready-for-result` | 200 | YES | completed->ready_for_result |
| B-extra | `POST /lis/samples/{id}/receive` | deprecated | ALIAS | Calls deliver() internally |

---

### GROUP C: Kanban

| # | Endpoint | Status | Works? | Notes |
|---|----------|--------|--------|-------|
| C1 | `GET /lis/sections/{id}/kanban` | 200 | YES | Returns `{received: [], in_processing: [], completed: []}`. Each item has: id, sample_id, sample, section_id, investigations (with status + allowed_transitions + panel_members), processing_status, assigned_to, sort_order. Does NOT include results. |
| C2 | `POST /lis/sections/{id}/kanban/{id}/start` | 200 | YES | received->in_processing |
| C3 | `POST /lis/sections/{id}/kanban/{id}/complete` | 200 | YES | in_processing->completed |
| C4 | `POST /lis/sections/{id}/kanban/{id}/reject` | 200 | YES | Body: `{"investigation_ids": [1], "rejection_reason": "..."}`. Field is `rejection_reason` NOT `reason`. Returns message not data object. |

---

### GROUP D: Request Operations

| # | Endpoint | Status | Works? | Notes |
|---|----------|--------|--------|-------|
| D1 | `POST /lis/requests/{id}/investigations` | 200 | YES | Body: `{"investigation_id": 50}`. Adds to request. Returns full request with updated investigations. |
| D2 | `DELETE /lis/requests/{id}/investigations/{invId}` | 200 | YES | Removes investigation from request. |
| D3 | `POST /lis/requests/{id}/cancel` | 200 | YES | Body: `{"reason": "..."}`. Only works if status is pending or sample_collected. |
| D4 | `GET /lis/request-investigations` | 200 | YES | Lists all with: status, allowed_transitions, prices, rejection info. |
| D4b | `POST /lis/request-investigations/{id}/transition` | 200 | YES | Body: `{"status": "in_progress"}`. Follows InvestigationStatus transitions. |
| D4c | `POST /lis/request-investigations/bulk-transition` | 200 | YES | Body: `{"investigation_ids": [31,32], "status": "in_progress"}`. Field is `investigation_ids` not `request_investigation_ids`. |

---

### GROUP E: Missing Endpoints (Backend Code Analysis)

| Feature | Exists? | Notes |
|---------|---------|-------|
| **Retract** (pull back submitted result) | NO | Not in routes or controllers. Use `return` to step back one status at a time. |
| **Invalidate** (invalidate published report) | NO | No route. Published is terminal in InvestigationStatus. |
| **Retest** (create new copy for re-testing) | NO | No route. Must reject via kanban + create new sample manually. |
| **Individual test cancellation** | PARTIAL | Use `request-investigations/{id}/transition` with `status: rejected`. Only from `received` or `in_progress`. |
| **Submit** (separate from enter) | NO | There is only `enter`. The flow is: pending -> enter -> validate -> approve -> release. No separate "submit" step. |
| **Bulk release** | NO | No bulk-release endpoint exists. Must release results one at a time. |
| **Generate missing results** | YES | `POST /lis/requests/{id}/generate-missing-results` -- fills in results that should exist but don't. |
| **Force delete request** | YES | `DELETE /lis/requests/{id}/force` -- hard deletes everything. |
| **Cleanup orphans** | YES | `POST /lis/requests/cleanup-orphans` -- removes orphaned records. |
| **Dev reset data** | YES | `POST /lis/dev/reset-data` -- presumably wipes test data. |

---

### GROUP F: Duplicate Results Analysis

**Request 1 has 31 results across 16 investigations** -- many duplicates.

**Root cause**: Request 1 has **2 parent samples** (sample 1 and sample 2). When each parent was delivered, the `GenerateResultsOnSampleReceived` listener created results for ALL investigations on the request, keyed by `(lab_request_id, lab_request_investigation_id, sample_id)`. Since each parent produces results with a different `sample_id`, the unique constraint passes and duplicates are created.

The listener checks: `where lab_request_id + lab_request_investigation_id + sample_id` -- so the same investigation gets a result per sample. This is technically correct for a multi-sample scenario but creates operational confusion because both sample 1 and sample 2 cover the same investigations.

**Example duplicates**:
- CBC (inv 1): Result 1 (sample 1, pending) + Result 17 (sample 2, entered)
- HGB (inv 2): Result 2 (sample 1, released) + Result 18 (sample 2, entered)

**The fix is operational**: don't create multiple parent samples for the same request unless they cover different investigations.

---

### Additional Endpoints Tested

| Endpoint | Status | Works? |
|----------|--------|--------|
| `POST /lis/results/{id}/auto-verify` | 200 | YES (returns rules evaluated) |
| `POST /lis/results/{id}/send-out` | 201 | YES (creates external sample + referral) |
| `POST /lis/samples/{id}/send-out` | 201 | YES (creates external child sample) |
| `GET /lis/referrals` | 200 | YES |
| `POST /lis/referrals/{id}/receive-results` | 200 | YES (body needs `referral_test_id` not `investigation_id`) |
| `POST /lis/referrals/{id}/complete` | 200 | YES (only from `resulted` status) |
| `POST /lis/referrals/{id}/cancel` | 200 | YES |
| `POST /lis/requests/{id}/apply-insurance` | 200 | YES |
| `GET /lis/result-reports/request/{id}/pdf` | 200 | YES (returns actual PDF) |
| `GET /lis/result-reports/result/{id}/pdf` | 200 | YES |
| `POST /lis/result-reports/publish` | 200 | YES (body: `request_ids` array + `channels`) |
| `GET /lis/result-reports/request/{id}/publish-history` | 200 | YES |
| All 12 report endpoints | 200 | YES (params: `from` and `to`, not `date_from`/`date_to`) |

---

### KEY FINDINGS FOR FRONTEND REBUILD

1. **No bulk-release** -- must release results individually (or build frontend batching)
2. **No retract/invalidate/retest** -- use `return` (step-back) and `reset` (clear value) instead
3. **Result `show` includes more data** than list (sample, labRequest loaded)
4. **Kanban does NOT include results** -- only investigations with their statuses
5. **Kanban reject field** is `rejection_reason` not `reason`
6. **Report params** use `from`/`to` not `date_from`/`date_to`
7. **Referral receive-results** needs `referral_test_id` not `investigation_id`
8. **Publish** needs `request_ids` (array) and will fail if not all results released
9. **Sample delivery is the most important event** -- triggers aliquoting + result creation + kanban entries
10. **Investigation transitions** are separate from result transitions -- the `request-investigations` endpoint manages the investigation workflow, while `results` endpoint manages result entry/approval
11. **Duplicate results** happen when multiple parent samples exist for the same request -- each deliver creates results for all investigations