Change user-facing strings from American to British English: analyze→analyse,
analyzing→analysing, optimized→optimised, color→colour, analyzes→analyses,
synthesizes→synthesises, optimization→optimisation. Code identifiers, status
enums, and developer-facing messages are intentionally unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New GET /analytics/by-agency endpoint groups review metrics by agency.
The Analytics page now shows a sortable agency performance table with
pass rates, failures, errors, and legal review counts for each agency.
Only visible to super_admin and oversight_admin users. Selected agency
row is highlighted when the AgencyFilterBar is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create a comprehensive 25-slide PowerPoint presentation showcasing all
Mod Comms features, including multi-agent AI system, campaign management,
real-time analysis, feedback reports, knowledge base, analytics, auditing,
user roles, and technical architecture. Includes a Python generator script
for reproducible builds and a companion features markdown document.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expire the SQLAlchemy cached user object after flush() so the
subsequent get_by_id() reloads the agency relationship with fresh
data. Previously the identity map returned the same Python object
with the old .agency, causing audit logs to record identical old
and new values on agency changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: Expose created_by field on CampaignResponse schema and all
response constructors in routes.py
- Frontend API layer: Add created_by to CampaignResponse interface and
createdBy to the frontend campaign converter
- Campaign list: Add column sorting (click headers to toggle asc/desc),
per-column text filter inputs below headers, and a "My Campaigns Only"
toggle that filters to campaigns created by the current user
- Default sort is lastModified descending to match existing behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reorder user details from Role/First/Last/Email to First/Last/Email/Role/Agency
so the 2-column grid shows names together at the top. Add Agency as its own
field instead of appending it to the Role display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The selectinload for FlaggedItem.submitter and ResolvedItem.submitter
was not chaining .selectinload(User.agency), so the submitter's agency
was always None in the API response. This caused the "Submit Agency"
column to be empty in the Flags and Resolutions tabs of the Auditing
page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded "OLIVER Agency" with the logged-in user's agency name
from UserContext. The field remains disabled/non-editable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a user_change_logs table to track all role and agency changes made
to users by super admins. Includes a change history modal in the User
Management screen (clock icon per row) showing timestamped, human-readable
change descriptions with the actor who made each change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents accidental Super Admin privilege grants by requiring users to
type "make this user super admin" before the role change is applied.
Modal blocks paste/drag input and reverts the dropdown on cancel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Role and agency changes now show an inline green checkmark + "Saved" indicator
on the affected row that auto-clears after 2 seconds. Agency creation shows a
green success banner that auto-dismisses after 3 seconds. Successful actions
also clear any stale error banners.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handleNavigateToAuditedItem silently failed because campaignProofs are
loaded lazily (only when viewing a campaign), and errors went to a
setError() state that was never rendered. Now the function fetches and
caches proofs on demand when not already loaded, and uses alert() for
visible error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Unassigned (no agency) non-admin users previously saw ALL campaigns due to
a truthiness check that treated None agency_id as "no filter". This was a
security bug — they should see NO campaigns and be blocked from creating them.
Backend: Add _NO_AGENCY sentinel to distinguish "no filter" from "no agency",
add early-returns at all 5 list/analytics endpoints, fix _check_campaign_access
to explicitly reject unassigned users, and block campaign creation with 403.
Frontend: Add isUnassigned boolean to UserContext, show informational empty
state on Campaigns view, and reinforce readOnly for defense-in-depth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch canWrite from blacklist (role !== 'oversight_admin') to explicit
whitelist (super_admin, agency_admin, basic_user) for clearer permission
logic. Propagate readOnly prop to CampaignDetail and ProofDetailView
subcomponents so upload/delete buttons are properly hidden for read-only
roles at all navigation levels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace single-line bullet format with a structured two-part format
(**Issue:** / **Recommendation:**) in all specialist and lead agent
prompts. Update Gemini response schema description to match. Update
frontend formatFeedbackText and formatFeedbackTextForPDF to parse
**bold** markdown and preserve line breaks within multi-line bullets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the hardcoded "Account Type" field with a "Role" field that
shows the user's role label and agency name from the backend /api/me
endpoint (e.g. "Super Admin (Oliver)", "Standard User (Barclays)").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agents now show example corrections in the format they're recommending
(e.g. sentence case examples when recommending sentence case) to avoid
contradictions between advice and examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all "verdict" language in the lead agent prompt with "status/summary"
and add prescriptive opening-line templates so the LLM produces consistent
output without a "Verdict:" prefix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the repetitive "Actionable Issues" label with the more
constructive "Key Actions" in the feedback report and PDF export.
Remove the uppercase CSS class so the heading renders in sentence case.
Update empty-state text to "No key actions identified."
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agents now spell out acronyms in full on first use (e.g. "Web Content
Accessibility Guidelines (WCAG)") for clarity. The instruction covers
common acronyms like WCAG, FSCS, GDE, APR, CTA, FCA, PRA, and T&Cs,
and applies to any acronym encountered in output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds an IMPORTANT instruction block to all 5 agent prompt templates
(legal, brand, channel best practices, channel tech specs, lead) that
enforces: capitalisation after full stops and in labels, consistent
bullet-point ending style, and "e.g." without a trailing comma.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instructs all five agents (legal, brand, channel best practices,
channel tech specs, lead) to prefer simple vocabulary over complex
alternatives (e.g. "add" over "incorporate", "about" over "regarding").
Also fixes "constitute" → "qualify as" in the legal agent prompt itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UAT feedback flagged the use of "violations", "violates", etc. as feeling
accusatory. Replaced all instances with constructive terms ("issues",
"doesn't align with", "doesn't meet") and added an explicit instruction
to all 5 agent prompt templates to avoid this language in output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added explicit UK English instruction to the Response Format section of
all five agents (legal, brand, channel best practices, channel tech specs,
lead) so output uses spellings like "authorised", "colour", "capitalise".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add UserRole type and AppUser interface to types.ts
- Create UserContext with useUser() hook providing role-based permission booleans
- Split App into App (auth wrapper) + AppContent (uses UserContext)
- Update Sidebar to filter nav items by UserRole instead of boolean isAdmin
- Add User Management nav item (super_admin only)
- Add AgencyFilterBar component for oversight_admin/super_admin session-level filtering
- Pass agencyId to getCampaigns, getAnalytics, audit endpoints in apiService
- Add getMe, getUsers, updateUser, createAgency to apiService
- Build UserManagement page with user table (role/agency dropdowns) and agency CRUD
- Add readOnly prop to Campaigns (hides create/delete/status-toggle for oversight_admin)
- Add readOnly prop to Settings (disables all ManagementCards, shows view-only banner)
- Pass agencyId to Analytics component for filtered data
- Update urlState with Knowledge Base and User Management views
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add CHECK constraint migration for users.role (super_admin, oversight_admin, agency_admin, basic_user)
- Add get_current_db_user dependency resolving Azure claims to User ORM with agency
- Add require_role() factory and require_write_access() dependency
- Auto-promote dev user to super_admin when DISABLE_AUTH=true
- Add /api/me, PUT /api/users/{id}, POST /api/agencies endpoints
- Apply agency-based data filtering on campaigns, analytics, audit routes
- Block oversight_admin from all mutation routes (campaigns, proofs, flags, resolves)
- Restrict dropdown option mutations to super_admin only
- Add role check in WebSocket handler to block oversight_admin from analysis
- Add CurrentUserResponse, UserUpdate, AgencyCreate schemas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add explicit no-citations instructions to all 5 agent prompts to prevent
Gemini from including page numbers, document names, or source citations
in analysis feedback. These references were unhelpful since the system
doesn't use RAG and users cannot action them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous prompts instructed Gemini to "remove redundancy, marketing
fluff, or content not relevant to..." which caused salient details —
especially unusual, granular, or edge-case instructions — to be lost
from spec output. Rewritten all 5 agent prompts (legal, brand_barclays,
brand_barclaycard, channel_best_practices, channel_tech_specs) to:
- Reframe the task as "restructure and organise" rather than "distil
and filter"
- Add a zero-tolerance detail-loss instruction with concrete examples
of unconventional rules that must be preserved
- Explicitly forbid omitting, summarising away, or paraphrasing
specific rules/values/conditions
- Allow merging only exact duplicates while keeping all unique content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instruct Gemini to begin its feedback with a "Specifications checked"
line recapping the channel, sub-channel, and proof type metadata so
reviewers can confirm the correct specs were applied.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread resolvedItems from App → Campaigns → ProofDetailView → FeedbackReport
→ SubReviewCard so that issues previously marked as resolved are restored
from the database on page load instead of resetting to actionable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
DB-loaded proofs don't have a tempId, so the retry handler's
proof.tempId === tempId check matched all DB proofs (both undefined).
Now the handler uses a matchProof helper that checks both tempId and
_id, and call sites pass proof.tempId || proof._id as the identifier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a proof analysis fails, "Analysis failed." is now a clickable
underlined link that opens a modal showing:
- The lead agent summary explaining why the analysis failed
- Details for each agent that returned an Error status
Applied to both Campaigns and Projects views.
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>
Prevents accidental deletion of knowledge base source documents by
prompting the user to confirm before proceeding.
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>
When the poll request fails (e.g. job not found 404), clear the activeJob
state and stop the interval instead of endlessly retrying. Also refresh
the KB detail to get the current state.
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>
Change from plain black text (font-medium text-black-title) to blue
link styling (font-semibold text-active-blue), matching the Flags and
Errors tabs for visual consistency.
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>