Commit graph

55 commits

Author SHA1 Message Date
michael
fc52ea6b11 Add comprehensive channel tech specs specification for Gemini analysis
Replaces placeholder with detailed technical specifications including:
- Platform-specific dimensions and safe zones (Meta, TikTok, Pinterest, YouTube/PMAX, Snapchat)
- Exact pixel measurements for safe zones extracted from source templates
- Brand element placement rules for Barclays and Barclaycard
- File format, typography, and accessibility requirements
- Comprehensive violations checklist for analysis

Based on Barclays and Barclaycard Social Templates (August 2025).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 12:18:47 -06:00
michael
7ed2486537 Add comprehensive channel best practices specification for Gemini analysis
Replace the 60-line placeholder with a detailed 613-line spec covering:
- 6 core creative principles and trust paradox for financial services
- Platform-specific guidelines: LinkedIn, Reddit, Instagram, Facebook, Twitter/X, YouTube
- Performance metrics and benchmarks from industry research
- Short-form video guidelines based on System1 research
- Mobile-first design requirements with touch targets and safe zones
- Copy and headline guidelines with character limits by platform
- Comprehensive violations checklist for assessment
- Measurement benchmarks and KPIs by platform

Sources: LinkedIn Creative Handbook, Reddit Mega Deck, Global Creative
Inspiration Report, LinkedIn B2C Guide, System1 research, Barclays campaigns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 12:10:36 -06:00
michael
404ba6868b Restructure agent system: remove Tone, split Channel, implement Legal
- 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>
2026-01-24 11:58:17 -06:00
michael
f24b7c7905 Add comprehensive Barclays brand specification for Gemini analysis
Creates prompts/brand_barclays.md with complete brand guidelines including:
- Sacred assets (Eagle, Portal, Cyan)
- Logo/Eagle specifications with sizing rules
- Portal sizing and treatment guidelines
- Full masterbrand color palette (24 colors with hex codes)
- Typography rules (Barclays Effra, 5 weights)
- Digital, social media, email, and advertising guidelines
- Accessibility requirements (WCAG AA/AAA)
- Common violations checklist for brand assessment

