The previous max_requests=200 caused workers to recycle every ~5 minutes under normal progress polling (~40 req/min), killing any in-flight background matching/QC thread on the worker. Bumping to 5000 means a worker only recycles after several hours, well past any single job. Also raise graceful_timeout to 600s so in-flight threads finish on legitimate shutdowns instead of being SIGKILL'd after 30s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| core | ||
| database | ||
| deploy | ||
| modules | ||
| static | ||
| templates | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| app.py | ||
| auth_middleware.py | ||
| box_client.py | ||
| CHANGELOG.md | ||
| config.py | ||
| DEPLOYMENT_CHECKLIST.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| DOCUMENTATION_SUMMARY.txt | ||
| gunicorn_config.py | ||
| INTEGRATION_TEST_REPORT.md | ||
| jwt_validator.py | ||
| MIGRATION_GUIDE.md | ||
| README.md | ||
| report_parser.py | ||
| requirements.txt | ||
| run.sh | ||
| run_prod.sh | ||
| setup.sh | ||
| test_integration.py | ||
| test_local.sh | ||
| wsgi.py | ||
Unified HM QC Platform
Version: 2.4.0 Status: Production (Deployed) Deployed at: https://ai-sandbox.oliver.solutions/hm-ai-qc-report
A comprehensive quality control platform for H&M marketing assets with AI-powered validation, video matching, and consolidated reporting.
Overview
The platform integrates seven tools into a single web application:
- Reporting - Consolidated QC reports from Box.com with search history
- HM QC - AI-powered image quality control (text legibility, language, quality, pricing)
- Video QC - AI-powered video quality control (direct video analysis via Gemini)
- Video Master Adot - Campaign-based master-to-adaptation video matching via Box
- Printer Check - CSV-to-PDF cross-referencing for print order validation
- Campaigns - Campaign presentation and media plan management for QC reference
- Usage Dashboard - API usage tracking, token counts, and cost estimates
Key Features
- Unified tabbed interface with H&M branding
- Local username/password authentication
- Multi-provider AI: OpenAI GPT-4o and Google Gemini 2.5 Flash
- Google Gemini direct video analysis (no frame extraction needed)
- Campaign presentation upload for guideline-based QC validation
- Pricing references library — standalone uploadable docs (PDF or multi-sheet Excel Mastersheet), per-run selectable, independent of any campaign
- Deterministic price matching — Excel
MPC Pricessheet parsed without LLM into structured per-locale lookup (format + actual prices) - Batch processing for both HM QC and Video QC — multi-file upload, sequential processing, batch results page, collapsible batch groupings on index
- Batch naming by job number (entered at upload time)
- Consolidated report generation: select multiple reports and download a combined HTML
- Asset thumbnails in report listings and embedded in HTML reports (base64)
- Sequential batch processing with memory-safe garbage collection
- Real-time progress tracking (SSE + polling)
- Docker deployment with Apache reverse proxy
- Usage tracking with estimated costs per API call
Deployment
Docker (Production)
# Clone from Bitbucket
git clone git@bitbucket.org:zlalani/hm_ai_qc_report_tool.git /opt/hm-qc-app
cd /opt/hm-qc-app
# Configure environment
cp .env.example .env
# Edit .env with production values (see .env.example)
# Generate password: python3 deploy/generate_password.py
# Build and start
docker compose build
docker compose up -d
# Create database tables
docker exec hm-qc-app python3 -c "from app import app; from core.models.database import db; app.app_context().push(); db.create_all(); print('Tables created')"
The app runs on 127.0.0.1:5050 inside Docker. Configure Apache or Nginx as reverse proxy — see deploy/ for config snippets.
Common Commands
docker compose logs -f # Tail logs
docker compose restart # Quick restart
docker compose down && docker compose up -d --build # Rebuild after code changes
git pull && docker compose down && docker compose up -d --build # Deploy update
Modules
1. Reporting
Consolidated QC report search from Box.com and local database.
Features:
- Job number search (single or comma-separated for multi-job)
- Async search with real-time progress bar
- Box reports saved locally for instant re-viewing (no re-fetch)
- Previous Box Reports section with View/Delete
- Dashboard with designer-friendly error display
- Export: HTML and CSV (full or errors-only)
Workflow: Search job number -> Progress bar -> Dashboard with aggregated results
2. HM QC
AI-powered image quality control for marketing assets.
Profile: H&M Image Check (3 checks)
- Filename Parse (30%) - Flexibly extracts country code, language, dimensions, campaign number from multiple H&M naming conventions (Display, DOOH, OOH, SOME STATIC, Social, POS)
- Image Quality (40%) - AI visual assessment with strict text legibility rules; validates against campaign presentation guidelines when available
- Price/Currency (30%) - Detects prices via LLM vision, then deterministic match against the attached Pricing Reference: currency symbol/format validated via
_formatlookup, actual prices validated via_priceslookup (exact numeric compare with 0.005 tolerance). Falls back to LLM-based campaign-sheet comparison only when the reference has no structured prices (e.g. legacy PDF references).
AI Quality Check evaluates:
- Text & title legibility (CRITICAL - illegible text = automatic fail)
- Language word validation (avoids false positives like "Rock" = German for skirt)
- Campaign guideline compliance (typography, layout, copy, logo placement)
- Image quality, color, composition
- Logo and branding clarity
Features:
- Single and batch file upload (up to 100 files)
- Batch report grouping: reports grouped by upload batch with collapsible sections, batch stats, and "Download All" ZIP
- Batch naming: batches display their job number (from configure step)
- Delete batch: removes all reports, files, and thumbnails in a batch
- Consolidated report: select multiple reports and download a single combined HTML with summary table + embedded individual reports
- Asset thumbnails in report listings and embedded in HTML reports (base64, self-contained)
- Sequential batch processing with gc.collect() between files for stable memory usage
- LLM provider choice: OpenAI GPT-4o or Google Gemini 2.5 Flash
- Two independent dropdowns at configure time: Campaign Presentation (for visual guideline checks) and Pricing Reference (for currency/price validation) — pick either, both, or neither
- Previous QC Reports with View/Download/Delete on all pages (index, upload, results)
- HTML report generation with per-check scoring
- Usage tracking (tokens + estimated cost)
Workflow: Upload -> Configure (provider + campaign + pricing ref + job number) -> Execute -> Results
3. Video QC
AI-powered video quality control with direct video analysis and batch processing.
Checks:
- Visual Quality (weight 50) - Language consistency + text legibility throughout the video
- Censorship (weight 50) - Body coverage compliance (only for
_CENmarket files, skipped otherwise) - Price/Currency (weight 30, new in v2.4.0) - Detects prices across video frames via LLM, deterministic-validates currency + actual price against the attached Pricing Reference. Skipped if no reference attached, locale not parseable from filename, GEN/CEN markets, or no price visible.
Overall score is the weighted mean of non-skipped checks (so skipping any one check falls through cleanly).
How it works (Google Gemini — default):
- Uploads the video file directly to Google Gemini via
genai.upload_file() - Gemini processes the full video with temporal context (motion, transitions, audio)
- AI analyzes language consistency, text legibility, branding, and prices in a single pass per check
- Language check includes false-positive prevention (e.g., "Rock" = skirt in German)
How it works (OpenAI — fallback):
- Extracts 1 frame per second from the video
- Stitches frames into a labeled grid image
- Sends grid to GPT-4o for analysis (1 API call per check)
Features:
- Default: Google Gemini direct video analysis (no frame extraction)
- Fallback: OpenAI GPT-4o frame grid method
- CEN market auto-detection from filename
- Multi-file batch processing (new in v2.4.0): upload up to 50 videos, sequential processing with
gc.collect()between files, batch results page with summary + per-file list + ZIP download - Previous Video QC Reports grouped by batch on the index page (collapsible sections, same pattern as HM QC)
- Two independent dropdowns at configure: Campaign Presentation and Pricing Reference
- Usage tracking
Workflow: Upload video(s) -> Configure (provider + campaign + pricing ref) -> Execute -> Results
- Single file →
/video-qc/results/<session_id>(single report) - Multiple files →
/video-qc/results/batch/<session_id>(batch summary + per-file list)
4. Video Master Adot
Campaign-based master-to-adaptation video matching using Box.com integration.
How it works:
- User enters campaign name
- System searches Box for campaign folder, finds Global Masters and Regional Masters
- Preview shows: master count, countries, adaptation count
- Phase 1: Downloads each master temporarily, fingerprints it (~50KB), deletes video
- Phase 2: Downloads each adaptation temporarily, matches against fingerprints, deletes video
- Results: per-master adaptation mapping, unmatched items, match rate
Matching Engine (4-tier cascade):
- Stage 0: Metadata filtering (80-95% reduction)
- Tier 1: Perceptual hash matching
- Tier 2: AKAZE feature verification
- Tier 3: AI Vision fallback (smart triggering)
Storage: Only fingerprints (~50KB/master) stored permanently. Videos deleted after processing.
Box Folder Structure:
CAMPAIGNS/{campaign_name}/
├── Global Masters/ (various casing)
│ ├── DOOH/
│ ├── DS/
│ ├── OLV/
│ └── ... (video files with MASTER in name)
└── Regional Masters/ (various casing)
├── DE/ (country code folders)
├── FR/
└── ...
5. Printer Check
CSV-to-PDF cross-referencing for print order validation. Ported from the CrossMatch desktop application.
What it does:
- User uploads a CSV order sheet and a ZIP file containing the PDF folder structure
- Filters CSV rows by selected geographic region and country groups
- Scans the PDF folder structure (multi-region or country-level layouts)
- Matches CSV filenames against actual PDF files
- Reports: matched, missing, and extra files with structural warnings
Features:
- Auto-detects CSV delimiter (tab or comma)
- Region and country group selection (EEU, CEU, etc.)
- Campaign detection and filtering from filenames
- Language column normalization (GEN files, KZ/MK locale handling)
- Folder structure validation: misplaced GEN files, duplicate GEN, wrong country folders, files at wrong level
- Results filtering by status (All, Matched, Missing, Extra)
- XLSX export of filtered data
- GEN asset priority: special handling for
Root/GENfolder validation
Folder Layouts Supported:
- Multi-Region:
Root/EEU/PL/,Root/CEU/DE/,Root/GEN/ - Country-Level:
Root/PL/,Root/DE/,Root/GEN/
Workflow: Select region -> Upload CSV + PDF ZIP -> Process -> View results -> Export XLSX
6. Campaigns
Reference data layer consumed by both Image QC and Video QC. Holds two independent things:
Campaign Presentations (tied to a campaign_id)
- PDF — creative guidelines with typography specs, layout rules, copy text, ratio-specific mockups. Parsed via LlamaParse (text + page images).
- Excel — media plan / spec sheet parsed via openpyxl into structured text.
- Multiple documents per campaign are supported and loaded together at QC time.
- Linked to a specific campaign ID (e.g.
1022B,1013A) typed at upload time.
Pricing References (standalone library, new in v2.4.0)
Independent uploadable documents — NOT tied to a campaign_id. Users pick one at QC configure time alongside (or instead of) a campaign presentation.
- Excel Mastersheet — parsed deterministically with openpyxl, no LLM. Looks for:
MPC Pricessheet → flat list of{product_id, language, country, price, currency, product_name}entries (the authoritative source).- Regional sheets (AME, CEU, EEU, NEU, SEU, FRN, SHE, GCN, EAS, IN, BR…) → formatted prices per locale column, used to derive currency
symbol,position,decimal_separator,thousands_separator. - Sheets matching
OLDorCOPYare skipped. Rows markedPRICE NOT PRESENT IN REPORTare skipped.
- PDF — falls back to LlamaParse + LLM extraction for currency format metadata only (no
_pricesproduced).
Stored shape in PricingReference.parsed_data_json:
{
"_format": {"en-US": {"currency_code":"USD","symbol":"$","position":"before",...}, ...},
"_prices": [{"product_id":"1334912002","language":"en-US","price":"49.99","currency":"USD",...}, ...]
}
Workflow
- Upload campaign presentation PDF for a campaign (e.g.,
1013A). - Upload the mastersheet as a Pricing Reference (give it a name, e.g. "1013A Mastersheet").
- On HM QC or Video QC configure, two independent dropdowns appear: Campaign Presentation and Pricing Reference. Pick either/both/neither.
Features
- Multiple documents per campaign (guidelines + media plan)
- Pricing references library — upload multiple, name them, delete independently
- Auto-polling: status badges update in-place when parsing completes
- View parsed content and page images (campaign presentations)
- API endpoints:
/campaigns/api/list— campaign presentations for dropdown/campaigns/api/<campaign_id>— specific campaign with parsed content/campaigns/api/pricing/list— pricing references for dropdown/campaigns/api/pricing/status/<id>— parse status for polling
Backwards compatibility
If storage/reference/global_pricing.json exists on first startup after upgrading from ≤v2.3.x and no PricingReference rows are present, it is auto-imported as a "Default (legacy global)" row so existing installs keep a valid reference attached. Users just pick it from the dropdown.
7. Usage Dashboard
API usage tracking across all tools.
Displays:
- Summary cards: total API calls, tokens used, estimated cost (USD)
- Breakdowns: by provider, model, tool, user
- Recent API calls table with full details
- Time filters: All Time, 30 Days, 7 Days, Today
Cost estimates based on per-model token pricing (GPT-4o, Gemini 2.5 Flash, etc.)
Configuration
Environment Variables (.env)
# Authentication
AUTH_USERS=admin:pbkdf2:sha256:600000$$salt$$hash
# Session
SESSION_COOKIE_PATH=/hm-ai-qc-report
# Box
BOX_CONFIG_PATH=config/box_config.json
BOX_REPORT_FOLDER_ID=133295752718
BOX_CAMPAIGNS_FOLDER_ID=156182880490
# Flask
SECRET_KEY=<generate-random-key>
FLASK_ENV=production
# Database (use absolute path for Docker)
DATABASE_URI=sqlite:////app/database/qc_platform.db
# LLM Providers
OPENAI_API_KEY=<your-key>
GOOGLE_API_KEY=<your-key>
Note: $$ in AUTH_USERS hash is required for Docker Compose (escapes $).
Architecture
Tech Stack
- Backend: Flask 3.0, SQLAlchemy, Gunicorn (gthread workers)
- Frontend: Bootstrap 5, Vanilla JS, Server-Sent Events
- AI: OpenAI GPT-4o, Google Gemini 2.5 Flash (via
google-generativeai) - Video: FFmpeg, OpenCV (AKAZE), Chromaprint
- Storage: Box.com (JWT auth), SQLite
- Deployment: Docker, Apache reverse proxy
Directory Structure
hm_ai_qc_report_tool/
├── app.py # Application factory
├── config.py # Configuration
├── Dockerfile # Docker image
├── docker-compose.yml # Docker services
├── deploy/ # Deployment scripts & configs
│
├── core/ # Shared infrastructure
│ ├── auth/ # Session-based authentication
│ ├── models/ # Database models (QCReport, UsageLog, CampaignPresentation, PricingReference)
│ ├── services/ # LLM config, Box client
│ └── utils/ # Progress tracker, report parser
│
├── modules/
│ ├── hm_qc/ # HM QC (checks, executor, batch executor, profiles)
│ ├── video_qc/ # Video QC (executor, batch executor, price check)
│ ├── video_master/ # Video Master (matching engine, campaign matcher)
│ ├── printer_check/ # Printer Check (CSV parser, folder scanner, matcher)
│ ├── campaigns/ # Campaign presentations + pricing references library
│ ├── reporting/ # Reporting (aggregator, Box search, cache)
│ └── usage/ # Usage dashboard
│
├── templates/ # Shared templates (base.html, login.html)
├── static/ # CSS, JavaScript
├── database/ # SQLite database
└── storage/
├── reports/ # QC report HTML files
├── campaigns/ # Campaign presentation PDFs + page images
├── pricing_references/ # Pricing reference files (per-row dir)
├── thumbnails/ # Asset thumbnails
└── reference/ # Legacy global_pricing.json (auto-imported on upgrade)
Security
- Local username/password auth with PBKDF2/scrypt hashing
- Session-based with
before_requestlogin enforcement - No hardcoded API keys (all from environment)
- Docker container binds to 127.0.0.1 only (not exposed to internet)
- HTTPS via Apache with wildcard SSL certificate
- httpOnly, Secure, SameSite=Lax cookies
License
Proprietary - H&M Hennes & Mauritz AB