Jul 8 - Update documents

This commit is contained in:
2026-07-08 16:25:35 -04:00
parent 2674ccbab4
commit 97a559d5c0
5 changed files with 198 additions and 7 deletions
+9 -3
View File
@@ -2,7 +2,7 @@
> **Audience:** AI assistants and developers working on this codebase.
> **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions.
> **Last reviewed:** July 2026 (Phase 19 complete + mobile API gap-fill Phases AE + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + Phase 28 inspection-notify fix + Phase 29 admin broadcasts + Phases 3032 device registry consolidation + ProxyFix reverse-proxy fix)
> **Last reviewed:** July 2026 (Phase 19 complete + mobile API gap-fill Phases AE + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + Phase 28 inspection-notify fix + Phase 29 admin broadcasts + Phases 3032 device registry consolidation + ProxyFix reverse-proxy fix + Phase 33 per-contract notification recipients + grouped Admin nav dropdown + forgot-password case-insensitive lookup & email normalization + transactional email sender/branding fix + Phase 34 facility QR public pages & report-a-problem + Phase 35 issue handler_type (our staff / facility / vendor) + Phase 36 scheduled inspections)
---
@@ -133,7 +133,7 @@ lt_janitorial_quality_control/
│ └── utils/
├── migrations/
│ └── versions/
│ └── phase32_device_token_columns.py ← HEAD
│ └── phase36_scheduled_inspections.py ← HEAD
└── ...
Note: `app/routes/broadcast.py` + `app/models/broadcast.py` (admin broadcasts) and
@@ -390,6 +390,7 @@ inspections.scheduled_inspection_id FK→scheduled_inspections SET NULL ← P
**A plan, not an inspection.** Names a facility + template + assigned inspector + `next_due_date`. Lifecycle:
- The assigned inspector (or a manager) clicks **Start**`scheduled_inspections.start` creates a normal `in_progress` Inspection with `scheduled_inspection_id` set, then redirects to the execute flow.
- On **completion** (execute route, status → `completed`), `ScheduledInspection.fulfill()` runs in the same atomic commit: `once``active=False`; recurring → `next_due_date` rolls forward past today via `_add_interval()` and the three `*_notified` flags reset.
- **Assignment notification** (immediate): on **create**, the assigned inspector gets an in-app + email "assigned to you" notification; on **edit**, only when the inspector actually changes (a "reassigned to you" notification to the new assignee). Via `_notify_assignee()` in the blueprint using `event_type=EVENT_SCHEDULED_INSPECTION`.
- **Reminders** are dispatched by the cron endpoint (see §11): advance (1 day before) + due-date to the inspector, overdue to admin/director — each fires at most once per occurrence via the `*_notified` flags. Uses `notify()` with `event_type=EVENT_SCHEDULED_INSPECTION`.
- Dashboard shows an **upcoming (next 7 days) / overdue** panel for non-customers (inspectors see only their own).
@@ -932,6 +933,9 @@ if obj is None: abort(404)
### Active Nav Tab
Detected via `request.endpoint.startswith('<blueprint>.')` in each nav `<a>` tag.
### Admin Nav Dropdown
Admin-only tools (Users, Audit Trail, Notification Matrix, Broadcast, Devices) are consolidated into a single **Admin** dropdown in `base.html` (plain text items, no icons, no dividers — matching sibling top-level nav links). The dropdown toggle shows `active` when any of its endpoints is active (`admin_active` flag). The separate user-menu dropdown keeps its own dividers — don't `replace_all` on `dropdown-divider` across the file.
### Display Names
Always use `user.display_name` in templates — never `.username` for display purposes.
@@ -960,7 +964,7 @@ Always use `user.display_name` in templates — never `.username` for display pu
The Contract selector is always a plain HTML `<select>` (never a WTForms field). On `change` it calls `GET /inspections/facilities_for_project/<project_id>` and replaces the Facility `<option>` list. When the Contract is cleared it restores the "All Facilities" placeholder. The filter bars auto-narrow the server-side facility dropdown on page load when `contract_id` is in the query string.
Pages using this pattern: `issues/form.html` (create), `issues/list.html` (filter bar), `inspections/list.html` (filter bar).
Pages using this pattern: `issues/form.html` (create), `issues/list.html` (filter bar), `inspections/list.html` (filter bar), `scheduled_inspections/form.html` (create/edit — Contract selector narrows the facility list; `selected_project_id` restores it on edit and on POST error).
The issues list and inspections list both accept a `contract_id` query param that filters the DB query to facilities belonging to that contract (`facility.project_id == contract_id`) and narrows the facility dropdown in the rendered HTML.
@@ -1206,6 +1210,8 @@ timeout = 30
| 72 | **Device registration has exactly ONE implementation — `register_device()` in `app/api/auth.py` → `api_device_tokens`** | A second `POST /api/v1/devices/register` (`app/api/devices.py` + `DeviceRegistration` model) was removed July 2026. It was shadowed by the `api_auth` route at routing time and queried the dropped `device_registrations` table. Do not reintroduce a competing device model or duplicate register route. |
| 73 | **Per-contract recipients are dispatched ONLY inside `notify_by_matrix()` — never add a parallel path** | `_notify_contract_recipients()` runs after role + global-custom-email routing and shares the `notified` / `sent_emails` dedup sets. Any new event that should reach contract recipients must go through `notify_by_matrix()` (passing `facility_id`, or an `issue_id`/`inspection_id` that resolves to one). Bypassing it means contract recipients are silently skipped and dedup breaks. Commit stays the caller's responsibility. |
| 74 | **The `public` blueprint (`/f/*`) is login-free — keep it occupant-safe** | Pages are addressed by unguessable `public_token` (never facility id), 404 on inactive/unknown facilities, and expose only a quality rating, last-inspected date, and open-issue COUNT — **never** issue descriptions, inspector names, per-item scores, or any other facility's data. The `report` POST must stay CSRF-protected (Flask-WTF form), rate-limited, and honeypot-guarded; public-reported issues are created with `reported_by=NULL`, `severity='medium'`, and routed through `notify_by_matrix('issue_created', facility_id=...)`. Do not add fields that leak internal detail, and do not reuse `render_template('base.html')` here — the public page is a standalone template with no authenticated nav. |
| 75 | **Email is stored lowercased; look it up case-insensitively** | User/customer email is normalized to `.strip().lower()` at every write site (`auth.py` profile/create/edit, `customers.py` invite/edit). Forgot-password lookup uses `db.func.lower(User.email) == input` so a mixed-case legacy row still matches — a plain `filter_by(email=...)` silently missed them and sent no reset (the failure was invisible because of the generic "if an account exists…" message). Keep both halves: normalize on write, case-insensitive on lookup. |
| 76 | **Transactional email `From` must be an SMTP-authorized identity, per-domain branding via display name only** | Reset-password sends from `MAIL_DEFAULT_SENDER`; customer invite sends from `branded_sender()` = `(per-domain display name, authorized address)`. A per-host `noreply@<subdomain>` sender is accepted by the relay then dropped by SPF/DMARC. See rule 64 and §8 `mail_utils.py`. |
---
+18 -4
View File
@@ -7,9 +7,11 @@ A production-grade web application for managing janitorial service contracts, fa
## Features
- **Inspection Management** — Execute structured inspections against configurable templates with a drag-and-drop form builder supporting ratings, pass/fail, photos, signatures, and free-form fields. Starting an inspection requires only a Template and Facility selection.
- **Issue Tracking** — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, resolution photo uploads, and inline quick-assign from the issues list
- **Scheduled Inspections** — Plan one-time or recurring (daily/weekly/monthly) inspection assignments per facility/template/inspector with a due date. The assigned inspector is notified on assignment, reminded the day before and on the due date, and managers are alerted on overdue; completing a linked inspection rolls recurring schedules forward automatically. Upcoming/overdue panel on the dashboard.
- **Issue Tracking** — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, resolution photo uploads, and inline quick-assign. Each issue records **who handles it** — our staff, the facility's own staff, or an external vendor — while always keeping one of our staff as the internal follow-up owner
- **Facility QR Codes** — Each facility has a printable QR code (single or bulk print sheet) linking to a login-free, occupant-friendly status page showing recent cleaning quality and a "Report a Problem" form that files an issue directly; token-addressed and rate-limited
- **Customer Portal** — Scoped facility visibility for client accounts with invitation-based onboarding; admin enters name and email only, customer chooses their own username and password via a 72-hour emailed link; expired invitation warnings surface on the admin dashboard
- **Notification System** — In-app + email notifications driven by an admin-controlled routing matrix; per-user preferences including digest mode and a one-click "Pause All Emails" toggle
- **Notification System** — In-app + email notifications driven by an admin-controlled routing matrix, plus optional per-contract additional recipients (staff users or external emails) for chosen events; per-user preferences including digest mode and a one-click "Pause All Emails" toggle
- **Reports & Analytics** — Comprehensive reporting suite accessible from the second nav position:
- *Overview & Trends* — facility scorecards, score trend charts, and PDF/CSV exports
- *Issues Aging* — open issues grouped into age buckets (`<24h``>4 weeks`) with SLA status per issue; Excel export
@@ -48,7 +50,7 @@ A production-grade web application for managing janitorial service contracts, fa
## Prerequisites
- Python 3.11+
- Python 3.12+ (several route files use nested same-quote f-strings that require 3.12)
- MySQL 5.7+ (or 8.0+)
- A configured SMTP server (port 465 or 587)
@@ -158,6 +160,8 @@ server {
}
```
> **Reverse proxy:** the app wraps its WSGI stack in `ProxyFix` (trusting one proxy hop), so it reads the real client IP and scheme from the `X-Forwarded-For` / `X-Forwarded-Proto` headers Nginx sets above. This is required for correct per-client rate limiting and `https://` external links. Do not remove those `proxy_set_header` lines.
### Systemd service (example)
```ini
@@ -180,13 +184,17 @@ WantedBy=multi-user.target
## Cron Jobs
Four background tasks require scheduled execution. All endpoints that require a token use `DIGEST_SECRET`.
Background tasks require scheduled execution. All endpoints that require a token use `DIGEST_SECRET`.
```bash
# SLA alerts — every 30 minutes
*/30 * * * * curl -s -X POST "https://your-domain.com/notifications/check-sla" \
-d "token=YOUR_DIGEST_SECRET"
# Scheduled inspection reminders (advance/due to inspector, overdue to managers) — every 30 minutes
*/30 * * * * curl -s -X POST "https://your-domain.com/scheduled-inspections/run" \
-d "token=YOUR_DIGEST_SECRET"
# Daily digest emails — 7:00 AM
0 7 * * * curl -s -X POST "https://your-domain.com/notifications/send-digest" \
-d "token=YOUR_DIGEST_SECRET&frequency=daily"
@@ -198,6 +206,10 @@ Four background tasks require scheduled execution. All endpoints that require a
# Scheduled report delivery — 8:00 AM
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run" \
-d "secret=YOUR_DIGEST_SECRET"
# Facility score-drop trend alerts — 8:00 AM
0 8 * * * curl -s -X POST "https://your-domain.com/notifications/check-score-trends" \
-d "token=YOUR_DIGEST_SECRET"
```
---
@@ -274,6 +286,7 @@ flask db downgrade
- **ENUM changes** require three steps: expand → migrate data → contract. Never skip steps.
- **`CREATE INDEX IF NOT EXISTS`** is not supported on MySQL < 8.0.1. Use `information_schema.statistics` existence checks instead — see `phase12_performance_indexes.py` for the reusable `_index_exists()` helper pattern.
- **Migration `revision` ids must be ≤ 32 characters** — Alembic's `alembic_version.version_num` column is `VARCHAR(32)`. A longer id passes the DDL step but fails when Alembic writes the version row (`Data too long for column 'version_num'`). Keep the `revision = '...'` value short even if the filename is long.
---
@@ -304,6 +317,7 @@ flask db downgrade
| `REDIS_URL` | — | Redis connection URI for shared rate-limit storage; optional but recommended in production |
| `GROQ_API_KEY` | — | Groq API key. When absent the AI chatbot is disabled; customers can still submit support tickets. |
| `GROQ_MODEL` | `llama-3.3-70b-versatile` | Groq model ID override |
| `GOOGLE_MAPS_API_KEY` | `""` | Optional. Enables the Google Maps embed of inspection GPS coordinates on the inspection detail page (admin/director). |
---
+62
View File
@@ -0,0 +1,62 @@
# Customer User Manual — New Sections (July 2026)
> Paste these sections into **JQC_Customer_User_Manual_v2.docx**. Suggested
> placement noted under each heading. Replace the *[Screenshot]* placeholders
> with captures from your environment.
---
## Facility QR Codes — Check Status & Report a Problem
*Suggested placement: after the "Viewing Your Facilities" section.*
Each facility has a **QR code** posted on-site (for example, at the entrance or
inside restrooms). Anyone — staff, occupants, or visitors — can scan it with a
phone camera to see the facility's current cleaning status and report a problem.
**No login is required.**
*[Screenshot: posted QR code / the scan landing page on a phone]*
### What the page shows
When you scan the code (or open the link), you'll see:
- **Cleaning Quality** — an overall rating (Excellent, Good, Fair, or Needs
attention) with the facility's average inspection score over the last 90 days.
- **Last Inspected** — the date of the most recent inspection and its score.
- **Open Issues** — how many issues are currently being tracked at the facility.
> For privacy, the public page shows only summary information. It never displays
> individual issue details, inspector names, or any other facility's data.
### Reporting a problem
1. On the facility status page, scroll to **Report a Problem**.
2. Optionally enter **where** in the building the problem is (e.g., "2nd floor
men's restroom").
3. Describe the problem.
4. Optionally add your **name and contact** and attach a **photo**.
5. Tap **Submit Report**.
Your report is filed as a new issue for that facility and the cleaning team is
notified immediately. You'll see a confirmation message when it's received.
*[Screenshot: the "Report a Problem" form]*
---
## Issues — Who Is Handling It
*Suggested placement: within the existing "Issues" / "Logging an Issue" section.*
When you view an issue, it now shows **Handled By**, indicating who is
responsible for resolving it:
- **Our Staff** — a member of the janitorial quality-control team.
- **Facility Staff** — the facility's own on-site staff are handling it.
- **External Vendor** — an outside contractor has been engaged.
In every case, one of our team members remains the **follow-up owner** who
verifies the work and updates the issue. You do not set this field — it is
managed by the JQC team — but it lets you see at a glance how your issue is
being addressed.
+29
View File
@@ -0,0 +1,29 @@
# Inspector (iPad) Manual — Update Note (July 2026)
> This note concerns **JQC_Inspector_Manual.docx** (the iPad / mobile app manual).
## No changes required this cycle
The features added in this round are **web-only** and are **not yet exposed in
the mobile API**, so the iPad app's behavior — and therefore this manual — is
unchanged:
- **Scheduled inspections** — created and executed through the web app. The iPad
app has no scheduled-inspection list or "Start from schedule" action.
- **Issue "Handled By" (Our Staff / Facility Staff / External Vendor)** — set and
displayed on the web only. The mobile issue screens are unchanged.
- **Facility QR codes / occupant report-a-problem** — a public web page; not part
of the inspector app.
## If these come to the iPad later
When/if the mobile app is extended to cover them, the manual will need:
- A **Scheduled Inspections** screen (list of assigned schedules, due dates,
"Start" to begin the inspection) — requires new `/api/v1` endpoints.
- A **Handled By** field on the issue detail/edit screen — requires adding
`handler_type` and the facility/vendor fields to the issue API payload and a
PATCH endpoint.
No action needed now beyond noting that the web and iPad feature sets differ in
these areas.
+80
View File
@@ -0,0 +1,80 @@
# Web Inspector Manual — New Sections (July 2026)
> Paste these sections into **JQC_Web_Inspector_Manual.docx**. Suggested
> placement noted under each heading. Replace the *[Screenshot]* placeholders
> with captures from your environment.
---
## Scheduled Inspections
*Suggested placement: as a new top-level section after "Starting an Inspection".*
Inspections can be **scheduled** in advance and assigned to you — either as a
one-time visit or on a repeating schedule (daily, weekly, or monthly). You are
notified when one is assigned to you and reminded as the due date approaches.
### Where to find your scheduled inspections
- **Dashboard** — the **Scheduled Inspections** panel lists what's coming up in
the next 7 days and warns you about anything overdue.
- **Inspections → Scheduled** — the full list of your assignments, with due
dates. Overdue items are highlighted in red; items due soon in yellow.
*[Screenshot: dashboard Scheduled Inspections panel]*
### Notifications you'll receive
- **When assigned** — an in-app and email notification the moment a schedule is
assigned (or reassigned) to you.
- **The day before** the due date — a reminder.
- **On the due date** — a reminder to complete it.
(Overdue items also alert management.)
### Completing a scheduled inspection
1. Open **Inspections → Scheduled** (or use the dashboard panel).
2. Find the scheduled inspection and click **Start**.
3. The inspection form opens, pre-set to the correct facility and template —
fill it in and submit exactly like any other inspection.
4. On submission, the schedule is automatically marked complete.
- **One-time** schedules are then closed.
- **Recurring** schedules automatically roll forward to the next due date —
you don't need to recreate them.
*[Screenshot: Scheduled list with the Start button]*
> Note: Scheduled inspections are managed (created, edited, reassigned) by
> administrators, directors, and project managers. As an inspector you see and
> execute the ones assigned to you.
---
## Issues — Who Is Handling It ("Handled By")
*Suggested placement: within the existing "Managing Issues" / "Updating an
Issue" section.*
Every issue now records **Handled By**, which separates who does the work from
who follows up:
| Handled By | Meaning |
|---|---|
| **Our Staff** | A JQC team member resolves it. |
| **Facility Staff** | The facility's own staff handle it (their contact is recorded on the issue). |
| **External Vendor** | An outside contractor handles it (vendor details are recorded on the issue). |
Regardless of who does the work, **one of our staff is always the follow-up
owner** — the person responsible for verifying the fix and updating the issue
in the app. You will continue to follow up on and update the status of issues
assigned to you even when facility staff or a vendor performs the actual work.
The **issues list** shows a **Facility** or **Vendor** badge on issues not
handled by our own staff, and you can **filter** the list by "Handled By".
> Setting the Handled By category and the facility/vendor contact details is
> done by administrators, directors, and project managers on the issue detail
> page. Inspectors update status and add notes/photos as usual.
*[Screenshot: issue detail showing the Handled By badge and follow-up owner]*