White-label firms (Phase 2.5)¶
What¶
A Firm record holds the audit firm's branding profile: display name, logo, primary/accent colors, methodology disclaimer, footer text, confidentiality notice, and per-firm engagement defaults (default archetype, default budget). Engagements reference a firm via firm_id; deliverable rendering looks up the firm at render time.
The result: cover page says "Prepared by Acme Audit Partners, LLP — Defense compliance · Pittsburgh", the logo embeds at the top of markdown / DOCX exports, the methodology page appends the firm's disclaimer, every page footer carries the firm's confidentiality notice. The end client sees a partner deliverable, not an AI report.
Why this matters¶
Without white-label, the deliverable cover says "Audit Findings — [Client Name]" with no firm identity — a partner can't put their reputation on it and can't sell it to their client. White-label is the gate to first paid engagement.
Data model¶
class Firm:
id: str # firm-<hex>
display_name: str # "Acme Audit Partners, LLP"
short_name: str # "Acme" — for tight footers
tagline: str # "Defense compliance · Pittsburgh"
logo_url: str # public HTTPS URL; firm hosts on their CDN
primary_color: str # cover heading bars (hex)
accent_color: str # callouts / highlights (hex)
methodology_disclaimer: str # appended to methodology section
footer_text: str # cover + page footer
confidentiality_notice: str # rendered above exec summary
default_archetype: str
default_budget_cents: int
Persistence¶
S3-backed lazy-load with best-effort upload, mirrors EngagementStore pattern.
In-memory cache, persisted on every mutation. Local cache at ${CACHE_DIR}/auditforge/firms.json for fast reads.
Default firm¶
When an engagement has firm_id=None or its referenced firm has been deleted, the deliverable renderer falls back to a hardcoded firm-default with neutral AuditForge branding ("Generated by AuditForge"). This means no engagement ever fails to render due to firm-lookup error.
Endpoints¶
| Method | Path | Purpose |
|---|---|---|
| GET | /auditforge/firm |
List all firms |
| POST | /auditforge/firm |
Create a new firm |
| GET | /auditforge/firm/{firm_id} |
Get one |
| PUT | /auditforge/firm/{firm_id} |
Partial update — only sent fields apply |
| DELETE | /auditforge/firm/{firm_id} |
Remove (engagements with that firm_id fall back to default branding) |
All gated by x-admin-token. See api-reference.md for full request/response shapes.
Frontend¶
FirmManagement.tsx is a CRUD UI accessible via the "Firms" tab in the AuditForge SPA shell. Color pickers (HTML5 color input + hex text input), logo URL preview, methodology / footer / confidentiality textareas, archetype + default budget pre-fills.
NewEngagementForm.tsx reads from listFirms() and shows a dropdown when firms exist; selecting a firm applies its default_archetype and default_budget_cents to the form. Falls back to a free-text input pre-filled with firm-default when no firms are configured.
Render integration¶
app/auditforge/report.py calls get_firm_store().get_or_default(engagement.firm_id) at the top of each builder (build_markdown_deliverable, build_docx_deliverable, build_json_deliverable) and applies branding:
- Markdown: logo image embed → "Prepared by [Firm] — [tagline]" → confidentiality notice as blockquote → at end of methodology, append disclaimer; end of doc, italic footer
- DOCX: logo placeholder (image upload to be wired in Phase 2.6 polish), prepared-by paragraph with firm color accent, confidentiality notice italic, methodology disclaimer italic, footer paragraph italic
- JSON: top-level
firmblock carrying id, display_name, tagline, logo_url, primary_color, footer_text — for downstream tooling that wants to apply its own rendering layer
Code¶
app/auditforge/firm.py— model + storeapp/auditforge_endpoints.py— five CRUD endpointsapp/auditforge/report.py— branding integration in all three deliverable formatsfrontend/src/components/FirmManagement.tsx— UIfrontend/src/api/auditforge.ts— typed API client
Open polish items¶
- DOCX logo embedding (currently text-only "Prepared by"; image upload requires async fetch + python-docx image insert)
- Custom font selection per firm
- Cover page page-break separator (DOCX has implicit page-break-before-heading; markdown doesn't)
- Firm-level user accounts (currently the admin token is shared across all firms)