Skip to content
Yetmgeta Redahegn
All work

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

  1. Tender portal
  2. Playwright fetcher
  3. n8n
  4. PostgreSQL
  5. Prefilter
  6. De-duplicate
  7. LLM qualification (AI evaluation / decision step)
  8. Verdict propagation
  9. 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.