MinTax - Enterprise Tax & Expense Analysis Platform
Solo-built offline-first desktop application for UK tax professionals featuring real-time analysis of 64-column expense datasets, AES-256-GCM encrypted data, and WebSocket streaming. Serving 500+ monthly users and generating $180K+ annual revenue.
Overview
I was the sole developer on MinTax, a production-grade desktop application used by UK tax professionals and accountants to perform complex tax calculations, analyze expenses, and generate HMRC-compliant reports. Every line of code, every architecture decision, every deployment pipeline was mine.
The core challenge: take messy Excel files with 64+ expense columns and thousands of rows, run real-time tax categorization across P11D, PSA, and PAYE frameworks, and deliver results in under 2 seconds, all while keeping sensitive financial data encrypted and the entire system running 100% offline.
What I Owned (Solo Developer Scope)
As the only engineer on this project, I was responsible for every layer of the stack, from pixel to deployment:
Why I Chose This Stack
Every technology choice was driven by the specific constraints of this project: offline-first, security-critical, data-heavy.
Svelte 5 (Runes)
With 64+ columns streaming via WebSocket, I needed fine-grained reactivity without virtual DOM overhead. Svelte 5's $state and $derived runes give me surgical updates: only the cells that change re-render, not the entire table.
Python + FastAPI
Pandas is unmatched for tabular data manipulation at this scale. FastAPI gave me async endpoints and native WebSocket support with minimal boilerplate, critical for streaming analysis results in real-time.
Electron + WebContentsView
The client needed cross-platform desktop deployment with offline capability. WebContentsView (not BrowserView) provides true process isolation per tab, essential for preventing data leakage between client accounts.
SQLite + Field-Level AES
An embedded database removes server dependencies for offline-first architecture. Field-level encryption (not full-disk) means I can query non-sensitive columns at full speed while keeping financial data encrypted at rest.
Technical Architecture
The application employs a multi-layer architecture with strict security boundaries between each layer:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRESENTATION LAYER β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β SvelteKit + β β Tailwind CSS β β WebContentsView β β
β β Svelte 5 β β Styling β β Multi-Tab β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
IPC Communication
(Context Isolated)
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β Electron Main β β License β β Auto-Update β β
β β Process β β Management β β System β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
Encrypted IPC + Session Keys
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DATA LAYER β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β SQLite + AES β β Knex.js β β Secure β β
β β Encryption β β Migrations β β Repositories β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
AES-256-GCM Encrypted API
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ANALYSIS ENGINE (Python FastAPI) β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β Multi-threaded β β WebSocket β β Pandas β β
β β Processing β β Streaming β β Data Engine β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Technology Stack
Frontend
Desktop Platform
Backend API
Database
Security
DevOps & Testing
Key Features & Capabilities
Multi-Tab Navigation System
Implemented a browser-like tabbing system using Electronβs WebContentsView API. Each tab operates as an isolated renderer process with its own lifecycle, enabling users to work across multiple companies and reports simultaneously without data cross-contamination.
- Custom tab management with dynamic creation, destruction, and state persistence
- Secure context isolation between tabs preventing cross-tab data leakage
- Full back/forward navigation history per tab with URL-based state preservation
- Graceful tab recovery and memory management preventing leaks
Real-Time Analysis Engine
Developed a Python-based expense analysis engine that processes complex Excel files (64+ columns) with real-time streaming updates. The engine categorizes expenses into taxable, non-taxable, and excluded categories while identifying review points requiring human attention.
- WebSocket-based real-time progress streaming with batch data updates
- Intelligent word-matching algorithms for automated expense categorization
- Progressive result streaming enabling live UI updates during analysis
- Multi-threaded processing for parallel computation on large datasets
- Background workers ensuring non-blocking UI during calculations
Enterprise-Grade Security Architecture
This wasnβt just a technical checkbox. The client marketed MinTax to high-tier UK accounting firms. HMRC compliance required that sensitive financial data (employee names, PAYE references, salary details) be encrypted at rest and in transit. Without this security posture, the app couldnβt be sold to enterprise clients. The encryption layer directly enabled the $180K+ revenue stream.
Implemented comprehensive security measures including end-to-end encryption for all API communications, field-level database encryption for sensitive data, and secure session key management.
- AES-256-GCM encryption with PBKDF2 key derivation (100,000 iterations)
- Session-based encryption keys regenerated per application launch
- Cross-platform WebCrypto implementation matching browser and Node.js APIs
- Encrypted electron-store for secure local configuration storage
- License verification system with hardware fingerprinting
Advanced Data Table with Dynamic Filtering
Built a high-performance, Excel-like data table component handling thousands of expense records with automatic type detection, dynamic filter generation, and real-time updates from streaming WebSocket data.
- Automatic column type detection (currency, dates, booleans, categories)
- Smart filter generation: range sliders for numbers, dropdowns for categories
- Configurable column visibility with 64+ available data columns
- Lazy loading and pagination for optimal performance with large datasets
- Caching layer for frequently accessed calculation results
Professional Report Generation
Comprehensive reporting system generating client-ready documents for tax compliance and analysis review.
- Multiple export formats: PDF, Excel (XLSX), and CSV
- Detailed expense breakdowns by category, entity, and tax year
- Review point summaries highlighting items requiring attention
- Tax position reports for P11D, PSA, and PAYE compliance
Technical Challenges & Solutions
Challenge 1: Cross-Platform Encryption Compatibility
The Challenge
The Python backend initially used Fernet encryption (AES-CBC) while the browser frontend used WebCryptoβs AES-GCM. These incompatible encryption methods caused decryption failures and 400 errors across the entire communication layer.
The Solution
Implemented a unified AES-256-GCM encryption layer across all three platforms (Python, Node.js/Electron, Browser) using identical PBKDF2 parameters: same hashing algorithm (SHA-256), same iteration count (100,000), matching salt values. Created a custom EncryptionManager class in Python using cryptography.hazmat primitives, and leveraged WebCryptoβs subtle API for consistent encrypt/decrypt operations on the frontend.
# Python AES-GCM Encryption (matching browser WebCrypto)
class EncryptionManager:
def __init__(self, encryption_key: str):
self.salt = b'electron_api_salt_2024'
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32, # 256 bits
salt=self.salt,
iterations=100000,
)
derived_key = kdf.derive(encryption_key.encode('utf-8'))
self.aesgcm = AESGCM(derived_key)
def encrypt_data(self, data: dict) -> str:
nonce = os.urandom(12)
json_bytes = json.dumps(data).encode('utf-8')
ciphertext = self.aesgcm.encrypt(nonce, json_bytes, None)
return base64.b64encode(nonce + ciphertext).decode('utf-8')// TypeScript AES-GCM Decryption (matching Python EncryptionManager)
class CryptoService {
private key: CryptoKey;
async deriveKey(password: string): Promise<CryptoKey> {
const salt = new TextEncoder().encode('electron_api_salt_2024');
const keyMaterial = await crypto.subtle.importKey(
'raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false, ['decrypt']
);
}
async decrypt(encrypted: string): Promise<object> {
const data = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));
const nonce = data.slice(0, 12); // First 12 bytes = nonce
const ciphertext = data.slice(12); // Rest = ciphertext + tag
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: nonce }, this.key, ciphertext
);
return JSON.parse(new TextDecoder().decode(decrypted));
}
}Challenge 2: Encrypted Database Migrations
The Challenge
Traditional SQL migrations couldnβt modify encrypted data. When consolidating duplicate payroll element records (a schema change), the encrypted data couldnβt be read or modified directly via SQL. Users with existing data would lose their configurations on upgrade.
The Solution
Developed a two-tier migration strategy: schema-level migrations via Knex.js for structural changes, and application-level data migrations that run through the SecureDbService repository layer. The data migrations read encrypted records, decrypt them, perform consolidation logic, and re-encrypt, all during application startup with comprehensive logging and rollback capabilities.
// Step 1: Schema-level migration: add the consolidated column
exports.up = function(knex) {
return knex.schema.alterTable('payroll_elements', (table) => {
table.text('merged_custom_types'); // New column for consolidated data
table.boolean('is_consolidated').defaultTo(false);
});
};// Step 2: Application-level migration: runs through encrypted repo layer
async function consolidatePayrollElements(companyId: string) {
// Auto-decrypts on read via SecureDbService
const elements = await payrollRepo.getByCompanyId(companyId);
const consolidated = groupByCategory(elements);
for (const [category, items] of Object.entries(consolidated)) {
const mergedTypes = items.flatMap(i => i.custom_types);
// Auto-encrypts on write via SecureDbService
await payrollRepo.update(items[0].id, {
custom_types: mergedTypes,
is_consolidated: true
});
// Delete duplicates β encrypted data handled transparently
for (const item of items.slice(1)) {
await payrollRepo.delete(item.id);
}
}
}Challenge 3: Real-Time WebSocket Streaming with Encryption
The Challenge
Streaming analysis results required maintaining encryption while processing thousands of rows. Excel formula strings (like =A1*B1) were appearing in output instead of calculated values, and JSON serialization was failing for NumPy data types.
The Solution
Implemented an analysis stream callback architecture: each batch is encrypted before transmission over WebSocket, with formula detection and recalculation logic for affected columns. Created custom JSON serializers handling NumPy types, NaN values, and pandas Timestamps. The WebSocket client auto-reconnects and maintains encryption state across connection drops.
# Streaming encrypted analysis results over WebSocket
async def stream_analysis_batch(websocket, batch_data, encryption_manager):
# Recalculate formula columns β Excel formulas don't transfer as values
if "Taxable Amount (Β£)" in batch_data.columns:
batch_data["Taxable Amount (Β£)"] = (
df["Expense Amount (GBP)"].values * df["%"].values
)
# Handle NumPy/Pandas types that break json.dumps
clean_data = batch_data.replace({np.nan: None}).to_dict(orient="records")
# Encrypt batch and stream β client decrypts with matching AES-GCM key
encrypted = encryption_manager.encrypt_data(clean_data)
await websocket.send_json({"encrypted": encrypted, "progress": batch_index / total})Challenge 4: Multi-Tab UI Performance with Heavy Components
The Challenge
Multiple tabs with heavy data tables (ExpensesDataTable with 64+ columns) were all mounting simultaneously, causing memory bloat and sluggish performance. Hidden tabs still consumed resources and reacted to state changes.
The Solution
Implemented dynamic component mounting using Svelte 5βs reactivity system: only the active tabβs component is mounted at any time. Switching tabs destroys the previous component and mounts the new one, with state preservation using URL parameters and parent component state.
<!-- Dynamic component mounting: only active tab exists in DOM -->
<script lang="ts">
let activeTab = $state('expenses');
// Components lazy-load and mount only when their tab is active
// Previous tab is fully destroyed, freeing memory
const tabComponents = {
expenses: () => import('./ExpensesDataTable.svelte'),
summaries: () => import('./SummariesTab.svelte'),
review: () => import('./ReviewPointsTab.svelte'),
};
</script>
{#key activeTab}
{#await tabComponents[activeTab]() then module}
<svelte:component this={module.default} {data} />
{/await}
{/key}Database Architecture
The application uses a normalized relational database design with field-level encryption for sensitive data. The SecureDbService layer automatically encrypts/decrypts designated fields transparently to the application logic.
Core Entities
| Entity | Purpose | Encrypted Fields |
|---|---|---|
| Companies | Client organizations | name, industry |
| Entities | Employing entities with PAYE references | name, paye_reference, office_locations |
| Reports | Analysis reports with tax year data | name, expenses_data, expenses_data_result |
| PSA Categories | PAYE Settlement Agreement categories | name |
| Payroll Elements | P11D and Payroll benefit types | type, custom_types |
| Annual Events | Company events for categorization | name, custom_name, descriptions |
| Analysis Positions | Tax positions for specific expense types | late_night_taxis, working_lunches, incidental_expenses |
| Long Service Awards | Service award configurations | serviceAwardType |
| Client Gifts | Gift reporting configurations | β |
| Incidental Expenses | Expense reimbursement settings | β |
Entity Relationships
Companies (1) ββββββ¬βββββ (*) Entities
ββββββ (*) Reports ββββββ (*) Entity_Reports (junction)
ββββββ (*) PSA_Categories
ββββββ (*) Payroll_Elements
ββββββ (*) Annual_Events
ββββββ (*) Analysis_Positions
ββββββ (*) Long_Service_Awards
ββββββ (*) Client_Gifts
ββββββ (*) Incidental_Expenses
Performance Optimization
Background Workers
Non-blocking UI during long-running calculations using Web Workers and async processing
Caching Layer
Frequently used calculation results cached to prevent redundant processing
Lazy Loading
On-demand data and component loading for large datasets
Virtual Scrolling
Only visible rows rendered in large data tables
Batch Processing
Parallel computation for multi-file analysis operations
Connection Pooling
Efficient database connection management via Knex.js
What Users Say
We used to spend half a day reconciling expense spreadsheets for a single client. MinTax does it in seconds and catches categorization issues we'd miss manually. It's completely changed how we handle P11D season.
Results & Impact
Growing user base of UK tax professionals and accountants across firms of all sizes
Subscription-based revenue generated for the client β enabled by enterprise security posture
Down from ~4 hours of manual Excel work per client β a 99.98% efficiency gain
Verified calculation accuracy across all tax categories (P11D, PSA, PAYE)
Project Metrics
| Category | Metric |
|---|---|
| Codebase | 10+ database tables with migrations |
| 15+ Svelte components | |
| 20+ IPC handlers | |
| 64 expense data columns | |
| Full TypeScript coverage | |
| Features | Multi-tab WebContentsView system |
| Real-time WebSocket streaming | |
| AES-256-GCM encryption layer | |
| License verification system | |
| Dynamic filtering engine | |
| Performance | Complex calculations in <2 seconds |
| Handles 10,000+ row datasets | |
| 99.9% calculation accuracy | |
| Business Impact | 500+ active monthly users |
| $180K+ annual subscription revenue | |
| HMRC-compliant security posture |
Skills & Competencies Demonstrated
Full-Stack Development
- Frontend: Svelte 5 with runes-based reactivity (
$state,$derived,$effect) - Backend: Python FastAPI with async endpoints and WebSocket support
- Desktop: Electron with process isolation and secure IPC patterns
- Database: SQLite with Knex.js migrations and repository pattern
Security Engineering
- Cryptographic implementation: AES-256-GCM, PBKDF2, session key management
- Cross-platform encryption compatibility (Python β Node.js β Browser)
- Secure architecture: Context isolation, sandboxed preload scripts
- Data protection: Field-level encryption, secure local storage
Software Architecture
- Monorepo management with npm workspaces
- Clean separation of concerns across packages
- Repository pattern for data access abstraction
- Event-driven architecture for real-time updates
Data Engineering
- Complex Excel processing with 64+ column datasets
- Pandas data transformation and analysis pipelines
- Real-time streaming data processing with WebSockets
- Dynamic type detection and intelligent filtering systems
DevOps & Tooling
- CI/CD pipelines with GitHub Actions
- Cross-platform builds with electron-builder
- E2E testing with Playwright
- Auto-update distribution system
Conclusion
MinTax was a solo effort across every layer of a production-grade desktop application, from cryptographic primitives to pixel-perfect data tables. It demonstrates that I can take a complex, security-critical, data-heavy product from architecture to deployment, alone, and deliver software that 500+ professionals rely on daily.
Built solo. Shipped to production. Serving 500+ users. Generating $180K+ in annual revenue.
Want to Work on Something Similar?
I'm available for freelance projects and full-time opportunities. Let's build something amazing together!