311 lines
24 KiB
Markdown
311 lines
24 KiB
Markdown
# Suggestions — Workflow & Lifecycle
|
|
|
|
> Source of truth: `apps/feedback/`. Suggestions are implemented as `FeedbackType.SUGGESTION` on the shared `Feedback` model (`apps/feedback/models.py:24`). There is **no dedicated model, status set, or workflow of its own** — every lifecycle claim here applies to all `FeedbackType` values (COMPLIMENT, SUGGESTION, GENERAL, INQUIRY, SATISFACTION_CHECK). Suggestion-specific behavior is called out where it exists.
|
|
> This document describes the **current implementation only**. Every claim is cited as `file:line`.
|
|
|
|
---
|
|
|
|
## 1. Purpose
|
|
|
|
The Suggestions module captures ideas/recommendations from patients, staff, and external sources and routes them through a lightweight triage-and-acknowledge workflow owned centrally by the Patient Experience (PX) team.
|
|
|
|
- `FeedbackType.SUGGESTION = "suggestion"` (`models.py:24`).
|
|
- The `Feedback` model docstring (`models.py:4-9`): "Tracks patient feedback (compliments, suggestions, general feedback)" and "Manages feedback workflow (submitted → reviewed → acknowledged → closed)".
|
|
- The word "Suggestion" is a **naming convention only** throughout the UI layer — the underlying record is a generic `Feedback`. E.g. `templates/feedback/feedback_detail.html:4` ("Suggestion Detail"); every audit event is named `suggestion_*` (`suggestion_created` `views.py:377`, `suggestion_updated` `views.py:437`, `suggestion_deleted` `views.py:494`, `public_suggestion_submitted` `views.py:1020`).
|
|
|
|
---
|
|
|
|
## 2. How a Case Starts
|
|
|
|
**Four distinct creation channels**, all producing a `Feedback` row with `feedback_type=SUGGESTION`, `status=SUBMITTED`, no `assigned_to`/`department`.
|
|
|
|
### 2.1 Channel A — Public Suggestion Form (unauthenticated)
|
|
- View: `public_suggestion_submit` (`views.py:937-1029`); `@csrf_exempt` + POST-only — **deliberately NOT `@login_required`** (`views.py:937`).
|
|
- URL: `path("public/suggestion/", ..., name="public_suggestion_submit")` (`urls.py:36`).
|
|
- Form: `PublicSuggestionForm` (`forms.py:251`) — a plain `forms.Form`.
|
|
- **Rate limiting:** per-IP cap of 5 submissions / 300s → HTTP 429 (`views.py:944`).
|
|
- Accepts JSON and form-encoded (`views.py:951`).
|
|
- **Required:** `contact_name`, `contact_phone`, `message`, `hospital` (`views.py:966`). Optional: `contact_email`, `category`/Area (defaults to `OTHER`, `views.py:977`), `title` (defaults to `message[:100]`).
|
|
|
|
Post-save (`views.py:992-1024`): `patient=None` at creation; linked **asynchronously** by `link_feedback_patient.delay(id, phone)` (`views.py:1015`); `analyze_suggestion_with_ai.delay(id)` (`views.py:1013`); `notify_staff_new_item.delay("suggestion", id)` (`views.py:1014`); audit `public_suggestion_submitted` (`views.py:1019`). Returns short pseudo-reference `"SG-{uuid[:8]}"` (`views.py:1026`) — **not** the internal `SGT-...` reference; the public cannot track by reference.
|
|
|
|
### 2.2 Channel B — Internal Staff Form (authenticated)
|
|
- View: `feedback_create` (`views.py:293-405`); `@login_required`. URL `feedback_create` (`urls.py:16`).
|
|
- **Does not use `FeedbackForm`** — builds the `Feedback` directly from POST (`views.py:311`), always `feedback_type=SUGGESTION` (`views.py:343`) — i.e. this view is hard-coded to create suggestions only.
|
|
- **Required:** `contact_name`, `contact_phone`, `message`, `hospital` (`views.py:318-326`). Optional: `title`.
|
|
- Post-save: **synchronous** `find_or_link_patient` (`views.py:336`); `analyze_suggestion_with_ai.delay` (`views.py:365`); `notify_staff_new_item.delay` (`views.py:366`); internal `FeedbackResponse` "Suggestion submitted by {user}" (`views.py:368`); audit `suggestion_created` (`views.py:376`).
|
|
|
|
### 2.3 Channel C — External API (machine-to-machine)
|
|
- View: `ExternalSuggestionCreateView` (`apps/integrations/api_views.py:700`), `POST /api/v1/external/suggestions/`.
|
|
- Auth via API key; hospital must match scope. Returns the canonical `reference_number` generated by `save()` (`SGT-YYYYMM-NNNN`); the same value is used to retrieve the record via `GET /api/v1/external/suggestions/<reference_number>/`. Same post-save tasks (`api_views.py:765`).
|
|
|
|
### 2.4 Channel D — PX Source-User Portal
|
|
- View: `source_user_create_suggestion` (`apps/px_sources/ui_views.py:1387`); `@login_required`; guarded by `source_user.can_create_suggestions`.
|
|
- **Uses `PublicSuggestionForm`** (`ui_views.py:1400`). Sets `source=source`, `metadata={"created_via":"source_user_portal"}`. Fires **only** `analyze_suggestion_with_ai` (notably does **NOT** call `notify_staff_new_item`).
|
|
|
|
### 2.5 What happens immediately after submission (all channels)
|
|
1. **`Feedback.save()` override** (`models.py:261`): if `reference_number` blank → `generate_reference("SGT", hospital)` → `SGT-YYYYMM-NNNN` (global monthly sequence, `apps/core/reference.py:32`). Comment on field (`models.py:168`): *"internal-only, not publicly trackable"*.
|
|
2. Default `status = SUBMITTED` (`models.py:196`).
|
|
3. **No Django signals** — there is no `apps/feedback/signals.py` and `apps.py` registers no `ready()` handler. All side-effects are triggered explicitly by views/tasks.
|
|
|
|
> **⚠ Gap:** `FeedbackForm` (`forms.py:13`) exists but is used **only by `feedback_update`** (`views.py:421`), not creation. `feedback_create` builds the object manually, bypassing the form's `clean()` validation.
|
|
|
|
### 2.6 Are `CommentImport` / `PatientComment` bulk-import channels? **No.**
|
|
`CommentImport` (`models.py:479`) and `PatientComment` (`models.py:534`) describe a **separate, parallel workflow** (monthly IT exports of raw comments; "Steps 0-5"). `PatientComment.suggestions` (`models.py:600`) is just free-text extracted from a comment — it does **not** create a `Feedback` row. No code path creates `CommentImport` records (admin-only). See §16.
|
|
|
|
---
|
|
|
|
## 3. Complete Lifecycle
|
|
|
|
### 3.1 `FeedbackStatus` enum (`models.py:30-37`)
|
|
`SUBMITTED`, `REVIEWED`, `ACKNOWLEDGED`, `CLOSED`, `REOPENED`.
|
|
|
|
### 3.2 `VALID_FEEDBACK_TRANSITIONS` (`models.py:40-46`) — verified exact
|
|
```
|
|
SUBMITTED → [REVIEWED, CLOSED]
|
|
REVIEWED → [ACKNOWLEDGED, CLOSED]
|
|
ACKNOWLEDGED → [CLOSED]
|
|
CLOSED → [REOPENED]
|
|
REOPENED → [REVIEWED, ACKNOWLEDGED, CLOSED]
|
|
```
|
|
|
|
### 3.3 Enforcement layers
|
|
- **Model:** NOT enforced — no `clean()` on `Feedback`.
|
|
- **DB:** NOT enforced — no `CheckConstraint` in `Meta` or migrations.
|
|
- **View:** enforced **only** in `feedback_change_status` (`views.py:776-859`): rejects no-op same-status (`views.py:807`); computes `allowed = VALID_FEEDBACK_TRANSITIONS.get(old_status, [])` and rejects `new_status not in allowed` (`views.py:811-817`).
|
|
- **Admin:** NOT enforced.
|
|
|
|
> **Implication:** transitions are **soft**. PX Admin editing via Django admin, or any direct ORM write, can move a suggestion to any status. Only the staff UI enforces the rules.
|
|
|
|
---
|
|
|
|
## 4. Status Definitions
|
|
|
|
| Status | Purpose | When entered | Who moves forward | Next |
|
|
|---|---|---|---|---|
|
|
| **SUBMITTED** (`models.py:33`) | Initial entry for every suggestion from every channel. UI badge: blue. | At `save()` on creation (`models.py:197`) | Any PX/hospital user with change-status perm (§5) | REVIEWED, CLOSED |
|
|
| **REVIEWED** (`models.py:34`) | PX staffer has triaged/read. Sets `reviewed_at`/`reviewed_by` (`views.py:821`). UI badge: orange. | `feedback_change_status` choosing `reviewed` | Same perm group | ACKNOWLEDGED, CLOSED |
|
|
| **ACKNOWLEDGED** (`models.py:35`) | PX formally acknowledged/accepted. Sets `acknowledged_at`/`acknowledged_by` (`views.py:824`). UI badge: green. | `feedback_change_status` choosing `acknowledged` | Same | CLOSED |
|
|
| **CLOSED** (`models.py:36`) | Terminal. Sets `closed_at`/`closed_by` (`views.py:827`). Reachable from SUBMITTED, REVIEWED, ACKNOWLEDGED, REOPENED. UI badge: slate. When closed, Quick Actions + Edit hidden (`feedback_detail.html:51,299`). | `feedback_change_status` | Same | REOPENED |
|
|
| **REOPENED** (`models.py:37`) | Closed suggestion re-opened. **Clears** `closed_at`/`closed_by` (`views.py:830`); does NOT clear `acknowledged_*`/`reviewed_*`. | `feedback_change_status` | Same | REVIEWED, ACKNOWLEDGED, CLOSED |
|
|
|
|
> **Note on ACKNOWLEDGED:** a `FeedbackResponse` with `response_type="acknowledgment"` is valid (`models.py:372`) and exposed by `FeedbackResponseForm` (`forms.py:121`), but `feedback_change_status` does **not** require a response to exist before allowing ACKNOWLEDGED. No programmatic coupling — acknowledgement is a status change; the optional acknowledgment response is an independent timeline entry.
|
|
|
|
---
|
|
|
|
## 5. Workflow Actions
|
|
|
|
### 5.1 List & Detail (read)
|
|
| Action | View | URL name | Permission |
|
|
|---|---|---|---|
|
|
| List | `feedback_list` (`views.py:37`) | `feedback_list` (`urls.py:13`) | `@login_required`; RBAC filtering (`views.py:59`): PX Admin all/hospital-filtered; Hospital Admin → own; Dept Manager → own dept; else own hospital |
|
|
| Detail | `feedback_detail` (`views.py:194`) | `feedback_detail` (`urls.py:14`) | `@login_required`; access checks (`views.py:213`) |
|
|
|
|
Detail computes a workflow stepper: Submitted → Assigned → Reviewed → Acknowledged → Closed (`views.py:278`) — advisory only, not authoritative.
|
|
|
|
### 5.2 Status change — `feedback_change_status` (`views.py:776`)
|
|
URL `feedback_change_status` (`urls.py:21`); POST-only. Permission (`views.py:783`): `is_px_admin OR is_hospital_admin OR is_px_management OR is_px_employee`. Logic: validate status value; reject no-op; enforce `VALID_FEEDBACK_TRANSITIONS`; apply side-effects (REVIEWED→`reviewed_at/by`, ACKNOWLEDGED→`acknowledged_*`, CLOSED→`closed_*`, REOPENED→clear `closed_*`); create `FeedbackResponse(response_type="status_change")`; audit. Form: `FeedbackStatusChangeForm` (`forms.py:219`) — lists all statuses without filtering (filtering in view).
|
|
|
|
### 5.3 Assign — `feedback_assign` (`views.py:721`)
|
|
URL `feedback_assign` (`urls.py:20`). Permission: PX/Hosp-Admin/PX-Mgmt/PX-Employee. Sets `assigned_to`/`assigned_at`; creates `FeedbackResponse("assignment")`; audit. Form: `FeedbackAssignForm` (`forms.py:238`). Candidate set: `get_assignable_users(hospital)` = active users in group **"PX Employee"** at the suggestion's hospital. No status restriction in the view (UI panel hidden when closed).
|
|
|
|
### 5.4 Add Response / Timeline — `feedback_add_response` (`views.py:862`)
|
|
URL `feedback_add_response` (`urls.py:24`). Permission: PX/Hosp-Admin/PX-Mgmt/Department-Manager. Creates `FeedbackResponse` with caller-supplied `response_type` (status_change/assignment/note/response/acknowledgment). No status restriction.
|
|
|
|
### 5.5 Send to Department — `feedback_send_to_department` (awareness-only)
|
|
URL `feedback_send_to_department` (`urls.py:22`). Permission: PX/Hosp-Admin/PX-Mgmt/PX-Employee. **Precondition:** `feedback.department` already set (`views.py:1104`). Emails `department.champion`/`manager`/`deputy_manager` with **"This is for your awareness. No response is required."** (`views.py:1126`). Creates `is_internal=False` note. **Does NOT change status, does NOT create a department-response workflow.** See §14.
|
|
|
|
### 5.6 Send-to (AJAX) — `feedback_send_to`
|
|
URL `feedback_send_to` (`urls.py:23`). Permission: PX/Hosp-Admin/PX-Mgmt/PX-Employee. Two modes: `recipient_type="person"` (assigns to a User + emails) or `"department"` (links `feedback.department` + emails/SMSs champion+manager via `get_champion_and_manager`; SMS says *"No response is required"*). Rejects dept with no champion/manager.
|
|
|
|
### 5.7 Toggle flags
|
|
| Action | View | Permission | Effect |
|
|
|---|---|---|---|
|
|
| Toggle featured | `feedback_toggle_featured` (`views.py:895`) | PX/Hosp-Admin | flips `is_featured` (`models.py:225`) |
|
|
| Toggle follow-up | `feedback_toggle_follow_up` (`views.py:916`) | PX/Hosp-Admin | flips `requires_follow_up` (`models.py:227`) |
|
|
|
|
### 5.8 Create PX Action — `feedback_create_action`
|
|
URL `feedback_create_action` (`urls.py:28`). Permission: PX/Hosp-Admin. Creates a `PXAction` (generic FK to Feedback), `source_type="suggestion"`, `status="open"`. **One-way spawn** — does not change Feedback status; "promote this suggestion into a tracked improvement action".
|
|
|
|
### 5.9 Attachments
|
|
`FeedbackAttachment` (`models.py:332`): no dedicated view — listed on detail (`views.py:230`) but creatable **only** via Django admin or programmatically.
|
|
|
|
### 5.10 CRUD
|
|
Create (`feedback_create` `views.py:293`); Update (`feedback_update` `views.py:408` — PX/Hosp-Admin only); Delete-soft (`feedback_delete` `views.py:476` — PX/Hosp-Admin only; `feedback.soft_delete` sets `is_deleted`/`deleted_at`/`deleted_by`).
|
|
|
|
---
|
|
|
|
## 6. Decision Points
|
|
|
|
The only **enforced** decision point is the transition gate inside `feedback_change_status` (`views.py:811`): *is `new_status` in `VALID_FEEDBACK_TRANSITIONS[old_status]`?*
|
|
|
|
Beyond that, **every other "decision" is operator judgement** — the code does not branch:
|
|
- "Needs review" — no predicate; SUBMITTED→REVIEWED or SUBMITTED→CLOSED directly.
|
|
- "Needs response before acknowledge" — **not enforced**.
|
|
- "Can be acknowledged directly" — only from REVIEWED or REOPENED; SUBMITTED→ACKNOWLEDGED forbidden.
|
|
- "Close vs reopen" — operator choice.
|
|
|
|
> **⚠ Gap — reopen unreachable via UI after closure:** the Quick Actions panel (containing the status-change form) is hidden when `status == 'closed'` (`feedback_detail.html:299`). The map allows CLOSED→REOPENED but no staff UI button offers it. Reopen is only possible via Django admin or direct ORM.
|
|
|
|
---
|
|
|
|
## 7. Assignment Flow
|
|
|
|
- Field: `Feedback.assigned_to` (`models.py:201`); `assigned_at` (`models.py:204`).
|
|
- **Initial owner:** `None` for every channel.
|
|
- **Reassignment:** `feedback_assign` overwrites each call — no history field; history preserved only via `FeedbackResponse("assignment")` entries.
|
|
- **Candidate set:** `get_assignable_users(hospital)` = active users in **"PX Employee"** group. Suggestions are assigned **to PX staff only** — never to a department manager/champion via this form.
|
|
- **Department routing:** `Feedback.department` (`models.py:148`) is **not auto-assigned** by any creation channel and is **not used for ownership/routing**. Populated only when a PX user manually sends the suggestion to a department — and even then it's for **notification only**, not ownership transfer (§14).
|
|
|
|
---
|
|
|
|
## 8. Investigation Process
|
|
|
|
**No investigation phase.** Compared to complaints/inquiries/observations there is no RCA-required state, no evidence-gathering, no explanation request, no SLA-driven investigation timer.
|
|
|
|
Closest analogues:
|
|
- **AI pre-analysis** (`analyze_suggestion_with_ai`, `tasks.py:184`): async after creation. Calls `AIService.analyze_suggestion(message, title)` (`apps/core/ai_service.py:2124`) and **overwrites** `category`/`priority`, optionally `title`, stores analysis under `metadata["ai_analysis"]` (`tasks.py:219`).
|
|
- **Optional RCA linkage:** `feedback_detail` (`views.py:236`) fetches `RootCauseAnalysis` objects; template has a (commented-out) "Initiate RCA" link. An RCA *can* be attached but is **not part of the suggestion workflow**.
|
|
|
|
### `CommentActionPlan` — a SEPARATE action-tracking sub-flow (NOT on Feedback)
|
|
`CommentActionPlan` (`models.py:660`) attaches to `PatientComment` (IT-export pipeline), **not** `Feedback`. It has its own status enum `CommentActionPlanStatus` (PENDING/ON_PROCESS/COMPLETED, `models.py:654`) and `timeframe`/`evidences`/`responsible_department` fields. Unrelated to the live suggestion lifecycle (§16).
|
|
|
|
---
|
|
|
|
## 9. Communication Flow
|
|
|
|
| Event | Channel | Where |
|
|
|---|---|---|
|
|
| Acknowledgement | none automatic — operator may add `response_type="acknowledgment"` `FeedbackResponse` | `feedback_add_response` |
|
|
| Response to patient | `FeedbackResponse("response", is_internal=False)` | `feedback_add_response` |
|
|
| Closure | no outbound message — only internal timeline note | `feedback_change_status` |
|
|
|
|
**Crucially, no email/SMS is ever sent to the suggester (the patient/contact) by the feedback app.** All notifications go **inbound to PX/department staff**:
|
|
- On creation: `notify_staff_new_item("suggestion", id)` emails/SMSs **PX Admins** (working-hours: all; after-hours: on-call) — `complaints/tasks.py:2489`; config block `complaints/tasks.py:2548`. Template `emails/new_suggestion_notification.html`.
|
|
- On send-to-department: emails champion/manager/deputy (`views.py:1113`) or champion+manager (`views.py:1255`) with explicit "No response is required".
|
|
|
|
So the suggester receives **only** the immediate `{"reference":"SG-..."}` JSON from the public endpoint (`views.py:1026`) and nothing further unless a PX operator manually contacts them outside the system. **Suggestions are inbound-only communication.**
|
|
|
|
---
|
|
|
|
## 10. Escalation Flow
|
|
|
|
**None.** Confirmed:
|
|
- `Feedback` has **no SLA fields** (no `due_date`, `sla_deadline`, `escalated_at`).
|
|
- Celery beat (`config/celery.py`) has SLA/escalation tasks for complaints, inquiries, observations — **but nothing for feedback/suggestions**.
|
|
- The only feedback-related beat entry is `analyze-feedback-sentiment` → `process_pending_sentiment_analysis` every 30 min (`config/celery.py:201`) — analytics, not escalation.
|
|
- Grep for `feedback.*sla|feedback.*escalat` returned **no matches**.
|
|
|
|
`AuditEvent.EVENT_TYPES` includes `"escalation"`/`"sla_breach"` (`apps/core/models.py:89`) but these are never emitted by the feedback app.
|
|
|
|
---
|
|
|
|
## 11. Resolution Process
|
|
|
|
Suggestions have no formal "resolution" — the resolution-equivalent statuses are **ACKNOWLEDGED** and **CLOSED**.
|
|
- **Who can move to ACKNOWLEDGED/CLOSED:** PX/Hosp-Admin/PX-Mgmt/PX-Employee (`views.py:783`).
|
|
- **Approval required:** none.
|
|
- **Patient confirmation:** not present — the suggester has no authenticated presence and is never asked to confirm.
|
|
- **Evidence:** none on `Feedback` (exists only on `CommentActionPlan.evidences`, `models.py:728`, in the separate IT-export pipeline).
|
|
|
|
"Resolution" for a suggestion is simply an operator deciding to flip status and optionally recording an internal/external `FeedbackResponse` note.
|
|
|
|
---
|
|
|
|
## 12. Closure Process
|
|
|
|
- **When closeable:** from SUBMITTED, REVIEWED, ACKNOWLEDGED, or REOPENED (`models.py:41-45`) — closable from almost every state.
|
|
- **Who closes:** PX/Hosp-Admin/PX-Mgmt/PX-Employee (`views.py:783`).
|
|
- **Side-effects:** `closed_at`/`closed_by` (`views.py:827`); `FeedbackResponse("status_change")` created; audit.
|
|
- **Reopen conditions:** CLOSED → REOPENED is the only exit; reopening clears `closed_at`/`closed_by`, leaves `acknowledged_*`/`reviewed_*` intact.
|
|
- **Permanently completed:** **no "permanent close" flag.** A suggestion can be reopened indefinitely (REOPENED → CLOSED → …).
|
|
- **Soft-delete:** independent of status — `feedback_delete` calls `soft_delete`; restorable via `SoftDeleteModel.restore()` (no UI button — admin only).
|
|
|
|
---
|
|
|
|
## 13. Exception Flows
|
|
|
|
The codebase implements **none** of the classic exception statuses as first-class concepts:
|
|
|
|
| Exception | Implemented? | Evidence |
|
|
|---|---|---|
|
|
| Duplicate | No | No `duplicate_of` FK, no detection task. Operators close+note. |
|
|
| Withdrawn | No | No `WITHDRAWN` status; suggester has no withdraw channel. |
|
|
| Invalid | No | No `INVALID`/`REJECTED`. SUBMITTED→CLOSED is closest (`models.py:41`). |
|
|
| Spam | No dedicated status | Only the public endpoint's IP rate-limit (5/300s → 429, `views.py:944`). Spam closed+noted manually. |
|
|
| Converted-to-complaint | No | No converter. Closest is `feedback_create_action` spawning a `PXAction`, not a Complaint. |
|
|
| Merged | No | No merge view, no `merged_into` FK. |
|
|
| Reopened | Yes | `models.py:37,44`; `views.py:830`. |
|
|
| Missing info | No | No "awaiting info" status. `requires_follow_up` flag (`models.py:227`, toggled `views.py:916`) is the only soft signal — a boolean, not a state. |
|
|
|
|
---
|
|
|
|
## 14. Department-Response Sub-Flow — **NOT present for Suggestions**
|
|
|
|
This is the single most important architectural difference between Suggestions and Complaints/Inquiries/Observations.
|
|
|
|
**Explicit confirmation — suggestions are NOT routed to departments for response:**
|
|
- `feedback_send_to_department` email body: *"This is for your awareness. No response is required."* (`views.py:1126`).
|
|
- `feedback_send_to` SMS body: *"PX360: Feedback '...' logged for {dept}. ... No response is required."* (`views.py:1273,1284`).
|
|
- `feedback_send_to` docstring: *"Awareness-only: no response required from the department."* (`views.py:1151`).
|
|
|
|
**What "send to department" actually does:**
|
|
1. Optionally sets `feedback.department` (`views.py:1252`; `feedback_send_to_department` requires it set, `views.py:1104`).
|
|
2. Looks up champion + manager via `get_champion_and_manager`.
|
|
3. Sends email (+ SMS in send_to) to those people.
|
|
4. Writes an `is_internal=False` `FeedbackResponse` note recording who was notified.
|
|
5. **Does not change status. Does not create any "department response" record. Has no SLA, no reminder, no overdue check.**
|
|
|
|
Contrast with complaints/inquiries/observations which have dedicated `check-overdue-*-dept-responses` and `send-*-dept-response-reminders` beat tasks — feedback/suggestions have **none**.
|
|
|
|
**Conclusion:** Suggestions are handled **centrally by PX**. Departments may be informed for awareness but are never obligated to act within the suggestion lifecycle. If PX wants a department to actually *do* something, they must escalate via `feedback_create_action` to spawn a tracked `PXAction` (separate module).
|
|
|
|
---
|
|
|
|
## 15. End-to-End Example
|
|
|
|
```
|
|
Patient submits suggestion via public form
|
|
↓ (views.py:937) → status=SUBMITTED, ref SGT-202607-0001,
|
|
AI analysis dispatched, PX admins notified
|
|
[⚠ suggester NOT auto-notified beyond immediate JSON ack]
|
|
PX staff assigns to a PX Employee
|
|
↓ (views.py:721) → assigned_to set; FeedbackResponse("assignment")
|
|
PX reviews
|
|
↓ (views.py:776) → status=REVIEWED, reviewed_at/by
|
|
[DECISION: acknowledge or close]
|
|
PX acknowledges
|
|
↓ (views.py:776) → status=ACKNOWLEDGED, acknowledged_at/by
|
|
[optional: add FeedbackResponse("acknowledgment") to timeline]
|
|
PX closes
|
|
↓ (views.py:776) → status=CLOSED, closed_at/by; Quick Actions hidden
|
|
[REOPEN only via admin/ORM — not via staff UI]
|
|
[NO department involvement; NO outbound patient comms]
|
|
```
|
|
|
|
---
|
|
|
|
## 16. Parallel Pipelines (NOT part of the Suggestion lifecycle)
|
|
|
|
These exist in the same app but are **distinct reporting/analytics workflows**, not part of the live suggestion lifecycle. Documented here for completeness.
|
|
|
|
### 16.1 `CommentActionPlan` (`models.py:660`)
|
|
Attaches to `PatientComment`, **not** `Feedback`. Status enum `CommentActionPlanStatus` (COMPLETED/ON_PROCESS/PENDING — default `models.py:715`). Fields: `problem_number`, `comment_text/_en`, `frequency`, `recommendation` (required), `responsible_department`, `timeframe` (e.g. "Q3"), `evidences` (free-text completion evidence), `month`/`year`. No transition map — free-editable via admin. Listed read-only in `action_plan_list` ("Step 5", `views.py:607`).
|
|
|
|
### 16.2 `PatientComment` (`models.py:534`)
|
|
Raw-unit model of the IT-export pipeline. Imported in monthly batches via `CommentImport` (`models.py:479`), which tracks `month`/`year`/`source_file`/`status` (pending/processing/completed/failed) and counts. Each `PatientComment` carries: `source_category` (Appointment/Inpatient/Outpatient), `comment_text`, `classification`/`sub_category`, sentiment keyword fields, **`suggestions` extracted text** (just text, not a link), `sentiment`, `is_classified`, `mentioned_doctor_name`, `frequency`, `month`/`year`. Views: read-only list (`comment_list` `views.py:538`) + Excel exports Step 1/Step 2 (`export_utils.py`). **Creation is admin-only** (no code path creates `CommentImport`).
|
|
|
|
**Bottom line:** treat Comments & Action Plans as a separate "IT-export reporting" module that happens to live in the same Django app, not part of the Suggestions lifecycle.
|
|
|
|
---
|
|
|
|
## Appendix — Flagged gaps / ambiguities
|
|
|
|
1. **Reopen unreachable via UI** after closure (Quick Actions hidden when `closed`, `feedback_detail.html:299`). CLOSED→REOPENED allowed by map but no staff UI button.
|
|
2. **`reference_number` divergence (external API resolved)** — `save()` generates canonical `SGT-YYYYMM-NNNN`; the external API now returns and retrieves by this canonical reference. **Public portal still diverges**: the public submit endpoint returns a different `SG-{uuid[:8]}` (`views.py:1026`), so the public cannot track by the real reference via the web portal.
|
|
3. **`FeedbackForm` unused for creation** — `feedback_create` builds manually, bypassing form `clean()`.
|
|
4. **Transitions unenforced at DB/model/admin** — only `feedback_change_status` enforces.
|
|
5. **No outbound communication to suggester** by design.
|
|
6. **`feedback_send_to_department` requires `department` set, `feedback_send_to` sets it** — overlapping confusingly.
|
|
7. **AI task overwrites operator-entered category/priority** (`tasks.py:219`) — may clobber a manual categorization made immediately after creation.
|