2026-06-26 16:37:44 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 16:37:44 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 16:37:44 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 16:37:44 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 09:04:34 -04:00
2026-06-26 09:04:34 -04:00

JQC — Janitorial Quality Control System

A production-grade web application for managing janitorial service contracts, facility inspections, issue tracking, and client reporting.


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
  • 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
  • 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
    • SLA Compliance — resolved-issue SLA compliance % broken down by severity tier and facility, with progress bars; Excel export (2 sheets)
    • Follow-up Closure Rate — tracks which flagged inspections received a re-inspection; per-facility closure rates; Excel export
    • Customer Facility PDF Summary — downloadable PDF report for a facility covering KPIs, area scores, and open issues; accessible from the scorecard page
    • Inspector Performance — per-inspector KPI summary with Excel export (admin/director only)
    • Scheduled Reports — recurring email delivery of summary, facility, or issues reports (daily/weekly/monthly)
  • Audit Trail — Immutable log of every create, update, and delete action with actor and IP capture
  • Support Chat — Groq AI-powered chatbot for customers with preset FAQ quick-replies; automatic escalation to a ticketing system when the AI cannot resolve the issue; admins manage and reply to tickets at /support/admin/tickets with in-app and email notifications on every state change
  • Inspector Performance Export — Excel (.xlsx) export of the inspector performance summary with a color-coded KPI sheet and a detailed inspection log sheet
  • Mobile API — JWT-authenticated REST API (Phase 7) for the companion iPad native app; rate-limited login and refresh endpoints
  • Contract Hierarchy — Facilities grouped into Contracts (internally "Projects") with optional Contract Manager assignment and per-contract customer access control

Tech Stack

Layer Technology
Backend Python / Flask
Database MySQL
ORM / Migrations SQLAlchemy + Alembic
Auth (web) Flask-Login + Flask-WTF (CSRF)
Auth (API) JWT + opaque refresh tokens
Rate limiting Flask-Limiter (Redis-backed in production; in-process fallback for dev)
Email Flask-Mail (SMTP)
PDF ReportLab
Frontend Bootstrap 5, Chart.js, Jinja2
Excel export openpyxl
AI chatbot Groq API (llama-3.3-70b-versatile)
Server Gunicorn + Nginx
Mobile SwiftUI + SwiftData (iOS 17+)

Prerequisites

  • Python 3.11+
  • MySQL 5.7+ (or 8.0+)
  • A configured SMTP server (port 465 or 587)

Installation

1. Clone and create a virtual environment

git clone <repo-url> jqc
cd jqc
python -m venv venv
source venv/bin/activate       # Linux / macOS
# venv\Scripts\activate        # Windows

2. Install dependencies

pip install -r requirements.txt

3. Create the database

CREATE DATABASE jqc CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'jqc'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON jqc.* TO 'jqc'@'localhost';
FLUSH PRIVILEGES;

4. Configure environment variables

Create a .env file in the project root (never commit this file):

SECRET_KEY=<long-random-string>
DATABASE_URL=mysql+pymysql://jqc:your_password@localhost/jqc

# Email
MAIL_SERVER=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=noreply@example.com
MAIL_PASSWORD=smtp_password
MAIL_DEFAULT_SENDER=noreply@example.com

# Application
APP_BASE_URL=https://your-domain.com
DIGEST_SECRET=<random-secret-for-cron-auth>

# Rate limiting (optional — recommended for production multi-worker deployments)
# REDIS_URL=redis://127.0.0.1:6379/0

Email SSL: Port 465 uses implicit SSL; port 587 uses STARTTLS. The application auto-detects based on MAIL_PORT — never set both flags to True.

5. Run database migrations

flask db upgrade

6. Start the development server

python run.py

The application will be available at http://localhost:5000.


Production Deployment

Gunicorn

gunicorn -c gunicorn_config.py wsgi:app

The included gunicorn_config.py binds to 127.0.0.1:8000 with sync workers. Log files are written to /home/jqc/logs/.

Rate limiting: Flask-Limiter uses Redis when REDIS_URL is set in the environment, falling back to in-process memory for local development. In production with multiple Gunicorn workers, set REDIS_URL=redis://127.0.0.1:6379/0 so rate-limit counters are shared across all workers. The redis package is included in requirements.txt.

