The accept='' attribute and help copy already allowed .srt, but a separate ALLOWED_EXTENSIONS array in upload.html's JS was filtering out .srt files as 'unsupported format'. Adding 'srt' to that array fixes the silent skip seen on Dev (file picker showed .srt as valid, then the submit handler dropped it). |
||
|---|---|---|
| core | ||
| database | ||
| deploy | ||
| docs/superpowers | ||
| migrations | ||
| modules | ||
| static | ||
| templates | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| app.py | ||
| box_client.py | ||
| CHANGELOG.md | ||
| config.py | ||
| deploy.sh | ||
| DEPLOYMENT_CHECKLIST.md | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| DOCUMENTATION_SUMMARY.txt | ||
| gunicorn_config.py | ||
| INTEGRATION_TEST_REPORT.md | ||
| MIGRATION_GUIDE.md | ||
| README.md | ||
| report_parser.py | ||
| requirements.txt | ||
| run.sh | ||
| run_prod.sh | ||
| setup.sh | ||
| test_integration.py | ||
| test_local.sh | ||
| wait_for_db.py | ||
| wsgi.py | ||
Unified HM QC Platform
Version: 2.5.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) - Video Master 3-pass matching (v2.5.0) — same-duration masters tried first, then strictly longer masters, with Gemini AI Vision as a same-duration/different-resolution fallback for crops; version-aware (only the highest
V<n>per file group is matched) - 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