SOLO BUILD BY ARIEL MAGALSO · LIVE PIPELINE — NOT A SCRIPT

Verdict.

//ARCHITECTURE

How Verdict works

Eleven stages, run in this order for a reason: identity resolution before spend, extraction before verification, verification before scoring, scoring before a draft is ever written.

Validate + idempotency

00

Ordinary code. Required fields, email/domain normalization. Replaying a submission_id returns the existing result — never reprocesses.

Identity resolution

01

Runs BEFORE enrichment/scoring, deliberately. Exact email or domain match only counts as "confident" — name similarity, even a perfect score, never auto-merges.

Classify message

02

Haiku labels the message into approved categories. Vendor pitches, job applications, and student research hard-disqualify here, before any research spend.

Find research source

03

Looks up a domain in the seeded fixture corpus. An ambiguous or missing identity means no safe source to pick — enrichment correctly returns nothing rather than guessing.

Extract facts

04

Haiku reads the source page and extracts only explicitly stated facts into a fixed field schema, with a verbatim supporting quote for each. There is no field for a score, a discount, or an instruction — structurally, nothing else can land here.

Verify facts

05

Two independent checks per fact: a deterministic quote-in-source substring check (free, catches fabricated quotes), then a batched Haiku pass judging entailment and flagging quotes that read as instructions rather than facts. Only what survives both becomes "verified".

Evidence-sufficiency gate

06

Ordinary code. Counts resolved ICP criteria against a floor. Below the floor: no score is ever computed — the lead returns the specific questions that would unblock it.

Score (if the gate is open)

07

Deterministic points-based scoring across 7 criteria, each with cited evidence. A veto on B2B-fit or region disqualifies regardless of the numeric total.

Propose CRM change set

08

A diff, never a write. Possible identity matches propose nothing — a human resolves the merge. Confident matches propose an update; new leads propose a create.

Draft outreach (gated)

09

Only for qualified bands, and only using the lead's own message plus verified facts. Every claim the draft makes is independently re-verified against that same grounding text — never trusted from the drafting call's self-report.

Audit + jobs

10

Every stage above writes a started/completed/failed audit event with duration, committed immediately so a poller sees stages land one at a time. A Postgres jobs table tracks attempts — the durable queue, not an external service.

//THE STACK

Boring where it should be boring

FastAPI + Jinja2 + SQLAlchemy, PostgreSQL (Docker Compose locally)
Claude Haiku for every model call, temperature pinned to 0
Durable job queue: a Postgres table with attempts/backoff, drained by a real worker process — no external queue service
Enrichment reads a seeded fictional “mini-web”, not the live internet — deterministic, $0, every citation a real in-app URL
Rate limiting + a race-safe daily spend cap (row-level locking, not read-then-write)

Extension point: workflow automation

A sales-ready qualification can fire a webhook to an external automation platform (n8n) to notify a rep, enrich a CRM record, or kick off a sequence — the pipeline already exposes a fire-and-forget hook (verdict/domain/notify.py) for exactly this, bounded and non-blocking so a downstream outage never affects a demo visitor. Deliberately not wired to a live workflow in the public demo — that stays inactive by design, not by omission.