Client project · Blih Marketing and Communication PLC
AI Tender Intelligence Automation
Replaced a team's manual tender research with a pipeline that fetches, enriches, qualifies and schedules tenders automatically.
- Role
- AI Automation Engineer
- Status
- Status: Delivered
- Code
- Private client repository
- Stack
- n8n
- PostgreSQL
- Node.js
- TypeScript
- Playwright
- Docker Compose
- LLM qualification
- Google Calendar API
- Gmail API
The problem
Blih is both a marketing/communications agency and a software development and IT outsourcing company, and its team was finding and screening tenders by hand. The first automated version judged tenders on their titles alone. The portal's list endpoint returned empty descriptions for all 197 tenders, so:
- the keyword prefilter dropped relevant tenders with generic titles (like "Procurement of Consultancy Services") before the AI ever saw them;
- the AI's calls were brittle: two copies of the same tender, differing only by one capital letter, got opposite verdicts;
- 32 of the 197 tenders (about 16%) were near-duplicates, which wasted AI calls and caused those inconsistent verdicts.
What I built
Playwright fetcher service (Node.js/TypeScript)
Logs in to the tender portal, saves and reuses the browser session, and logs in again when authentication expires. It parses the portal's JSON/HTML payloads, normalizes tenders and removes duplicates by source ID. The internal endpoints (
/health,/session-status,/fetch-tenders) are protected with a shared secret.Description enrichment
Fetches each tender's detail page, 5 at a time with a 15-second timeout, and converts the HTML to clean text. It fails soft: one bad page never breaks the run.
n8n workflow (32 nodes)
Fetch → upsert into PostgreSQL (never wiping a stored description) → fail-open prefilter → de-duplicate to one representative per normalized title → LLM qualification → propagate the verdict to duplicates → Google Calendar deadline events → Gmail summary → run logging.
Fail-open prefilter
Keeps anything with a marketing/communications or software/IT/outsourcing angle and only drops clear non-fits.
AI as the authoritative filter
The LLM judges each tender on its described scope of work against Blih's two service lines, and gives a firm qualified/rejected call unless the case is genuinely ambiguous. Temperature 0, and the model can be swapped with one environment variable.
Privacy
The model only receives compact tender data (title, description, client, deadline, URL), never cookies, auth data or raw HTML.
Idempotent side effects
Calendar events are created only once per tender, and every workflow run is recorded.
PostgreSQL
Schema with 4 ordered migrations (tenders, qualifications, calendar events, workflow runs), reusable SQL snippets, and an operations runbook.
Architecture
- Tender portal
- Playwright fetcher
- n8n
- PostgreSQL
- Prefilter
- De-duplicate
- LLM qualification (AI evaluation / decision step)
- Verdict propagation
- Google Calendar + Gmail
Green marks an AI evaluation / decision step.
Engineering highlights
- Descriptions are enriched before the AI sees a tender, so verdicts rest on the scope of work, not the title
- Near-duplicates are judged once and the verdict is propagated, so duplicates get one consistent call
- Side effects are idempotent and every run is logged
Results & verification
- Live smoke test: 15 of 15 fetched tenders received real, HTML-stripped descriptions
- Verdict propagation dry-run against a real duplicate pair in a rolled-back database transaction
- Workflow JSON and wiring validated, and the new and changed SQL queries syntax-checked
Contact
Have something to build?
Tell me what you want automated or which AI feature you need, and I'll reply with a concrete plan.
Addis Ababa, Ethiopia (UTC+3). Full overlap with Europe, morning overlap with the US.