CURRENT PUBLIC OPPORTUNITIES. CLEAR FIT. CLEAR NEXT ACTION.
JobFilter is a qualification layer for small UK construction and maintenance firms. The current /find-jobs procurement path uses Find a Tender OCDS releases and ranks public notices by trade, location evidence, stage, deadline, and buyer context. It does not sell exclusive access or promise an award.
- Live scans may return no result when no verified opportunity matches the trade and patch.
- Internal sample records are blocked from production, even if a runtime toggle is set incorrectly.
- Planning, energy, company, paid checkout, and WhatsApp coverage depend on provider credentials and activation checks; they are not treated as live merely because code exists.
/test,/test/intake,/dev-portal, and/api/statusare development-only surfaces and return 404 in production.
| Readiness | Sources | Customer use |
|---|---|---|
| Live/current | Find a Tender (FTS) OCDS release packages | Primary current-notice scan |
| Legacy/backfill | Contracts Finder | Pre-February-2025 history/transition only; excluded from current scans |
| Experimental | Planning Data, Public Contracts Scotland, Sell2Wales and other unproven adapters | Disabled by default and absent from primary promises |
| Credential-required | EPC, Companies House, Stripe and WhatsApp | Only available after credentials, configuration and live verification |
- A Next.js production application with public opportunity ingestion, normalisation, deduplication, scoring, and free/paid response shaping.
- Server-side redaction that keeps buyer, deadline, exact value, action route, and exact scoring depth out of free responses.
- Source health and partial-failure handling: one failed provider does not invent a fallback job or crash the scan.
- Supabase account/data plumbing, Stripe checkout/webhook routes, and Meta WhatsApp delivery code with explicit configuration failures.
- Release gates for dependency security, type safety, lead-quality rules, postcode handling, free-tier privacy, production-only route protection, and a full production build.
Official sources
-> fetchers
-> normalise
-> deduplicate
-> score and rank
-> redact by access tier
-> dashboard / delivery
Key areas:
leadEngine/— source registry, fetchers, postcode logic, normalisation, scoring, quality audit, and scan orchestration.server/routes/— Express-compatible APIs mounted through the Next.js catch-all API route.app/api/— native Next.js handlers for account, alerts, payments, waitlist, and delivery workflows.src/pages/andsrc/components/— product UI, scanner, account surfaces, trust pages, and trade landing pages.supabase/migrations/— versioned database changes and row-level access policies.tests/regression/— runnable product, privacy, quality, and runtime checks.
- Source volume and trade/location fit vary with what buyers publish; an empty scan is a valid outcome.
- EPC, Companies House, Stripe pricing, and WhatsApp delivery require provider credentials and live environment verification.
- Public tenders may be pursued by other suppliers; territory routing must never be presented as exclusive notice access.
- The current route surface is larger than the intended flagship journey and still needs product simplification.
No API key is required for the live Find a Tender search.
Optional:
PORT=3000
NODE_ENV=development
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=Production data is moving to Supabase:
Run supabase/migrations/20260522_vercel_supabase_saas.sql
Keep SUPABASE_SERVICE_ROLE_KEY server-only.
npm installnpm run devOpen:
http://localhost:3000
http://localhost:3000/find-jobs
npm run lint
npm audit
npx tsx tests/regression/production-source-safety-regression.mjs
npx tsx tests/regression/postcode-filter-regression.mjs
node tests/regression/free-scanner-redaction-regression.mjs
npx tsx tests/regression/lead-engine-quality-regression.mjs
npm run build
node tests/regression/production-runtime-regression.mjsLive lead scanner endpoint:
POST /api/leads/search
Request:
{
"postcode": "B14 7QH",
"trade": "electrical",
"radiusMiles": 25
}Response shape:
{
"ok": true,
"source": "lead_engine",
"count": 0,
"region": "West Midlands",
"outward": "B14",
"leads": [],
"errors": []
}Waitlist endpoint:
POST /api/waitlist
{
"name": "A Tradesman",
"trade": "Electrician",
"contact": "name@example.com",
"source": "site"
}Expected pattern:
{ "ok": true }Intake scoring endpoint:
POST /api/intake/score
{
"jobType": "Plumbing",
"urgency": "Emergency",
"postcode": "B14 7QH",
"phone": "07123456789",
"details": "Leaking boiler",
"hasPhotos": true
}Success query:
curl -s -X POST http://localhost:3000/api/leads/search \
-H "Content-Type: application/json" \
-d "{\"postcode\":\"B14 7QH\",\"trade\":\"electrical\",\"radiusMiles\":25}"Expected pattern:
{
"ok": true,
"source": "lead_engine",
"count": 1,
"region": "West Midlands",
"outward": "B14",
"leads": [{ "source": "FTS" }],
"errors": []
}Empty result query:
curl -s -X POST http://localhost:3000/api/leads/search \
-H "Content-Type: application/json" \
-d "{\"postcode\":\"BT1 5GS\",\"trade\":\"roofing\",\"radiusMiles\":10}"Expected pattern:
{
"ok": true,
"source": "lead_engine",
"count": 0,
"region": "Northern Ireland",
"outward": "BT1",
"leads": [],
"errors": []
}Invalid postcode query:
curl -s -X POST http://localhost:3000/api/leads/search \
-H "Content-Type: application/json" \
-d "{\"postcode\":\"BAD\",\"trade\":\"electrical\",\"radiusMiles\":25}"Expected pattern:
{
"ok": false,
"source": "lead_engine",
"count": 0,
"region": "",
"outward": "",
"leads": [],
"errors": ["valid UK postcode required"]
}Waitlist:
curl -s -X POST http://localhost:3000/api/waitlist \
-H "Content-Type: application/json" \
-d "{\"name\":\"A Tradesman\",\"trade\":\"Electrician\",\"contact\":\"name@example.com\",\"source\":\"readme\"}"Intake score:
curl -s -X POST http://localhost:3000/api/intake/score \
-H "Content-Type: application/json" \
-d "{\"jobType\":\"Plumbing\",\"urgency\":\"Emergency\",\"postcode\":\"B14 7QH\",\"phone\":\"07123456789\",\"details\":\"Leaking boiler\",\"hasPhotos\":true}"/ Home
/find-jobs Live scanner
/pricing Pricing
/codex Technical editorial page
/free-tools Free quote, job, and diesel tools
/tips Tips for tradesmen
/vantage Bid/presentation advantage
/vicinity Past-work marketing advantage
/privacy Privacy policy
/terms Terms
/health Frontend health page
/api/health API health JSON
npm run lint
npm run test:fts
npm run buildVercel requirements:
- Framework preset: Next.js
- Project root:
JobFilterV1 - Build command:
npm run build - Environment variables from
.env.example - Supabase migration applied before production data writes
- Find a Tender notices do not always include exact delivery postcodes, values, contact details, or eligibility evidence.
- Radius filtering uses delivery-postcode centroids and fails closed when a notice does not provide enough delivery evidence.
- Contracts Finder is retained only for documented legacy/backfill use and is not queried by the current scanner.
- Planning, EPC and other adapters stay outside the primary promise until live coverage and mappings are proven.
