2026-04-25 12:11:05 -04:00
2026-04-25 12:11:05 -04:00
2026-03-17 11:13:42 -04:00
2026-02-21 10:36:53 -05:00
2026-02-06 15:30:38 -05:00
2026-02-06 16:03:55 -05:00
2026-02-21 10:36:53 -05: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
  • Issue Tracking — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, and resolution photo uploads
  • Customer Portal — Scoped facility visibility for client accounts with invitation-based onboarding (email link, 72-hour token)
  • Notification System — In-app + email notifications driven by an admin-controlled routing matrix; per-user preferences and digest mode
  • Reports — On-demand PDF/CSV scorecards and scheduled recurring email reports (daily/weekly/monthly)
  • Audit Trail — Immutable log of every create, update, and delete action with actor and IP capture
  • Mobile API — JWT-authenticated REST API for the companion React Native / Expo mobile application
  • Project Hierarchy — Facilities grouped into Projects with optional Project Manager assignment and per-project 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
Email Flask-Mail (SMTP)
PDF ReportLab
Frontend Bootstrap 5, Chart.js, Jinja2
Server Gunicorn + Nginx
Mobile React Native + Expo

Prerequisites

  • Python 3.11+
  • MySQL 8.x
  • 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>

Email SSL: Port 465 uses implicit SSL (MAIL_USE_SSL=True). Port 587 uses STARTTLS (MAIL_USE_TLS=True). 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/.

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

Two background tasks require scheduled execution:

SLA Alerts

Checks all open issues against their SLA window and dispatches notifications:

# Run every 30 minutes
*/30 * * * * /home/jqc/venv/bin/python -c "
from app import create_app
from app.utils.sla import send_sla_alerts
app = create_app('production')
with app.app_context():
    send_sla_alerts()
"

Digest Emails and Scheduled Reports

# Daily digest — run at 7:00 AM
0 7 * * * curl -s -X POST "https://your-domain.com/notifications/send-digest?frequency=daily&secret=YOUR_DIGEST_SECRET"

# Scheduled reports
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run?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 projects, facilities, and reports; cannot manage users or system settings
inspector Executes inspections and manages assigned issues
customer Read-only portal scoped to assigned facilities; receives notifications on their facilities

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 → Invite Customer
  2. Enter the customer's name and email address
  3. The system auto-generates a username and sends an invitation email with a 72-hour setup link
  4. Customer clicks the link, sets their password, and gains access to their scoped portal
  5. Admin assigns the customer to one or more Projects/Facilities via Customers → Manage Assignments

Mobile API

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

Authentication Endpoints

Method Endpoint Description
POST /api/v1/auth/login Login; returns access + refresh tokens
POST /api/v1/auth/refresh 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 for push notifications

Access tokens expire after 60 minutes. Refresh tokens are valid for 30 days and rotate on every use.


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 ENUM changes require three steps: expand the ENUM to include both values, migrate existing data, then contract the ENUM. See Claude.md for the full protocol.


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 Token for authenticating cron requests
MAX_CONTENT_LENGTH 50MB Maximum upload size per request

License

See LICENSE for terms.

S
Description
LT Janitorial Quality Control app
Readme GPL-3.0
6 MiB
Languages
Python 56.9%
HTML 41.4%
CSS 1.7%