This spec is sent to Gemini when users select "Barclays" brand for
proof analysis, providing condensed guidelines for accurate assessment.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 11:41:10 -06:00
michael
2cd3b2b9ae Add brand selection support for Barclays vs Barclaycard guidelines
- 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>
2026-01-24 11:31:59 -06:00
michael
d3e7f99be0 Add comprehensive Barclaycard brand specification for Gemini analysis
- 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>
2026-01-24 11:25:46 -06:00
michael
b52162b111 Fix vertical text alignment in RAG status badges for PDF export
Add lineHeight: 1 to RagStatusBadge component styling to ensure text
is tightly contained within its bounding box, allowing flexbox centering
to work correctly in PDF rendering engines.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:52:11 -06:00
michael
ec2fb82205 Make Gemini analysis responses concise and actionable
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>
2026-01-24 09:48:08 -06:00
michael
e094014dbe Improve CLAUDE.md and README.md with backend documentation
- Add workflow instruction to commit and push after code changes
- Add backend development commands (Python venv, uvicorn)
- Add database commands (Alembic migrations)
- Expand environment setup for both frontend and backend
- Document backend architecture (agents, services, repositories, models)
- Add PostgreSQL and Alembic to tech stack
- Update data persistence section for backend storage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:42:39 -06:00
michael
c1030ee292 Add PDF rasterization support for reliable preview and analysis
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>
2026-01-24 09:36:56 -06:00
michael
caf4539e1d Fix analytics pass rate calculation and add Analysis Errors stat
- Exclude errors from pass rate denominator since they weren't successfully reviewed
- Add missing Analysis Errors stat card to display error count
- Update grid layout to accommodate 5 stat cards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:19:44 -06:00
michael
a4d67fdf46 Format campaign last modified date for better readability
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:09:10 -06:00
michael
f1776df710 Persist navigation state in URL for browser refresh support
- Add URL utility functions for parsing and building URL state
- Initialize app state from URL parameters on page load
- Sync navigation changes to URL via browser history API
- Handle browser back/forward navigation with popstate listener
- Support deep linking to campaigns and proofs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:04:04 -06:00
michael
e2fd9549f7 Add support email functionality via Mailgun
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>
2026-01-24 07:03:11 -06:00
michael
b119951f93 Fix retry button for failed proofs and hook up download asset button
- 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>
2026-01-24 07:01:58 -06:00
michael
94a37f3ed8 Add migration to cleanup duplicate dropdown options
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>
2026-01-23 13:22:44 -06:00
michael
874c1fceee Add debugging for proof types not showing in dropdown
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>
2026-01-23 13:09:25 -06:00
michael
3b868f7415 Fix proof types not loading for sub-channels in dropdown hierarchy
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>
2026-01-23 13:01:39 -06:00
michael
d2d01aaea7 Fix campaign update by re-fetching after flush
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>
2026-01-08 10:27:26 -06:00
michael
f4fd850a49 Force recreate backend container on deploy
Ensures new code changes are picked up by using --force-recreate
flag when starting the backend container.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 10:24:43 -06:00
michael
37c2532263 Fix async SQLAlchemy MissingGreenlet error on campaign update
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>
2026-01-08 10:20:56 -06:00
michael
96d459c322 Center text in PDF report status badges
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 10:13:20 -06:00
michael
091f9ecd87 Add proof types for Meta sub-channel
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 10:07:28 -06:00
michael
74930d623f Replace agent icons with clean Heroicons
Replaced custom AI-generated icons with standard Heroicons for a more
professional appearance on the home page.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 10:02:28 -06:00
michael
8038e4014e Make Workfront Campaign ID optional and propagate to proofs
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>
2026-01-08 09:49:23 -06:00
michael
abfcb6aae2 Fix campaign status change not persisting in UI
Use optimistic update pattern to immediately reflect status changes
in the UI, with rollback on API error.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:31:59 -06:00
michael
5d5bfd571b Update browser tab title to Barclays Mod Comms
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:24:27 -06:00
michael
5fc36e358f Add Download Report button to proof detail view
Rename Download button to Download Asset and add new Download Report
button that generates a PDF report for the current proof version directly
from the proof review screen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:15:18 -06:00
michael
e59960b23b Fix home page responsiveness on small screens
- Make Hero height responsive (350px mobile, 400px tablet, 500px desktop)
- Remove overflow-hidden and flex-1 from ChecksOverview to allow
  content to flow naturally and enable scrolling
- Add responsive padding to ChecksOverview for better mobile spacing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 10:07:46 -06:00
michael
9c25677563 Make Agency Lead field editable in campaign creation
- Add agencyLead state and form field to CreateCampaignModal
- Remove disabled attribute and hardcoded value from Agency Lead input
- Pass agency_lead to backend API when creating campaigns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 10:00:23 -06:00
michael
7e24c9bd50 Filter health check logs from uvicorn access log
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>
2025-12-19 06:37:56 -06:00
michael
8eb0821c9f Delete associated files when proof or campaign is deleted
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>
2025-12-19 06:25:02 -06:00
michael
c3396be029 Fix download button in proof detail view
The download button was only logging to console instead of
downloading the proof file. Added handleDownload function that
supports both data URLs and remote URLs with proper CORS handling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 06:16:07 -06:00
michael
6bdb02d78b Seed database with agencies, brand guidelines, and dropdown options
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>
2025-12-18 17:16:23 -06:00
michael
d080a20ee1 Add migration to seed default dropdown options
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>
2025-12-18 17:09:05 -06:00
michael
0e6f5be46d Support both v1.0 and v2.0 Azure AD token issuer formats
Azure AD issues tokens with different issuer formats depending on the
app registration's accessTokenAcceptedVersion setting:
- v1.0: https://sts.windows.net/{tenant}/
- v2.0: https://login.microsoftonline.com/{tenant}/v2.0

