Remove proof_version_id from FlaggedItemCreate and ResolvedItemCreate
request schemas — the backend already derives it from URL path params.
The frontend was sending an empty string which caused Pydantic to reject
the request with 422, silently preventing flags/resolves from saving.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After processing a new knowledge base spec, invalidate_cache() was
clearing the DB spec from the cache without replacing it. The next
analysis would then fall back to static prompts/*.md files instead of
using the newly generated DB spec.
Now invalidate_cache() accepts optional new_spec_content to immediately
populate the DB cache, and knowledge_base_service passes the freshly
distilled spec content so it's available for the next analysis without
a server restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- LlamaParse service now returns a ParseResult dataclass with markdown,
total page count, and a list of failed pages (page number + error)
- Knowledge base service sets status to "partial" (instead of "parsed")
when some pages failed, with a descriptive error listing which pages
failed and why
- Frontend StatusBadge shows "partial parse" in orange for partial status
- Error details are shown inline below the document row for both partial
and error statuses
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Handle MarkdownPageFailedMarkdownPage objects gracefully by checking for
the markdown attribute with hasattr instead of assuming all pages have
it. Failed pages now log their type and all attributes so the actual
LlamaParse error is visible in logs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse documents concurrently (up to 10 at a time via semaphore) instead
of serially. Each coroutine uses its own DB session for per-document
status updates, while a shared lock serializes job progress increments
on the main session to avoid session-sharing issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All routine MSAL token verification logs now use DEBUG level so they
don't flood the console on every polling request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The background task runs in its own DB session but the job row hadn't been
committed yet by the request session. The background task couldn't find
the job, causing FK violations when trying to create spec_versions.
Fix: explicitly commit the request session after creating the job and
before adding the background task, ensuring the job row is visible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Frontend: only treat parsing_documents/distilling as actively running;
pending jobs older than 2 minutes are ignored as stale
- Backend: add fail_stale_jobs() that marks pending/active jobs older than
5 minutes as failed before checking for active jobs in trigger_processing
- Prevents UI from getting stuck on old jobs that never completed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create DB record first to get the auto-generated UUID, then use that ID
for the storage key. Previously a separate UUID was generated for storage
but the DB record got a different one, causing file retrieval to fail
during processing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove bidirectional back_populates between SpecVersion and ProcessingJob
since both sides have FKs to each other (circular), causing SQLAlchemy to
see both as MANYTOONE. ProcessingJob.spec_version is now a standalone
relationship with explicit foreign_keys. SpecVersion no longer has a
reverse relationship to ProcessingJob (not needed for any queries).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add explicit foreign_keys argument to both sides of the bidirectional
relationship to resolve the multiple FK paths (SpecVersion.processing_job_id
and ProcessingJob.spec_version_id).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace deprecated llama-cloud-services package with llama-cloud>=1.0 (API v2).
Use AsyncLlamaCloud client with tier="agentic_plus" for maximum parsing accuracy
on complex layouts, tables, and visual structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create ErrorItem record when proof analysis results in "Analysis Error" status
- Add submitter_name/submitter_agency fields to ErrorItemResponse schema
- Eager-load proof creator and agency in error items query to avoid N+1
- Populate submitter fields from proof creator in the API route
- Update frontend ErrorItemResponse type and conversion to map submitter fields
- Fix ErrorsTable proof name styling to blue link (text-active-blue) matching Flags tab
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, proof metadata collected during upload was only used for database
persistence. Now it flows through the entire analysis pipeline so agents can
tailor their feedback to the specific channel and format being reviewed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add validation to check MAILGUN_API_URL has a valid protocol prefix
and MAILGUN_API_KEY is set before attempting to make HTTP request.
Returns False gracefully with warning log instead of crashing with
httpx.UnsupportedProtocol error.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add file_hash and is_identical_file columns to proof_versions table
- Compute MD5 hash on file upload and compare with previous version
- Display warning banner when uploading identical file as revision
- Return is_identical_file in WebSocket response and API endpoints
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a subsequent revision of a proof is uploaded, the analysis now takes
place in context of the previous version's results. The system identifies:
- Resolved issues: fixed in the new revision
- Outstanding issues: still present from previous version
- New issues: introduced in the new revision
Key changes:
- Add resolvedIssues, outstandingIssues, newIssues fields to SubReview
- Add PreviousReviewContext model for passing previous review data
- Update all specialist agents to accept previous_review context
- Extend GeminiService with include_revision_fields parameter
- Add get_latest_version_review() repository method
- Update LeadAgent to synthesize cross-version context in summary
- Fetch previous analysis in WebSocket handler for revisions
First version analysis continues to work exactly as before with revision
fields set to null.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Run all 4 specialist agents (Legal, Brand, Channel Best Practices,
Channel Tech Specs) concurrently instead of sequentially. This reduces
total analysis time to roughly the duration of the slowest agent rather
than the sum of all agent times.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed the AI model used for proof analysis from gemini-2.5-flash
to gemini-3-pro-preview for improved analysis capabilities.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move /files/{storage_key}/pages endpoint before the base /files/{storage_key}
endpoint so FastAPI matches the more specific route first.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Backend: Generate PDF thumbnail from first rasterized page on upload
- Backend: Add /files/{storage_key}/pages endpoint for PDF rasterization
- Frontend: Add getPdfPages() method to apiService
- Frontend: Create usePdfPages hook for on-demand PDF page loading
- Frontend: Pass pdfPages prop to ProofPreview in Campaigns view
This fixes the issue where PDF uploads showed no visual preview in results.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add persistent Docker volume for file storage to fix 404 download errors
- Set FILE_STORAGE_PATH env var in Dockerfile and docker-compose.yml
- Increase thumbnail generation limit from 500KB to 10MB for images
- Remove encodeURIComponent from file download URL to prevent path encoding
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace removed toneAgentReview and channelAgentReview with the new
channelBestPracticesAgentReview and channelTechSpecsAgentReview in
the WebSocket handler. Update /info endpoint agent list to match.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove Tone Agent (tone is now part of Brand specs)
- Split Channel Agent into Channel Best Practices Agent and Channel Tech Specs Agent
- Convert Legal Agent from stub to full Gemini-powered implementation
- Add new prompt files for channel_best_practices.md, channel_tech_specs.md, legal.md
- Update ReferenceDocsService with new methods for loading specs
- Update schemas and analysis service to use new agent structure
- Update all frontend components to use new agent names and properties
- Update mock data in Projects.tsx and Campaigns.tsx
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add brand field to AnalyzeProofOptions interface and WebSocket message
- Pass campaign's brandGuidelines to analyzeProof in App.tsx (upload & retry)
- Extract brand from WebSocket message in handlers.py and pass to analysis
- Update AnalysisService.analyze_proof to accept brand parameter
- Refactor BrandAgent to dynamically select brand spec based on brand param
- Add get_barclays_brand_spec() method to ReferenceDocsService (placeholder)
The brand agent now uses the appropriate specification (Barclaycard spec or
Barclays spec when available) based on the campaign's brandGuidelines setting.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create prompts/brand_barclaycard.md with structured brand guidelines
covering logo, Card Portal, colors, typography, and accessibility
- Update ReferenceDocsService with get_barclaycard_brand_spec() method
- Update BrandAgent to use the new spec instead of raw reference docs
- Spec is ~15KB vs ~293KB of raw docs for more efficient analysis
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit formatting instructions to agent prompts requesting bullet-point
output instead of verbose paragraphs. Update JSON schema descriptions for
feedback and summary fields to enforce concise, outline-style format.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PDFs are now converted to PNG images at 200 DPI before being sent to
Gemini for analysis. This fixes the unreliable iframe-based PDF preview
and ensures all pages are properly analyzed.
- Add PyMuPDF dependency for PDF rasterization
- Create pdf_service.py with rasterize() and get_page_count()
- Update agent interfaces to accept list of images for multi-page support
- Add analyze_with_images() to Gemini service for multi-image analysis
- Return rasterized PDF pages via WebSocket for frontend display
- Add page navigation UI for multi-page PDFs in preview components
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend:
- Add email_service.py with Mailgun API integration
- Add SupportEmailRequest schema for email endpoint
- Add Mailgun config settings (API URL, key, from address, support email)
- Update .env.example with Mailgun configuration variables
Frontend:
- Update Login.tsx SupportModal to send emails via /api/support/email
- Update Profile.tsx question form to send emails via apiService
- Add loading states, success/error feedback, and auto-close on success
The support forms on both the login page and profile page now actually
send emails to the support team instead of just showing alerts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GET /files/{storage_key:path} endpoint to serve stored files
- Add getFile() method to apiService to fetch files from backend
- Update convertProofToFrontend() to preserve fileStorageKey
- Update handleRetryAnalysis() to fetch file from backend when not in memory
- Update handleDownload() to download original file instead of thumbnail
After page refresh, the retry button now fetches the original file from
backend storage using the fileStorageKey, allowing failed proofs to be
reprocessed. The Download Asset button also now downloads the original
uploaded file rather than the preview thumbnail.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The staging database has duplicate sub-channels (5 "Meta", 2 "Magazine")
which causes the last duplicate (with 0 proof types) to overwrite the
correct one in the API response.
This migration:
1. Identifies duplicate sub-channels and channels
2. Keeps the one with the most children (proof types)
3. Deletes the duplicates
4. Adds unique partial indexes to prevent future duplicates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend logging:
- Log channel, sub-channel, and proof type counts in get_all_hierarchical()
- Log Meta proof types specifically
- Log API response for Social.Meta
Frontend logging:
- Log raw API response in apiService
- Log dropdown options in App.tsx when loaded
- Log available proof types in UploadProofModal when channel/subchannel selected
This will help diagnose why Meta proof types are not appearing on staging.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace nested selectinload chain with separate explicit queries for
channels, sub-channels, and proof types. The self-referential
selectinload chain didn't reliably load the third level (proof types)
in async SQLAlchemy.
Also add order_by to DropdownOption.children relationship for
consistent ordering.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous refresh() call only reloaded relationships, not scalar
attributes like updated_at. Re-fetching via get_by_id ensures all
attributes and relationships are properly loaded.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refresh campaign object after flush to reload expired attributes
and relationships, preventing lazy load failures in async context.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Workfront Campaign ID field is now optional when creating campaigns.
When provided, it is used as the base for proof workfront IDs instead of
generating random ones.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add HealthCheckFilter to suppress /health endpoint logs at INFO level,
reducing noise from Docker healthcheck requests every 30 seconds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, deleting a proof or campaign only removed database records,
leaving orphaned files in the storage directory. Now the delete endpoints
extract file_storage_key from proof versions and delete files via
storage_service before removing database records.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend:
- Update migration to seed agencies (OLIVER Agency, Barclays, etc.)
- Seed brand guidelines (Barclays, Barclaycard) in dropdown_options
- Seed channel/sub-channel/proof-type hierarchy
- Add /api/agencies endpoint to list all agencies
- Update DropdownOptionsResponse to include brand_guidelines
- Update dropdown repository to return brand guidelines
Frontend:
- Update DropdownOptions interface to include brandGuidelines
- CreateCampaignModal now receives brand guidelines from API
- Settings UsersTab fetches agencies from API instead of hardcoded list
- Add getAgencies() method to apiService
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Populates the dropdown_options table with default channels,
sub-channels, and proof types that were previously hardcoded:
- Social (Meta, X, LinkedIn, TikTok, YouTube)
- Display (Programmatic, Direct Buy, Rich Media)
- Email (Marketing, Transactional)
- Print (Magazine, Newspaper, Direct Mail)
- OOH (Billboard, Transit, Street Furniture)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change frontend apiTokenRequest scopes from OpenID-only to CLIENT_ID/.default
This makes Azure AD issue tokens with audience = app client ID instead of Graph API
- Add diagnostic logging in backend to show token claims before verification
- Fixes 401 Unauthorized errors on all API calls after login
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace all localStorage-based state management with API calls
- Load campaigns, proofs, and audit items from database
- Persist proof analysis results to database via WebSocket
- Add dropdown options CRUD API endpoints (channels, sub-channels, proof types)
- Create DropdownRepository for managing dropdown options
- Update Analytics component to fetch data from API
- Remove demo data and localStorage persistence code
Frontend changes:
- App.tsx: Initialize apiService with MSAL, use API for all CRUD operations
- apiService.ts: Add dropdown options API methods
- Analytics.tsx: Fetch stats from /api/analytics
Backend changes:
- New dropdown_repository.py for dropdown CRUD
- routes.py: Add 7 dropdown endpoints
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change frontend scopes from api://{client_id}/.default to
openid, profile, email for simpler authentication
- Update backend token validation to expect ID token format:
- Audience: client_id (not api://{client_id})
- Issuer: v2.0 endpoint
This avoids requiring Application ID URI setup in Azure AD.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The auth dependency was requiring the Authorization header before
checking DISABLE_AUTH, causing API endpoints to fail in dev mode.
Now returns mock user immediately when DISABLE_AUTH=true.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>