HP is no longer a placeholder. The client gets a new hp_copy_review
profile (single weighted check, client-specific visibility) as its
default, plus the generic static_general and video_general profiles
it already had visibility into.
Adds a "Default Profile" sub-tab to the Settings modal. Lists the
current client's profiles as radio buttons, shows which is the active
default and whether it's a runtime override or the static value from
client_config.py. Admins click a different profile + Set to override;
clear-override button reverts to the static value.
Storage layer: backend/client_defaults.json (gitignored, per-server),
following the same pattern as user_access.json. Resolution order in
client_config.get_default_profile(): override → static
default_profile field → None. The Box webhook handler is the sole
consumer that needs profile selection without a logged-in user; it
now reads via get_default_profile() so overrides take effect.
Why a separate JSON, not rewriting client_config.py: a buggy override
write can never break server boot — worst case the override is
ignored and the static value applies. Cleaner separation between
"static config you check in" and "runtime overrides admins make".
Backend:
- client_config.get_default_profile(client_id) — resolver
- client_config.set_default_profile(client_id, profile_id) — validates
+ writes (rejects profiles not in client's profile list)
- client_config.clear_default_profile_override(client_id)
- GET /api/clients/<id>/default_profile (any auth'd user)
- PUT /api/clients/<id>/default_profile (admin-only, _require_admin)
- DELETE /api/clients/<id>/default_profile (admin-only)
- Box webhook handler in api_server.py now uses get_default_profile()
Frontend:
- New "Default Profile" tab button + tab content in Settings modal
- showTab hook loads settings when tab activates
- loadDefaultProfileSettings / saveDefaultProfile /
clearDefaultProfileOverride functions
- DOM-construction (createElement + textContent) used throughout —
no innerHTML with interpolated values, so user-controllable
strings (client_id, profile_id) can never cause XSS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First client to use the Phase 4 unattended-QC pipeline. Adds three
optional fields to the loreal entry in client_config.py:
- box_folder_id=381501258415 (AI-QC > INCOMING > AI QC LOREAL IN)
- box_reports_folder_id=382076841334 (AI-QC > REPORTS > AI QC LOREAL REPORTS)
- default_profile=loreal_static
When a file lands in the INCOMING folder, /api/box/webhook will pick
it up, run loreal_static (strict-grade), and upload the HTML report
to the REPORTS folder. Other clients remain unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds machine-to-machine Box integration alongside the existing per-user
OAuth scaffolding. The new JWT client (backend/box_jwt_client.py) is
the auth/file/webhook surface used for unattended workflows: load the
Custom App JSON config, sign a JWT assertion, exchange for a 60-minute
service-account access token (cached + refreshed automatically), and
expose file download/upload + V2 webhook CRUD + HMAC signature
verification.
Wires a new POST /api/box/webhook endpoint (NOT @auth.require_auth — it
authenticates each delivery via Box's HMAC signature headers) that:
1. Verifies the signature against env-configured signing keys
(BOX_WEBHOOK_PRIMARY_KEY / BOX_WEBHOOK_SECONDARY_KEY).
2. Dedups deliveries by box-delivery-id with a bounded in-memory cache.
3. Maps the source folder to a client via a new
get_client_by_box_folder() helper on client_config.
4. Spawns a background thread that downloads the file, runs the same
technical pre-flight + LLM check pipeline as the user-uploaded path,
writes the HTML report to output/<client>/, uploads the report back
to the client's box_reports_folder_id, and logs the run with a
synthetic 'box_webhook' user.
Webhook runs skip media-plan / localization / OCR context — those are
user-UI concepts without a meaningful source in unattended runs. The
existing /api/start_analysis path is unchanged.
client_config.py gains three optional per-client fields used by the new
flow when present: `box_folder_id`, `box_reports_folder_id`, and
`default_profile`. Existing client entries keep working without them.
.gitignore now excludes backend/config/box_jwt_config.json so the JWT
config (with its embedded private key + passphrase) never lands in git.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new clients in demo/eval phase. Each uses Honda-style minimal
setup (static_general + video_general only) until real scope and test
assets arrive. Descriptions are placeholders to be replaced once scope
is confirmed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the 'dow_jones' block from CLIENT_PROFILES. After this, the
client picker no longer renders Dow Jones; the four archived profiles
are unreachable from user flows. Nine clients remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removed axa_pdf_accessibility from axa_policy_document (was 8 checks, now 7)
and created a new axa_accessibility profile that contains only that check.
Marked the new profile strict_grade: true so a single PDF/UA-1 rule failure
forces an unmistakable Fail badge on the report — mirrors how axes4 PAC is
used in practice (single-purpose, binary verdict).
Lets users run accessibility-only QC without sitting through the rest of
the policy-document checks, and removes weight from the policy-document
score that the accessibility check wasn't really earning (its 0/10 verdict
was dragging the overall grade in a way that obscured the content checks).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New profile boots_ppack for QCing multi-page Boots production packs
(PowerPoint-exported PDFs, 4-18 pages each). Built on top of AXA's
document-mode infrastructure — branched off feature/axa-document-mode
because it reuses the dispatcher, ingest, and result writer.
New checks:
- boots_logo_compliance — three-path scoring (master wordmark / partner
lock-up / no branding) so OLIVER x BOOTS-style footer lock-ups aren't
scored against master wordmark rules. Conservative without a formal
Boots logo guideline.
- boots_colour_palette — verifies CMYK/RGB/Hex spec values on creative-
guidance pages against canonical Boots Blue / Health Primary Blue /
Offer Red, plus visual sanity-check on artwork pages.
Existing checks tuned:
- boots_brand_name_accuracy: closed-world list semantics. Brands not on
the approved list now go to names_not_on_list (manual review) instead
of failing — the list is sourced from the original 7 docs and is known
incomplete (Remington, Imodium, Maybelline etc. are legitimate Boots-
stocked brands not on it).
- boots_tandc_wording: explicit font-weight caveat — Boots Sharp Regular
vs Light isn't reliably distinguishable by vision LLM at small sizes.
Surfaced via font_weight_caveat field + needs_manual_check value.
Page classifier (document_mode/page_classifier.py):
Heuristic tags each page as cover / checklist / palette / notes /
artwork. Validated on all 10 sample packs.
Strict-grade exemption (Profile.strict_grade flag):
Only artwork-classified pages count towards Pass/Fail. Cover, checklist,
palette, and notes pages are still QC'd and reported as Informational
but cannot trigger a Fail. Banner shows exactly which artwork-page
checks fell below 6.
Result writer extended:
- Per-page table with score + page_type pill for any page_each-scope
check (auto-applied as fallback)
- Strict-grade banner (red on violation, green when clean)
- Page_type pills throughout the per-page strip
Smoke-test result (Remington 4-page pack, 2026-05-05):
Overall 70.75/100, strict-grade Fail. After two iterations of prompt
tuning, all three remaining strict-grade violations are real catches:
orphan asterisk in T&Cs, "they may not be stocked" wording deviation,
missing "Charges may apply". brand_name_accuracy 7.0 (was 3.0 before
list fix), logo_compliance 9.5 (was 1.5 before lock-up path fix).
Local-only — not pushed to dev or merged to develop until after Boots
show-and-tell. Same posture as feature/axa-document-mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both get the static_general + video_general profile bundle, matching Honda's setup. Total clients goes from 8 to 10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default-deny access model with admin grant/revoke via new User Access
tab. /api/clients filters by user grants; client-scoped endpoints
enforce access server-side. Admin role and client grants persist in
user_access.json with audit trail in usage logs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add Honda client with static_general and video_general profiles
- Add video QC capability using Gemini native video analysis (4 checks:
visual_quality, brand_consistency, text_legibility, pacing_flow)
- Add video_general profile assigned to all 8 clients
- Extend session lifetime with MSAL silent token refresh (proactive
every 45min + reactive on expiry), switch cache to localStorage
- Re-enable OCR layout measurements for Amazon checks
- Add scope boundary notes to all 6 Amazon checks to prevent cross-
check penalization (locale errors isolated to logo_country only)
- Relax margins left-alignment tolerance from 1% to 4% to account
for logo lockup internal padding
- Update brand guidelines DB with Amazon localization matrix and
processed Dove PDF summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New boots_static profile (5 checks, 2.0 weight each) for retail promotional
artwork compliance: caveat rules, brand name accuracy (~170 names), offer
mechanics, T&C wording, and currency/locale. Strict grading override (any
check <6 = Fail). Guidelines embedded from 7 thematic guidance documents.
Also splits client-specific documentation out of CLAUDE.md into separate
CLAUDE_LOREAL.md, CLAUDE_AMAZON.md, CLAUDE_BOOTS.md, and CLAUDE_DOW_JONES.md
files to reduce main file size.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New client with embedded brand guidelines for Dow Jones Corporate,
MarketWatch, and Wall Street Journal sub-brands. Guidelines sourced
from live.standards.site scrapes and baked into check prompts.
- dow_jones_static: 5 checks (logo, color, typography, square motif, photography)
- marketwatch_static: 6 checks (logo, color, typography, image treatment, layout, art direction)
- wsj_static: 6 checks (logo, color tiers, typography, imagery, capitalization, layout)
- System now has 7 clients, 12 profiles, 65 QC checks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move reporting from settings modal into a dedicated tab within each
client's main view with date range filtering, usage stats, and cost
tracking. Add admin panel with platform-wide user activity overview,
accessible only to configured admin users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Boots client with static_general profile and Amazon client with
6 new brand-specific QC checks based on ASD 2025 design guidelines:
amazon_required_elements, amazon_logo_country, amazon_typography,
amazon_headline_layout, amazon_margins, and amazon_box_placement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Issue: After implementing visibility control, all profiles were showing
for all clients because existing profiles don't have visibility fields.
Fix: Updated get_profiles_with_visibility() to fall back to CLIENT_PROFILES
mapping when visibility settings are not present in profile JSON files.
Behavior:
- Profiles with visibility field: Use new visibility system
- Profiles without visibility field: Use CLIENT_PROFILES mapping (backward compatible)
This ensures:
- L'Oreal sees: loreal_static, static_general
- Diageo sees: diageo_key_visual, diageo_packaging, static_general
- Unilever sees: unilever_key_visual, unilever_packaging, static_general
- General sees: static_general, inclusive_accessibility
Tested: All clients now correctly see only their assigned profiles
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## New Features
### L'Oréal Static General Profile
- Created new profile with 3 checks optimized for digital marketing assets
- Even weighting (33.3% each) for 100-point scoring scale
- Removed print-specific requirements (3m viewing distance)
- Focus on marketing text vs product packaging distinction
### Multi-File Queue System (web_ui.html)
- Added file queue functionality for batch processing
- Users can now upload and process multiple files simultaneously
- Queue displays file status (pending, analyzing, complete, error)
- Individual file removal and queue clearing options
- Progress tracking for batch operations
### New General QC Checks
1. background_contrast_general
- Optimized for digital assets (no distance requirements)
- Checks logo, product, and marketing text contrast
- Detects overlapping and blending issues
- Provides element-by-element breakdown
2. text_readability_general
- Focus on marketing text only (excludes product packaging)
- Checks for overlapping elements
- Digital readability optimization
- Specific issue identification
3. language_consistency (enhanced)
- Better distinction between marketing and packaging text
- Detailed language detection and reporting
- Lists specific text analyzed
### Usage Tracking System
- Added usage_tracker.py for analysis logging
- Tracks user activity, profile usage, and costs
- Daily log files in JSONL format
- Cost estimation per LLM provider
## Bug Fixes
### Authentication & User Management
- Fixed Flask 'g' import missing issue
- Fixed user info access in background threads
- Pass user_info to threads instead of accessing g.user
- Improved error handling for usage logging
### HTML Report Generation
- Fixed missing analysis details in reports
- Now extracts and displays all JSON fields properly
- Shows comprehensive breakdowns:
- Analysis details
- Elements checked (logo, product, text)
- Marketing text found
- Issues identified
- Specific recommendations
- No more blank "Pass/Fail" results
### Scoring System
- Fixed usage_tracker to handle dict of check results (not list)
- Better handling of model_used field variations
- Skip non-dict check results gracefully
## Configuration Changes
### Model Versions (llm_config.py)
- Fixed invalid GPT-4.1 model ID to gpt-4o
- Added Gemini 3 Pro beta model option
- AVAILABLE_MODELS dict for UI selection
- Model version override support
### Profile Updates
- Static General: 3 checks, total weight 10.0
- Each check: text_readability_general (3.33), background_contrast_general (3.33), language_consistency (3.34)
- Maximum score: 100 points
## Technical Improvements
- Enhanced prompt engineering for consistent LLM outputs
- Mandatory detailed explanations in all checks
- Structured JSON responses with comprehensive fields
- Better error messages and fallback handling
- Client configuration support (client_config.py)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>