From a2d465b3d2c78b5808073770ad91a78b89e4a443 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Tue, 9 Jun 2026 16:59:43 -0400 Subject: [PATCH] 06/09 Update documents --- CLAUDE.md | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++--- README.md | 12 +++++-- 2 files changed, 110 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index dbcfa7f..236fec4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ > **Audience:** AI assistants and developers working on this codebase. > **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions. -> **Last reviewed:** May 2026 (Phase 19 complete + mobile API gap-fill Phases A–E + customer UI refinements: contract filter scoping on Inspections/Issues list, dynamic invitation email domain from `request.host_url`, "Your Facilities" card-grid dashboard panel) +> **Last reviewed:** June 2026 (Phase 19 complete + mobile API gap-fill Phases A–E + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot) --- @@ -43,6 +43,7 @@ - **Notification** system (in-app + email) driven by an admin-controlled matrix - **Reports** — on-demand PDF/CSV scorecards and scheduled email digests - **Audit trail** — immutable log of every create/update/delete action +- **Support chat** — Groq AI chatbot for customers with preset FAQ chips; escalation to admin via ticketing system; customers can view and reply to their own tickets; admins manage tickets at `/support/admin/tickets` - **Mobile API** — JWT-authenticated REST layer for the iPad native app - **iPad native app** — SwiftUI + SwiftData offline-first inspection tool (Phase A + B + C complete) @@ -98,8 +99,11 @@ lt_janitorial_quality_control/ │ ├── models/ │ │ ├── inspection.py # Inspection — mobile_local_id column (Phase B) │ │ ├── issue.py # Issue — mobile_local_id (Phase B), reported_by (Phase 18), mobile_photo_paths (Phase 19) +│ │ ├── support.py # SupportTicket, SupportTicketReply (Phase 23) │ │ └── ... │ ├── routes/ +│ │ ├── support.py # /support/* — AI chat, ticket submit/list/detail (Phase 23) +│ │ └── ... │ ├── static/ │ │ └── uploads/ # UPLOAD_FOLDER root │ │ ├── inspection_photos/ @@ -109,10 +113,17 @@ lt_janitorial_quality_control/ │ │ └── issues/ │ │ ├── view.html # Shows photo_path + mobile_photo_paths under "Photo Evidence" │ │ └── issues_view.html # Same photo evidence logic +│ ├── templates/ +│ │ └── support/ +│ │ ├── chat.html # Customer AI chatbot + FAQ chips + submit-ticket modal +│ │ ├── my_tickets.html # Customer: list of own tickets +│ │ ├── my_ticket_detail.html # Customer: ticket detail + staff replies + follow-up form +│ │ ├── admin_tickets.html # Admin: paginated ticket list with status filter tabs +│ │ └── admin_ticket_detail.html # Admin: ticket detail + reply form + status controls │ └── utils/ ├── migrations/ │ └── versions/ -│ └── phase19_issue_mobile_photos.py ← HEAD +│ └── phase23_support_tickets.py ← HEAD └── ... ``` @@ -134,6 +145,8 @@ lt_janitorial_quality_control/ | `MAIL_DEFAULT_SENDER` | From address | | `DIGEST_SECRET` | Authenticates all cron endpoints | | `REDIS_URL` | Optional. When set, Flask-Limiter uses Redis for shared rate-limit counters across Gunicorn workers. | +| `GROQ_API_KEY` | Optional. When set, enables the AI chatbot at `/support/chat`. Absent → chat input disabled; customers see a "Submit to Support" fallback only. | +| `GROQ_MODEL` | Optional. Groq model ID. Defaults to `llama-3.3-70b-versatile`. | ### Email SSL Auto-Detection @@ -232,6 +245,34 @@ notifications: id, user_id, title, body, link, is_read, created_at, issue_id, notification_preferences: id, user_id, event_type, email_enabled, digest_mode, digest_frequency ``` +### IssueComment + +``` +issue_comments: id, issue_id (FK), user_id (FK), body, created_at, + status_at_time, is_customer_visible (BOOLEAN, default False) ← Phase 22 +``` + +**`is_customer_visible`:** Staff comments are hidden from customers by default (`False`). Staff can tick "Share with customer" at post time to set `True`. Customer-authored comments are always stored as `True`. Customers see only `is_customer_visible=True` comments; staff see all. + +### SupportTicket / SupportTicketReply + +``` +support_tickets: id, customer_id (FK→users SET NULL), facility_id (FK→facilities SET NULL), + subject VARCHAR(200), body TEXT, status VARCHAR(20) DEFAULT 'open', + created_at DATETIME + status values: open / answered / closed + +support_ticket_replies: id, ticket_id (FK→support_tickets CASCADE), user_id (FK→users SET NULL), + body TEXT, created_at DATETIME +``` + +**Flow:** +- Customer submits ticket via chat page modal → status `open` → admins notified (in-app + email) +- Admin replies → status auto-advances to `answered` → customer notified (in-app + email, link to `/support/my-tickets/`) +- Customer adds follow-up → status reverts to `open` → admins notified again +- Admin can manually set: `open` / `answered` / `closed` +- Closed tickets cannot receive new replies from customers + ### NotificationMatrix ``` @@ -269,9 +310,12 @@ api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version | Contracts | ✅ | ✅ | ✅ | read | scoped | | Templates | ✅ | ✅ | ❌ | ❌ | ❌ | | Inspections (execute) | ✅ | ✅ | ✅ | ✅ | read | -| Issues (create/assign) | ✅ | ✅ | ✅ | ✅ | read | +| Issues (create/assign) | ✅ | ✅ | ✅ | ✅ | ✅ create own | | Issues (quick-assign) | ✅ | ✅ | ❌ | ❌ | ❌ | | Issue verification | ✅ | ✅ | ❌ | ❌ | ❌ | +| Issue comments | ✅ | ✅ | ✅ | ✅ | followed/reported issues only | +| Support Chat (AI) | ❌ | ❌ | ❌ | ❌ | ✅ | +| Support Tickets (manage) | ✅ | ✅ | ❌ | ❌ | own only | | Reports | ✅ | ✅ | ✅ | ✅ | scoped | | Scheduled Reports | ✅ | ✅ | ✅ | ❌ | ❌ | | Audit Trail | ✅ only | ❌ | ❌ | ❌ | ❌ | @@ -304,6 +348,7 @@ api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version | `audit` | `/audit` | list (admin only), view, purge | | `reports` | `/reports` | index, facility report, scorecard, CSV/PDF export | | `scheduled_reports` | `/scheduled-reports` | CRUD + manual trigger | +| `support` | `/support` | `GET /chat`, `POST /chat/message` (AJAX→Groq), `POST /tickets`, `GET /my-tickets`, `GET/POST /my-tickets/`, `GET /admin/tickets`, `GET/POST /admin/tickets/` | | `api` | `/api/v1` | parent blueprint | | `api_auth` | `/api/v1` | `/auth/login`, `/auth/refresh`, `/auth/logout`, `/auth/me`, `/devices/register` | | `api_facilities` | `/api/v1` | `/facilities`, `/facilities//areas` | @@ -561,7 +606,9 @@ phase1_projects_roles → phase6_features → phase7_mobile_api → phase8_notif → phase19_issue_mobile_photos → phase20_inspector_assignments → phase21_template_active - → phase21_performance_indexes ← HEAD + → phase21_performance_indexes + → phase22_comment_visibility + → phase23_support_tickets ← HEAD ``` ### phase21_performance_indexes @@ -578,6 +625,22 @@ sudo systemctl restart gunicorn Adds `active` boolean column to `inspection_templates` so templates can be deactivated without deletion. Inactive templates are hidden from the inspection-start form but remain accessible in the template management UI. Uses `INFORMATION_SCHEMA` column existence check — safe to re-run. +### phase23_support_tickets + +Creates `support_tickets` and `support_ticket_replies` tables. Uses table existence check — safe to re-run. + +**Deploy order:** +```bash +flask db upgrade +pip install groq # if not already installed +# Set GROQ_API_KEY in environment / systemd unit +sudo systemctl restart gunicorn +``` + +### phase22_comment_visibility + +Adds `is_customer_visible BOOLEAN NOT NULL DEFAULT FALSE` to `issue_comments`. Existing comments default to staff-only visibility. Uses `INFORMATION_SCHEMA` column existence check — safe to re-run. + ### phase19_issue_mobile_photos Adds `mobile_photo_paths JSON NULL` to `issues` table. Stores extra evidence photos submitted from the iPad at issue-creation time, separate from `result_photos` (resolution photos) so they appear under "Photo Evidence" on the web. Uses `INFORMATION_SCHEMA` existence check — safe to re-run. @@ -665,6 +728,35 @@ Rendered in `dashboard.html` for `current_user.role == 'customer'`. Uses a Boots - **Count badge:** The card header always shows the total facility count as a `badge bg-secondary rounded-pill`. - The JS block is only emitted when `customer_facilities|length > 9`; the search input is only emitted when `customer_facilities|length > 6`. +### Issue Comments — Visibility & Authorship + +- Comments live in the left column of `issues/view.html` under the issue description, rendered as chat bubbles. +- Each bubble shows: colored avatar circle (color keyed to `author.id % 7`), display name, role badge (Staff / Customer), `is_customer_visible` badge (staff-only view), status-at-time badge, timestamp, and body. +- **Staff commenting:** A hidden checkbox `name="is_customer_visible"` in the Add Comment form defaults to unchecked (staff-only). Checking it marks the comment visible to customers. +- **Customer commenting:** Only shown when `can_customer_comment = is_following or issue.reported_by == current_user.id`. Customer POST bypasses `IssueUpdateForm`; the route sets `is_customer_visible=True` unconditionally. +- **Read filtering:** `GET issues/view` passes `filter_by(is_customer_visible=True)` to customers; staff receive all comments. + +### Inspection List Filters + +`inspections.index()` accepts five additional query params: `date_from`, `date_to` (ISO date strings), `score_min`, `score_max` (0–100 floats), `inspector_id` (int). Inspector filter is suppressed when the viewer has the `inspector` role (they always see their own only). The `inspectors` variable is passed to the template only for non-inspector roles so the dropdown is conditionally rendered. + +### Inspector Performance — Excel Export + +`GET /reports/export/inspector-performance` generates a `.xlsx` with two sheets: +- **Performance Summary** — all inspector KPIs, color-coded cells, totals row +- **Inspection Detail** — individual inspection records for the period + +Accepts `date_from`, `date_to`, `inspector_id` query params matching the HTML report page. Logs an `EXPORT` audit action. Uses `openpyxl`. + +### Support Chat — Customer UX + +`GET /support/chat` — customer only. Renders: +- Greeting message with `current_user.display_name` (injected via `var userName = {{ current_user.display_name | tojson }}` — use `tojson` not inline interpolation to prevent XSS/quote breaks). +- FAQ quick-reply chips: text stored in `data-faq="..."` HTML attribute (HTML-escaped with `| e`), read in JS via `btn.dataset.faq`. **Never use `| tojson` in an `onclick=""` attribute** — it emits double-quoted JSON inside a double-quoted attribute, breaking HTML parsing and truncating the `