Software Engineer · Full-Stack

I build and design systems end to end.

Backend services, distributed data pipelines, and production AI — from the design doc to the migrations to the frontend.

Selected work

01

Distributed report service

Designed & built

30s → under 5s

The problem: reports took ~30s and froze the browser while dozens of data jobs ran one after another.

What I built: an event-driven service that runs the jobs in parallel across isolated workers and streams results to the page as each one finishes, with crash-safe checkpoints so a run resumes where it left off instead of restarting. Load time dropped from ~30s to under 5s, with 80–90% smaller payloads.

Report generation pipeline The Browser sends a request to the Orchestrator, which enqueues a job on the BullMQ job queue and fans it out to parallel Workers. Results are then streamed back to the Browser over a single SSE connection. Browser Orchestrator Job queue (BullMQ) Worker Worker Worker (parallel) streamed back over one SSE connection
Node.js BullMQ Redis Pub/Sub SSE BunnyCDN Docker
02

Billing V2 platform

Sole owner, end to end

The problem: migrate live billing from per-user to per-workspace pricing with add-ons, without dropping or double-charging a single customer.

What I built: the new billing platform end to end — per-workspace subscriptions, capacity add-ons, and an admin console to grant free access and issue credits. The hard part was correctness under load: atomic Postgres locks make concurrent charges and capacity checks race-free, and three independent recovery layers ensure every payment is recorded exactly once.

Billing recovery layers A Stripe event fans out to three recovery layers — immediate write, webhook retry, and a reconcile cron — which all converge on one consistent record. Stripe event Immediate write Webhook retry Reconcile cron One consistent record
TypeScript PostgreSQL plpgsql RPCs Stripe JWT Jest
03

Connector-agnostic report UI

Designed & implemented

The problem: supporting many ad platforms across many report types meant rebuilding the same screens over and over, and a fix on one being forgotten on the rest.

What I built: a plugin system where each report page is written once and every platform plugs in a small handler that declares what it supports through capability flags. Adding a platform is a new folder, not a rewrite, collapsing what would have been N×M pages into one shared set of components.

Connector UI registry pattern A shared contract and registry feeds per-connector handlers, each declaring capability flags, which render through reusable components. Shared contract + registry Connector handler capability flags Connector handler capability flags Connector handler capability flags Reusable components
React Next.js Plugin registry Capability flags
04

Spend Guard

Designed & prototyped

The problem: advertisers quietly waste budget on “fatigued” ads that have passed their peak, but spotting which ones — and how much is being wasted — means reading each ad’s daily performance curve by hand.

What I built: an algorithm that finds them automatically. It scans active ads against account-wide baselines to flag suspects cheaply, then runs a time-series fatigue-curve analysis on each one — detecting the performance peak and measuring the post-peak decay to estimate wasted spend in euros — and ranks what to pause, with a validation pass that strips out false positives. Validated on real customer data and pitched to leadership.

Ad fatigue detection pipeline Active ads are screened against account baselines, then a fatigue-curve analysis detects each peak and measures the post-peak decay to estimate wasted spend, which is validated and ranked into a pause list. Active ads Baseline scan flag suspects Fatigue curve peak detection post-peak decay Wasted spend € estimated per ad Validate drop false positives Pause list ranked by waste
TypeScript Time-series analysis Meta Ads API Fatigue curves
05

Smart Tagging ETL

Designed & built

The problem: before ad creatives can be auto-tagged by AI, thousands of Meta images and videos have to be fetched and normalized into one consistent, AI-ready format at scale — without redoing work or falling over on flaky source APIs.

What I built: the media-ingestion pipeline that feeds the tagging AI. A three-stage producer/worker ETL on a Redis-backed queue: it resolves each asset’s real source URL from Meta, normalizes it on a GPU (a resized JPEG for images, an 8×8 frame mosaic for videos), uploads the result to the CDN, and marks each row ready for the tagging model to consume.