Update backend to accept both formats by trying each issuer in sequence.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 17:03:47 -06:00
michael
dd5ee09d07 Fix JWT signature verification by requesting correct token audience
- 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>
2025-12-18 16:59:54 -06:00
michael
c07c66a583 Connect frontend to PostgreSQL database via API
- 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>
2025-12-18 13:50:37 -06:00
michael
6ff69cc308 Display actual user name in sidebar from MSAL
- Add userName and userEmail props to Sidebar component
- Pass user info from MSAL to Sidebar in App.tsx
- Replace hardcoded "Steve O'Donoghue" with actual logged-in user

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 13:23:28 -06:00
michael
04527d65db Add MSAL debug logging to frontend and backend
- Frontend: Set MSAL log level to Info, add [MSAL] prefix
- Frontend: Add [MSAL Auth] logs for token acquisition
- Frontend: Add [MSAL Login] logs for login popup flow
- Backend: Add [MSAL Backend] logs for token verification
- Backend: Add [MSAL Backend] logs for auth dependency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 13:11:30 -06:00
Michael Clervi
dc17cd087c permissions changes 2025-12-18 16:51:27 +00:00
michael
5b9e824da9 Use OpenID scopes instead of custom API scopes
- 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>
2025-12-18 10:50:02 -06:00
michael
cc2c68bb02 Auto-discard package-lock.json changes before git pull
The package-lock.json changes when npm install runs on different
platforms/npm versions. Since deploy.sh runs npm install anyway,
discard local changes to this file before pulling to avoid conflicts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 10:42:54 -06:00
michael
e06dd414c9 Don't overwrite frontend/.env.local in deploy script
- Remove auto-generation of frontend/.env.local
- Instead, check that it exists and fail with helpful message if not
- Update .env.deploy.example to document manual .env.local setup

This allows manual management of frontend credentials without
the deploy script overwriting them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 10:41:14 -06:00
michael
fed9bb26bf Add Azure AD MSAL variables to deploy script
- Include VITE_AZURE_CLIENT_ID, VITE_AZURE_TENANT_ID, and
  VITE_AZURE_REDIRECT_URI in frontend .env.local generation
- Document these variables in .env.deploy.example

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 10:39:15 -06:00
michael
3f1e1b5227 Fix React version mismatch with MSAL packages
Downgrade React from 19.x to 18.3.1 for compatibility with
@azure/msal-react v3.x which requires React 18.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 10:31:01 -06:00
michael
0742cc0aa4 Fix DISABLE_AUTH check in get_current_user dependency
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>
2025-12-18 10:15:37 -06:00
michael
99af0164e6 Add PostgreSQL database support with Alembic migrations
Backend:
- Add PostgreSQL service to docker-compose with health checks
- Add SQLAlchemy async models for all entities (Agency, User, Campaign,
  Proof, ProofVersion, FlaggedItem, ResolvedItem, ErrorItem)
- Add Alembic migration framework with initial schema migration
- Add repository layer for CRUD operations
- Add REST API endpoints for campaigns, proofs, and audit items
- Add file storage service for proof uploads
- Update WebSocket handler to optionally persist analysis results

Frontend:
- Add apiService.ts for REST API communication
- Update geminiService.ts to support database persistence options

Deployment:
- Update deploy.sh to handle database migrations (6-step process)
- Update Dockerfile to include alembic configuration
- Add PostgreSQL environment variables to .env templates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 12:27:18 -06:00
michael
321a9ca820 Implement Microsoft MSAL SSO with PKCE flow
Frontend:
- Add @azure/msal-browser and @azure/msal-react packages
- Create authConfig.ts with MSAL configuration for PKCE flow
- Create authService.ts for token acquisition and user info
- Wrap App with MsalProvider in index.tsx
- Replace dummy login with real MSAL loginPopup() in Login.tsx
- Update App.tsx to use useIsAuthenticated/useMsal hooks
- Update Profile.tsx to display real user data from claims
- Update geminiService.ts to include access_token in WebSocket messages
- Update WIPReviewer.tsx to pass msalInstance for auth

Backend:
- Add python-jose and httpx dependencies for JWT verification
- Create auth_service.py with Azure AD JWKS fetching and token verification
- Create auth.py FastAPI dependency for protected REST endpoints
- Update main.py to verify tokens on WebSocket and protect /info endpoint
- Add AZURE_TENANT_ID, AZURE_CLIENT_ID, DISABLE_AUTH to config

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 08:43:30 -06:00
michael
3df1b9fb92 Auto-generate .env for docker compose commands
The deploy script now creates a .env file with COMPOSE_PROJECT_NAME
and BACKEND_PORT so that manual docker compose commands (ps, logs, etc.)
work without needing to set environment variables.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 06:37:41 -06:00