Two-part Systems Engineering capstone, team of three. First PrintScript, a programming language written from scratch in Kotlin: streaming lexer, AST parser, interpreter, configurable formatter and a static analyser driven by YAML and JSON rules, built on SOLID and GoF patterns. Then the platform around it, 8 independent services (snippet, execution, static analysis, user, gateway, UI, infra, asset) with Auth0 authentication, deployed on Azure across separate dev and prod VMs. Docker CI/CD wired so touching one service rebuilds that container in place while touching infra rebuilds everything, with branch-based continuous delivery through Gradle.
Automations that
survive production.
I build the unglamorous half of software: the integrations, the fallbacks and the checks that decide whether a workflow still runs at 3 a.m. on a Tuesday. Day to day that means n8n and Zapier pipelines against HubSpot, Salesforce and Pipedrive at Darwin AI. On my own time it means shipped products, cloud infrastructure and LLM integrations built to degrade gracefully instead of failing loudly.
What I work with
AI & Automation
Languages
Web & Frameworks
Cloud & DevOps
Data
Integrations & Auth
Security
Quality & Practices
Design & Frontend craft
IoT & Embedded
Selected projects
Collaborative Lab 2 project built with three fifth-year students acting as product owners. React front end, NestJS back end, deployed on Azure. I contributed across the whole stack, front end, back end and database, with primary ownership of cloud infrastructure and deployment.
A billing application built solo in Python to replace a legacy Microsoft Access system that had outlived its usefulness. Migration off Access is a one-time startup step, read straight from the .mdb with access_parser, so at runtime the app needs neither Access nor any ODBC driver. Structured as a monorepo around a UI-agnostic core (SQLAlchemy 2.0 over SQLite in WAL mode) shared by two clients: a PySide6 desktop app and a Flet Android app. One domain layer, two very different front ends, no duplicated business rules.
Shipped for real: signed Android APKs per ABI and a Windows installer via PyInstaller and Inno Setup, with a stable release key so updates install over the previous version without wiping user data. Correctness is held down by BDD where the Gherkin scenarios are the business rules themselves, so a rule that changes has exactly one obvious place to change, plus Appium visual end-to-end runs against the APK on an emulator.
The 72 group-stage matches do not fit in anyone's calendar. Scout sorts them per user into unmissable, worth it, and just watch the highlights, and justifies every call. The obvious move was to let an LLM recommend. I rejected it: not explainable, not reproducible, does not learn. Instead the engine is an explainable Bayesian model running client-side. Eight normalised features per match, each weight a normal distribution, so the score carries its own uncertainty: a high but unconfident score never reaches unmissable, while a mid score whose optimistic bound clears the cut gets promoted, upper-confidence-bound style. Every thumbs up or down updates the posterior through a diagonal Kalman filter with credit assignment, so a complaint about kickoff time does not punish the features about football.
Verified rather than assumed: a test suite that pins the maths down, the features, timezone conversion, classification thresholds and credit assignment, plus a live accuracy metric in the UI measured against cold-start priors so the model cannot grade itself on what it already learned.
Handing a whole repository to an LLM is expensive and noisy. Wasp inverts the flow: deterministic tools find candidates, the model only spends tokens confirming and explaining the high-value ones. A Rust CLI walks the repo respecting .gitignore, orchestrates proven OSS scanners (Semgrep, Gitleaks, Trivy), then normalises and compresses everything into a compact JSON. A companion Claude Code skill does the triage: it reads only the flagged fragments, discards false positives, maps findings to OWASP and CWE, assigns severity and proposes remediation.
wasp doctor. An incremental cache fingerprints every file and re-scans only what changed, which took a large monorepo from about 142 seconds down to 6. CI mode emits SARIF and exits 2 above a severity threshold, so it can block a pipeline. Developed under strict TDD, with tests that parse fixture output and therefore need none of the scanners installed.
Year-long final-year capstone delivered for a real client: a web platform that benchmarks AI models and agents across different harnesses and tool sets, so results are comparable instead of anecdotal. The interesting problems are the ones a leaderboard never shows, namely fair harness parity, run isolation, cost and latency accounting, and making a stochastic result reproducible enough to defend.
Also built
Where I work
Darwin AI is an AI-powered sales automation platform that plugs into clients' CRMs to qualify leads and run follow-ups. I am the technical bridge between the product and its customers: I design the automation workflows, own the CRM integrations, fix what breaks in production, and lead technical onboarding from kick-off to go-live.
Landings and web apps, end to end
Alongside my job I take freelance work: landing pages and web applications from blank page to deployed, by one person. Two things I do differently. I do not send a single design and then defend it, I put up several proposals so you decide looking at real options instead of a description. And the visual language gets built for the project rather than pulled off a shelf: icons drawn for the brand, type chosen on purpose, none of the default fonts and stock illustration that make a page instantly readable as machine-generated.