
Romain CAILLE
Fullstack developer, ready for the agentic era.
- status
- Open to work · available now
- location
- Remote or relocation · from Nantes, France
- experience
- Spotime · 2025–nowEnedis, fullstack apprentice · 2023–2025U Tech, fullstack apprentice · 2022–2023
- education
- Master's, IT & Information Systems · EPSI
- english
- C1
- resume
- resume.pdf
Each project is an event. It goes through three stages.
Collection: what enters the system, as is.
Processing: validation, enrichment, decisions. This is where the trade-offs live.
Exposure: APIs, dashboards, what others consume.
Spotime
problemField crews of 10 to 30 people, spread across sites, track hours on paper. Every month-end turns into hours of manual re-entry before payroll can start. Spotime replaces that with three surfaces: a web back office for admin and scheduling, an employee mobile app (iOS/Android) and an Electron desktop app for on-site clock-in, published on the Windows Store.
decisionClock-ins are stored local-first and synced as soon as the network returns, with no data loss or duplicates. GPS location is captured only at the moment of clock-in. Development runs with a team of subagents (PO, architect, tester, developer, reviewer), framed by user stories, a DoR/DoD and TDD.
+ trade-offs & what broke− collapse
trade-offCold email to construction SMEs wasn't landing, so I switched to cold calling. Traded reach for signal quality, on purpose.
what broke / learnedConstruction still showed no traction, even on direct calls. Pivoting to personal care services proved more receptive, with a client coming in inbound. Testing the channel first, then the market, beats rewriting the pitch.

1 / 2Two paying clients: one in agriculture, and one in personal care services who came in inbound.
Fraud Engine
problemLearn Go and event-driven design on a case where event ordering matters: fraud detection. The pipeline has to absorb growing load and scale horizontally without code changes.
decisionThree independent Go binaries (generator, scorer, sink) linked by Kafka, partitioned by card_id so a card is always scored by the same worker. Redis holds per-card velocity in a sliding window, ClickHouse stores scored transactions. Adding a scorer only triggers a partition rebalance.
+ trade-offs & what broke− collapse
trade-offWhen its 1,000-slot buffer is full, the generator drops messages instead of blocking. Acceptable for a load test, since the ceiling stays visible in metrics. On a real payment pipeline, losing an event before the fraud check is not: backpressure or a persistent buffer instead.
what broke / learnedThe first version ran 3 scorers at ~3 tx/s. kafka-go's default 1s BatchTimeout capped writes at about one message per second, and each Redis command was its own round-trip. Explicit batching (size or timer, first one wins) and a pipelined Redis call fixed it: one optimized scorer now keeps up with a generator alone.
2 generators · 1 → 3 scorersLive scaling: with 1 scorer for 2 ramping generators, lag climbs to ~410 messages (~8s of traffic). Starting 2 more scorers drains it to 0. Three scorers handle ~220 tx/s combined.
Event Hub
problemWeb analytics count visitors. Stripe counts money. Nothing joins the two, so teams selling through Stripe can't tell which source, campaign or page actually brings revenue without a data warehouse or a tag manager.
decisionOne endpoint, POST /v1/events, for every source: browser SDK, backend, CI, Stripe webhooks. The server derives the fields it guarantees (source, session, visitor); the client only sends jsonb context. Payments link back to the first visit through a visitor id in Stripe metadata. The core never knows what an event type means, and a test fails the build if it does.
+ trade-offs & what broke− collapse
trade-offCookieless by default: the visitor key is an HMAC that rotates daily, and IP and User-Agent are never stored. Enough to count, not to follow, so renewals and LTV only attribute in consented mode. Privacy first, attribution depth second.
what broke / learnedOne Stripe payment fires four webhooks that all carry an amount. Mapping them to disjoint event types and reading one precise type keeps the same euro from being counted four times. In tests, ad-hoc supertest ports failed once every ~66,000 requests on a random suite; a shared harness with one server per suite fixed it.




1 / 5Working proof of concept: web analytics and Stripe revenue attribution shipped, with traffic, revenue and deploy markers on one chart. Next: a per-project ingestion cap and cohort views in the dashboard.
This is what I do. Let's talk.
