Architecture Overview
Repository Structure
Butler Series consists of 3 repositories:
| Repository | Role | Tech |
|---|---|---|
butler-platform | Auth, billing, admin, docs | Next.js + FastAPI |
butler-tax | Accounting SaaS | Vue.js + FastAPI |
butler-law | Legal Q&A (tax/labor) | Next.js + FastAPI |
Technology Stack
Butler Tax
- Frontend: Vue.js 3 (Composition API) + Vite (port 3001)
- Backend: FastAPI (port 8001)
- Business Data: MongoDB (
butler_taxdatabase) - PII Data: Firestore (
company_profiles,bank_accounts,clients) - Auth: Firebase Auth via Butler Platform
- AI Chat: Anthropic Claude Sonnet 4.6
- OCR: Gemini 2.5 Flash
Butler Platform
- Auth Frontend: Next.js (port 3003)
- Studio Admin: Next.js (port 3004)
- Platform API: FastAPI (port 8003)
- Database: MongoDB (
butler_platform) + Firebase Admin SDK
Butler Law
- Frontend: Next.js (port 3002)
- Backend: FastAPI (port 8002)
- Database: MongoDB (
butler_law) — also used as vector store via numpy cosine similarity - Embedding model:
paraphrase-multilingual-mpnet-base-v2(local)
Data Architecture
MongoDB Collections (Business Data — Butler Tax)
| Collection | Purpose |
|---|---|
corporates | Corporate entity profiles |
employees | Employee records |
receipts | Expense receipts |
invoices | Issued & received invoices |
transactions | Bank transactions |
matches | Reconciliation records |
audit_logs | Operation audit trail |
approval_rules | Approval workflow configurations |
matching_rules | Reconciliation rules |
journal_rules | Journal entry rules |
permission_settings | Dynamic role-based permissions |
Firestore Collections (PII Data — Butler Tax)
| Collection | Purpose |
|---|---|
company_profiles | Company profiles with PII (address, phone, registration number) |
bank_accounts | Bank account details (account number, holder name) |
clients | Client/vendor master with contact info |
All Firestore documents include a firebase_uid field for scoping.
Key Design Patterns
CorporateContext
All Butler Tax API routes use Depends(get_corporate_context) to scope data access to the authenticated corporate entity. The context exposes:
corporate_id— MongoDB document IDfirebase_uid— Firebase Auth UIDrole— employee role (staff/approver/accounting/manager/admin)db— MongoDB connectionfs— Firestore client (lazy-initialized)is_tax_firm_proxy— true during tax firm impersonationactual_uid— original tax firm UID during impersonation
Tax Firm Impersonation
Tax firms can act on behalf of their client companies via the ?as_corporate={firebase_uid} query parameter. The backend validates the advising_tax_firm_id relationship and fixes the role to accounting. All proxy actions are recorded in audit_logs with is_tax_firm_proxy: true.
Role Hierarchy
| Role | Rank | Access Level |
|---|---|---|
staff | 1 | Basic access, submit documents |
approver | 2 | Approve documents within scope |
group_leader | 2 | Same rank as approver |
manager | 3 | Department-level management |
accounting | 4 | Full accounting access |
admin | 5 | Full administrative access |
Lazy Firestore Initialization
The Firestore client is wrapped in LazyFirestore so that firestore.client() is only called on the first .collection() access. Routes that don't touch PII data continue to work even when Firebase credentials are not configured locally.
Butler Chat Format
Butler Chat uses an embedded text format for tool calls:
[[TOOL:tool_name]]
human-readable confirmation message
[[/TOOL]]
[[TOOL_PAYLOAD:{"key":"value"}]]The frontend parses this and renders a confirmation UI before calling POST /advisor/tools/{tool_name} with confirmed: true. Migration to Anthropic's native Tool Use API is a future task.