Media ingestion ETL pipeline A producer batches assets onto a queue, workers resolve each Meta source URL and normalize the media on a GPU, then the result is uploaded to the CDN and marked ready for AI tagging. Producer batches assets Extract resolve Meta URL Transform (GPU) resize · 8×8 mosaic BullMQ queue queue normalized asset Upload to CDN Bunny CDN Marked ready for AI tagging
Node.js BullMQ Redis sharp ffmpeg GPU Bunny CDN
06

RESVA — real-estate CRM

Built end to end · part-time

The problem: a US real-estate firm was routing leads from cold-callers to agents by hand, with no fair distribution and no visibility into which agents actually converted.

What I built: a CRM that captures leads and hands them to agents automatically — round-robin via Sideqik so the load is even — classifies each lead’s status as it moves through the funnel, and scores agents on performance.

Lead routing and scoring flow Leads from cold-callers enter the CRM, are distributed round-robin through Sideqik to agents, then each lead’s status is classified through the funnel and agents are scored on what they convert. Lead sources cold-callers CRM captures leads Round-robin Sideqik even assignment Agents assigned in turn Lead status classified in funnel Agent scoring conversion rate
Vue.js Ruby on Rails PostgreSQL Sideqik

Side projects

StenryAI

Co-built · In progress

A QA tool for call centres that does more than score calls after the fact: it transcribes a live call and separates speakers in real time, then coaches the agent during the call with live hints while scoring them against a custom rubric. Transcription and diarization run locally (whisper.cpp, pyannote); the scoring and coaching layer was benchmarked across local and cloud models (Ollama llama3.1, OpenAI, Gemini). Co-built with my partner to prove the concept, not for commercial use.

StenryAI live coaching loop Audio from a live call is transcribed, speakers are separated, then scored against a rubric. Scoring produces a QA score, and also feeds live hints back to the agent in real time, during the call. Live call audio streaming Transcribe whisper.cpp · local Separate speakers pyannote · local Score vs rubric Ollama llama3.1 · OpenAI · Gemini (benchmarked) QA score Live hints (real-time, during the call)
whisper.cpp pyannote LLM

France Travail MCP

concept proven

An MCP server that lets AI assistants (Claude, ChatGPT, Cursor) query live French job-market data — listings, salary benchmarks, and hiring trends — through the standard tool-use interface they already speak. Deployed on Cloudflare Workers, not yet public. It's the same tool-use pattern enterprise AI copilots are built on.

France Travail MCP flow An AI assistant talks to an MCP server on Cloudflare Workers, which calls the France Travail API. AI assistant MCP server (Cloudflare Workers) France Travail API
MCP Cloudflare Workers France Travail API

canucode.lol

Live

A browser arcade of quick brain games for developers — a code-typing sprint, a memory grid, a colour match — with a global leaderboard. Shipped solo from idea to live domain in a weekend; two organic posts, zero ad spend, 200+ players across 16 countries.

canucode.lol flow The game client sends scores to a Supabase leaderboard. Game client (React / Vite) Supabase leaderboard
React Vite Supabase

Intern

Built end to end

The problem: routine coding tasks — small features, fixes — eat focus time, but handing them to an AI blindly means unreviewed code and runaway cost.

What I built: an autonomous pipeline that takes a task from Notion and orchestrates specialized agents to plan it, implement it on a branch, run the tests, and review the diff — then opens a merge request for me to approve. Each phase runs in an isolated context to stay cheap and avoid quality loss, with a live cost budget and a human approval gate before anything is pushed.

Task automation pipeline A Notion task moves through plan, implement, validate and review phases, then a human approval gate before a merge request is opened. Notion task the brief Plan agent Implement on a branch Validate tests isolated context per phase Review the diff Approval gate human Merge request
TypeScript Claude Agent SDK Multi-agent orchestration Notion API GitLab

Skills

Languages
TypeScript JavaScript Python SQL Ruby
Backend
Node.js NestJS Express FastAPI Ruby on Rails Celery / RabbitMQ
Frontend
React Next.js Vue.js
Data & infra
PostgreSQL Supabase Redis / BullMQ BigQuery GCP / GKE Docker GitLab CI
AI / LLM
Gemini multimodal LLM evaluation MCP servers PyTorch / Captum
Spoken
English — fluent French — A2

Get in touch

I’m always happy to talk about systems, AI, or interesting problems.