server {
    listen 443 ssl;
    server_name your-domain.com;

    client_max_body_size 50M;    # must match MAX_CONTENT_LENGTH in config.py

    location / {
        proxy_pass         http://127.0.0.1:8000;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }

    location /static/ {
        alias /path/to/jqc/app/static/;
        expires 30d;
    }
}

Systemd service (example)

[Unit]
Description=JQC Gunicorn
After=network.target mysql.service

[Service]
User=jqc
WorkingDirectory=/home/jqc/lt_janitorial_quality_control
EnvironmentFile=/home/jqc/.env
ExecStart=/home/jqc/venv/bin/gunicorn -c gunicorn_config.py wsgi:app
Restart=on-failure

[Install]
WantedBy=multi-user.target

Cron Jobs

Four background tasks require scheduled execution. All endpoints that require a token use DIGEST_SECRET.

# SLA alerts — every 30 minutes
*/30 * * * * curl -s -X POST "https://your-domain.com/notifications/check-sla" \
    -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"

# Expired/revoked API token cleanup — 3:00 AM
0 3 * * * curl -s -X POST "https://your-domain.com/notifications/cleanup-tokens" \
    -d "token=YOUR_DIGEST_SECRET"

# Scheduled report delivery — 8:00 AM
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run" \
    -d "secret=YOUR_DIGEST_SECRET"

User Roles

Role Description
admin Full access to all features including Audit Trail and Notification Matrix
director Broad access equivalent to admin, excluding Audit Trail and Notification Matrix
project_manager Manages contracts, facilities, and reports; cannot manage users or system settings
inspector Executes inspections and manages assigned issues
customer Portal scoped to assigned facilities; can create issues, comment on followed/reported issues, use the AI support chat, and submit/reply to support tickets

Creating the First Admin Account

flask shell
>>> from app import db
>>> from app.models.user import User
>>> u = User(username='admin', email='admin@example.com', role='admin')
>>> u.set_password('your-secure-password')
>>> db.session.add(u)
>>> db.session.commit()

Customer Onboarding

  1. Admin navigates to Customers → New Customer
  2. Enter the customer's Full Name and Email only — no username or password required from the admin
  3. The system auto-generates a temporary username and sends an invitation email with a secure 72-hour setup link
  4. Customer clicks the link and arrives at the account setup page where they choose their own username and set their password
  5. The account activates immediately and the customer can log in
  6. Admin assigns the customer to Contracts/Facilities via Customers → Manage

Expired invitations (token past 72 hours, password never set) are surfaced as a warning banner on the Customers page with inline Resend buttons. Resending generates a fresh 72-hour token.


Mobile API

The REST API is available at /api/v1/ and uses JWT Bearer token authentication.

Authentication Endpoints

Method Endpoint Rate Limit Description
POST /api/v1/auth/login 10/min Login; returns access + refresh tokens
POST /api/v1/auth/refresh 30/min Rotate refresh token; returns new access token
POST /api/v1/auth/logout Revoke refresh token
GET /api/v1/auth/me Return current user profile
POST /api/v1/devices/register Register APNs device token

Access tokens expire after 60 minutes. Refresh tokens are valid for 30 days and rotate on every use. Expired and revoked tokens are cleaned up automatically on each login and via a nightly cron job.


Database Migrations

# Apply all pending migrations
flask db upgrade

# Create a new migration after model changes
flask db migrate -m "description of change"

# Roll back one migration
flask db downgrade

MySQL Compatibility Notes

  • 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.

Application Logs

Log File Contents
logs/jqc.log Application log (rotating, 5 × 5 MB)
/home/jqc/logs/gunicorn-error.log Gunicorn worker errors
/home/jqc/logs/gunicorn-access.log HTTP access log

Configuration Reference

Variable Default Description
SECRET_KEY required Flask session signing key
DATABASE_URL required SQLAlchemy connection URI
MAIL_SERVER SMTP hostname
MAIL_PORT 587 SMTP port (465 = SSL, 587 = STARTTLS)
MAIL_USERNAME SMTP username
MAIL_PASSWORD SMTP password
MAIL_DEFAULT_SENDER noreply@janitorialqc.local From address
APP_BASE_URL "" Base URL for links in emails
DIGEST_SECRET Authenticates all cron endpoints
MAX_CONTENT_LENGTH 50MB Maximum upload size per request
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

License

See LICENSE for terms.

S
Description
Janitorial QC Multi-tenant
Readme GPL-3.0
2.8 MiB
Languages
Python 62.5%
HTML 36.1%
CSS 1.4%