A scoring model trained from zero, and the call infrastructure around it

Sole engineer on an entire B2B and B2C lead-generation product: the architecture, the backend, the front end, the mobile apps, the machine learning and the production servers.

1 scoring model designed, trained and served in-house
2 databases - transactional and analytical
3 Flutter apps across iOS and Android
1 engineer on the whole product
MY ROLE
Everything. There was nobody else on the software.
STACK
Django with REST and GraphQL, PostgreSQL, ClickHouse, Flutter, Ubuntu with Nginx and Docker, a custom VoIP orchestration backend.
HARDEST PART
Replacing a third-party scoring API with something of my own - and being able to show it was better, not merely cheaper.

The situation

A lead-generation business selling into the United States, with a product that had to do three things at once: capture and qualify leads, decide what each one was worth, and route calls between buyers and sellers in real time. When I joined there was no engineering team. There was me.

Lead quality was being scored by a third-party API charged per call. It worked, in the sense that it returned a number. It could not be tuned, its latency was somebody else's problem, its pricing was somebody else's decision, and its edge cases around US telemarketing law were somebody else's interpretation.

The scoring model

I replaced it with a model of our own: designed, labelled, trained, served and evaluated in-house, with the feature pipeline that fed it.

The inputs were the interesting part - device intelligence, line type and carrier lookups, reputation data, behavioural events during the form itself, and consent metadata. Whether a number is a mobile on a major carrier, and whether the person filled the form in eleven seconds or ninety, turns out to say more about lead value than most of what people put in demographic fields.

Bringing it in-house bought four things the vendor could not sell: control over latency, a per-lead cost that dropped with volume instead of rising with it, our own reading of the compliance edge cases, and - most importantly - the ability to decide how confidence should drive routing and pricing. A score you cannot interrogate is a score you cannot act on with any nerve.

The rest of the product

A Django backend exposing both REST and GraphQL, on PostgreSQL for transactional data and ClickHouse for the high-volume analytics and scoring telemetry - two databases because one shape of query does not serve both, and pretending otherwise is how analytics ends up slowing down the thing that earns the money.

The admin back office had JWT APIs that introspect Django's own admin metadata, so adding a model exposed it to the mobile clients automatically. That saved a week each time and, more valuably, removed a class of bug where the mobile app and the web admin disagreed about what a record looked like.

Call infrastructure end to end: several dialler and carrier platforms for outbound and inbound, routing services, a distribution layer between buyers and sellers, and a backend of my own orchestrating the live flow from dial through transfer, recording and disposition. Voice agents on outbound. Continuous scrubbing against do-not-call and telemarketing-consent rules wired into lead acceptance rather than bolted on afterwards, because a compliance check that runs after you have already called is a record of the violation, not a control.

Then the parts nobody counts: a custom CMS, three Flutter applications for iOS and Android covering B2B, B2C and admin, and the production infrastructure - hardened Ubuntu, Nginx, Docker, zero-downtime releases, DNS across two providers, certificates, firewall and intrusion banning.

What it was like being the only engineer

Clarifying, mostly. There is nobody to negotiate an interface with and nowhere for a misunderstanding to hide. But it puts a hard ceiling on how much can be in flight, and it makes documentation an act of self-defence rather than a courtesy - the person who will not remember why this queue has a two-second delay is me, in March.

What I would do differently

I would have built the model's evaluation and monitoring on the same day as the model, not after it was live. It performed well, and I knew that from offline evaluation - but for the first weeks I could not have proven quickly that it was still performing well in production. That gap is uncomfortable and it was avoidable.