On server restart, stale active jobs are automatically resumed rather
than failed. Docs already parsed in a prior run are skipped (resume from
cache), docs stuck at 'parsing' are reset to 'pending' and re-parsed.
- Repository: add get_all_stale_active_jobs() and reset_stuck_parsing_docs()
- Service: skip already-parsed docs in _parse_doc(), reset stuck docs on start
- Main: recover stale jobs via asyncio.create_task() in lifespan startup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Correct domain (mg.oliver.solutions), API key, and from address so
support questions from the Profile page actually deliver.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change placeholder from '#WF_12345' to 'WF1234567'
- Remove format validation and error messaging (field is optional, free-form)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
knowledge_base_service and analysis_service were local variables inside
the lifespan() function — not module-level exports. Importing them via
'from app.main import ...' always failed with ImportError → 500.
Use request.app.state (same pattern as analysis_routes.py) instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates all display labels (PDF report, campaign page, Knowledge Base card, analytics, status dashboard, checks overview) and aligns internal agent name in backend. Adds migration 010 to update the knowledge base display_name in production DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
legalAgentReview, brandAgentReview, channelBestPracticesAgentReview,
channelTechSpecsAgentReview, and leadAgentSummary are the actual keys
stored in the JSONB column — not legalAgent, brandAgent, channelAgent.*,
and leadAgent.summary which were causing empty CSV values.
Adds a server-side CSV export covering all campaign, proof, and version
data including agent RAG statuses. The export respects the active agency
filter so oversight admins can scope the download to a single agency.
- backend: `CampaignRepository.get_export_rows()` — flat join across
Campaign → Proof → ProofVersion with Agency and User, extracts agent
RAG statuses from the `agent_review` JSONB column
- backend: `GET /api/export/campaigns-csv` endpoint gated to
super_admin / oversight_admin, streams a dated CSV file
- frontend: `apiService.downloadCampaignsCsv(agencyId?)` — fetches blob
and triggers browser download
- frontend: threads `selectedAgencyId` prop from App → Campaigns →
CampaignList so the export uses the active filter
- frontend: Export CSV button in CampaignList header, visible only to
super_admin / oversight_admin, with spinner while downloading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Now that REST polling removes the 30s GCP LB timeout constraint,
gemini-3.1-pro-preview is restored as primary and gemini-3-flash-preview
is used only when Pro fails or times out.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /api/analyze submits an analysis job and returns job_id instantly.
GET /api/analyze/{job_id} returns progress + result; frontend polls every 2s.
Analysis runs as asyncio.create_task in the background — each HTTP request
completes in milliseconds, well within the 30s GCP Load Balancer limit.
- Add backend/app/services/job_store.py: in-memory AnalysisJob store with
30-min TTL cleanup
- Add backend/app/api/analysis_routes.py: POST + GET /api/analyze endpoints
with full analysis pipeline (hash check, DB persistence, PDF pages, etc.)
- Remove backend/app/websocket/: handlers.py, manager.py, __init__.py
- Update backend/app/main.py: wire analysis_router, store analysis_service
in app.state, drop all WebSocket imports and endpoint
- Update frontend/services/geminiService.ts: replace WS with fetch+poll;
function signatures unchanged so App.tsx / WIPReviewer.tsx need no edits
- Remove VITE_BACKEND_WS_URL from vite.config.ts, deploy.sh, .env.deploy.example
- Update cloudrun.yaml: remove WebSocket-specific session affinity annotation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gemini-3.1-pro-preview takes ~25s per call, hitting the GCP load
balancer's 30s hard timeout before analysis completes. Flash model
returns in ~5-8s, fitting comfortably within the limit. Pro model
kept as fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend now sends client→server ping every 15s during analysis to keep
the GCP LB idle timeout alive from both directions. Backend responds
with pong. Previously only server→client heartbeats were sent, which
didn't reset the proxy's client-side idle timer.
Also updates favicon to Oliver brand mark (gold M).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- index.html: remove broken /index.css link (file never existed, noisy 404)
- index.html: fix favicon path to use %BASE_URL% so Vite rewrites it
correctly for /modcomms/ subpath deployment
- public/favicon.svg: add minimal M favicon (was completely missing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Upstream SSL terminator closes idle WS connections at ~26s. Heartbeat
at T+25 was racing with the close. 10s interval keeps the connection
alive through any proxy with up to ~20s idle timeout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ProofDetailView: remove h-full from root div so content can scroll
naturally (h-full was capping height at viewport, blocking scroll)
- App.tsx: wire up error state to a top toast banner so users see
failures (campaign load, proof upload, etc.) instead of silent drops;
auto-dismiss after 8s, same pattern as notification toast
- Apache: enabled mod_proxy_wstunnel (was missing — proxy_http was
handling WebSocket connections and dropping them due to HTTP-level
timeouts; wstunnel provides a proper bidirectional tunnel)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 25s heartbeat ping from backend to prevent Apache/proxy idle-timeout
killing the connection during 1-3 min analysis runs
- Handle heartbeat silently in both analyzeProof and analyzeWIPProof frontend handlers
- Run PDF rasterization via asyncio.to_thread so heartbeats aren't blocked
- Wrap analyze_proof with asyncio.wait_for(timeout=300) for a hard 5-min cap
- Log dropped send_message calls in ConnectionManager instead of swallowing silently
- cloudrun.yaml: add sessionAffinity, startup probe, raise containerConcurrency 4→10,
document DISABLE_AUTH option
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
window.location.origin alone gives https://baic.oliver.solutions, but the
app is deployed at /modcomms/ (VITE_BASE_PATH=/modcomms/), so the logo
was loading from the wrong path (404). Now uses:
window.location.origin + import.meta.env.BASE_URL + filename
which resolves correctly in both dev (http://localhost:3000/...) and
production (https://baic.oliver.solutions/modcomms/...).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- knowledge_base_service.py: wrap Gemini distillation call in try/except
to fall back to fallback_client/fallback_model if primary times out,
matching the fallback behaviour in GeminiService._generate_content()
- models.py: fix SpecVersion.source_document_ids ORM type annotation from
Mapped[Optional[dict]] to Mapped[Optional[list]] — the field stores a
JSON array of document ID strings, not an object
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace self.gemini.client with self.gemini.primary_client on line 295 of
knowledge_base_service.py. GeminiService only exposes primary_client and
fallback_client — there is no client attribute. This caused all processing
jobs to fail at the distillation step, which is also why Version History
was always blank (no SpecVersion records were ever created).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add LLAMA_CLOUD_BASE_URL config option so the LlamaCloud regional
endpoint can be set without code changes (fixes 401/region errors
on production); pass it through to AsyncLlamaCloud client init
- Document LLAMA_CLOUD_BASE_URL in .env.deploy.example with EU endpoint
- Copy BAR-ModComms-logo-v5.png to frontend/public
- Sidebar: update logo reference v4 → v5
- PDF header: update logo v4 → v5, wrap in black (#000) band for
legibility, remove duplicate "Oliver" wordmark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The deploy script now generates both env files automatically — no more
manual file creation on the server. All secrets and VITE_ vars are
centralised in .env.deploy (gitignored). Updated .env.deploy.example
with all required variables and inline documentation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add VITE_BASE_PATH support to vite.config.ts so assets resolve correctly under /modcomms/ subpath
- Fix home URL in urlState.ts to use BASE_URL instead of hardcoded '/'
- Fix sidebar logo src to use BASE_URL prefix (Vite doesn't rewrite TSX src attributes)
- Fix Azure AD redirect/logout URIs to include BASE_URL subpath in authConfig.ts and App.tsx
- Add migration 009 to remove Mindshare/Zenith and add Rapp agency
- Update .env.deploy.example with production values for baic.oliver.solutions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PDFReport: Fix RagStatus badge padding (symmetric 4px top/bottom + lineHeight 1.2) so text is centered rather than floating at the top
- PDFReport: Add breakInside: 'avoid' to preview+summary grid, agent reviews grid, and li elements to prevent content being cut mid-sentence across PDF page breaks
- Campaigns: Lower two-column layout breakpoint from xl (1280px) to lg (1024px) so laptop screens show side-by-side proof detail view
- Campaigns: Add flex-wrap to button row so Download Proof / Download Report / New Version buttons wrap gracefully on smaller screens
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace html2canvas + jsPDF with window.print() in both handleExportPDF
and handleDownloadReport — browser print properly respects CSS break-inside:
avoid on agent cards and page-break-before on proof pages, eliminating
orphaned section headings
- Add listStylePosition: 'outside' and explicit lineHeight to <ul>/<li>
elements in PDFReport so bullet symbols sit at the text baseline
- Add pageBreakInside: 'avoid' alongside existing breakInside: 'avoid' on
agent cards for cross-browser compatibility
- Replace placeholder shield icon and plain-text Oliver SVG on cover page
with BAR-ModComms-logo-v4.png (Barclays eagle) and styled Oliver wordmark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add min-w-[900px] to root container so content scrolls horizontally below 900px instead of being crushed
- Change grid breakpoint from lg to xl so 3-column layout only fires at 1280px
- Add min-w-0 + truncate to proof title to prevent clipping at narrow widths
- Add flex-wrap to buttons row so buttons wrap rather than overflow
- Add shrink-0/whitespace-nowrap to all action buttons to prevent compression
- Improve button interaction states with hover:shadow-sm, active:scale-95, and transition-all
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sub-Channel dropdown: always white bg + azure border (never azure fill),
even when a value is selected; channel dropdown retains azure fill
- Add button: joined to input field as a single group (no gap, shared
border, matching corner radius); button colour is azure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Disabled sub-channel dropdown: was showing grey bg + grey border;
now shows azure outline (opacity-50) consistent with unselected state
- Chevrons: now white when dropdown is azure-filled (selected),
azure when unselected — previously always azure which was invisible
against the azure background
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
h-28 w-auto was locking height which can distort aspect ratio in the
narrow sidebar. w-full h-auto scales proportionally from the container
width with object-contain as a safety net.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Azure AD v1 access tokens (sts.windows.net issuer) use the 'upn' claim
for the user principal name/email, not 'email' or 'preferred_username'.
Add 'upn' as a fallback so email is correctly resolved on login.
Also add debug logging to show which claims are present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Settings: selected dropdown state now shows azure bg with white text
- Analytics stats: icon circle bg changed from white to grey (#EFEFEF)
- Analytics AI summary: uniform border (remove asymmetric left border);
lightbulb icon sized to match other icons (h-9 w-9)
- Sidebar: active nav item highlight changed from azure to white,
visually connecting to the white main content area
- Sidebar: logo increased from h-20 to h-28
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a user already exists in the DB, get_or_create_from_azure was
returning early without updating their email from Azure AD claims.
Users created before email sync was in place would permanently show
empty emails in User Management.
Now syncs email from Azure AD claims on each login if the stored
email is empty.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Standalone Python CLI tool plan for Barclays IVU Model compliance testing:
batch WebSocket analysis, AI-based scoring via Claude, consistency metrics,
per-run PDF reports, and comparative drift detection reports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Oversight admins can now create campaigns, upload proofs, and
flag/resolve issues when they have an agency assigned. They retain
all existing cross-agency read access for analytics, auditing, and
user management. Oversight admins without an agency see a read-only
campaigns view.
Changes:
- Add oversight_admin to canWrite permission in UserContext
- Guard readOnly for oversight_admin without agency in App.tsx
- Remove oversight_admin block from require_write_access dependency
- Remove WebSocket oversight_admin upload block in main.py
- Require agency for oversight_admin campaign creation in routes.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All modal inner containers now have border-2 border-oliver-azure
for consistent Oliver branding across:
- CreateCampaignModal, CreateProjectModal
- FeedbackReport (resolve + flag modals)
- UserManagement (confirmation + history modals)
- Campaigns (upload, delete confirmation, version history modals)
- Projects (upload, delete modals)
- Login (support contact modal)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace entire Barclays colour palette (navy #1A2142, lime #C3FB5A, violet
#7A0FF9) with Oliver brand tokens: black #1A1A1A, gold #FFCB05, orange
#FF5C00, azure #0487B6, sky #5DF5EA, grey #EFEFEF, green #09821F.
- Switch font from Inter/Barclays Effra to Arial (system font)
- Add new Oliver logo asset (BAR-ModComms-logo-v4.png)
- Sidebar: black background, new logo, azure active state
- Hero: orange "Intelligent Review" text, hide AI-Powered tagline
- Hide ChecksOverview on Home page per Oliver design
- Toast notification: orange background with black text
- All tables: sky headers, alternating white/grey rows
- Campaign badges: gold "In Progress", green "Completed"
- Analytics: grey KPI cards, sky accent on Key Insight, oliver trend colours
- All buttons: azure fill, pill-shaped (rounded-full)
- All tabs/toggles/dropdowns: azure accent colour
- Update HTML title to "Mod Comms - Intelligent Review"
- Default border radius set to 10px
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: thread on_fallback callback through analysis chain
(gemini_service → agents → analysis_service → handlers). The handler
sends a 'model_fallback' WebSocket message exactly once per analysis
when the primary model is unavailable.
Frontend: handle 'model_fallback' WS message and show a dismissible
yellow toast at the bottom of the screen with an 8-second auto-dismiss.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
google-genai SDK expects http_options 'timeout' in milliseconds.
Passing 45 (seconds) was interpreted as 45ms → ~1s deadline,
which Google API rejected with 400 INVALID_ARGUMENT
'Manually set deadline 1s is too short. Minimum allowed deadline is 10s.'
Primary: 45_000ms (45s), Fallback: 150_000ms (150s)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
asyncio.wait_for cannot reliably cancel SDK-internal HTTP connections.
Replace with two genai.Client instances — one per model — each configured
with http_options={'timeout': N} so the TCP connection is actually torn
down when the deadline is reached.
Primary model: 45s, Fallback model: 150s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log analysis showed fallback model responses up to 154s under parallel
load. 60s was too aggressive and would cause false timeouts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Primary model (gemini-3.1-pro-preview): 45s timeout
Fallback model (gemini-3-flash-preview): 60s timeout
Without timeouts, the fallback model under high load would wait
indefinitely, causing analysis to hang for 10+ minutes per file.
asyncio.TimeoutError from the primary model is now handled the same
as other exceptions (falls through to fallback).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>