# UK Planning Leads Finder (`porcelain_vista/uk-planning-lead-qualifier`) Actor

Find contractor opportunities in recent UK planning applications across 18 trades. Exact filters, local semantic ranking, and explainable fit scores.

- **URL**: https://apify.com/porcelain\_vista/uk-planning-lead-qualifier.md
- **Developed by:** [Vlad Vitvitsky](https://apify.com/porcelain_vista) (community)
- **Categories:** Lead generation, Automation, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## UK Planning Leads Finder

Find potential contractor jobs in recent UK planning applications without reading every municipal record by hand.

Choose a trade and planning authorities. The Actor builds the source query from a curated service catalog, collects public applications, combines literal BM25 matching with a packaged local MiniLM model, and checks that each result contains direct evidence of a selected service.

No external AI key is required. The ranking model runs inside the Actor.

### Supported contractor packs

The catalog contains 18 trade packs and 63 selectable services:

- roofing; HVAC and ventilation; signage and shopfronts; windows, doors, and glazing
- tree surgery; solar PV and battery storage; EV charging
- extensions and general building; demolition; fencing and gates
- landscaping and groundworks; drainage; facades, cladding, and rendering
- heritage restoration; telecommunications; external lighting and security
- highways, parking, and access; industrial and agricultural buildings

Commercial roofing also has specialized rules for common false matches such as roof-mounted plant, solar panels, rooflights, loft conversions, and minor residential roof references. The other packs use a shared recall-first service-evidence qualifier and expose weaker stages as risks instead of silently hiding possible work.

### Quick start

Select one trade pack and one or more planning authorities:

```json
{
  "authorities": ["Leeds"],
  "tradePack": "hvac_ventilation",
  "serviceIds": ["commercial_extraction", "heat_pumps"],
  "recentDays": 30,
  "maxCandidates": 30,
  "maxResults": 10,
  "onlyNew": false,
  "includeRejected": false,
  "scoring": {
    "mode": "catalog_hybrid",
    "minimumScore": 45,
    "transformerBatchSize": 32
  }
}
```

Leave `serviceIds` empty to search all standard services in the selected trade. `minimumScore: 45` is the recall-first default. To audit borderline and rejected records, set `includeRejected` to `true`; to inspect every ranked candidate, temporarily use `minimumScore: 0`.

### How it works

1. Expands the selected services into planning-language source phrases.
2. Collects recent records from public UK planning data through PlanIt.
3. Normalizes fields and groups related application records into projects.
4. Applies exact filters for stage, application type, size, required phrases, and excluded phrases.
5. Ranks candidates with BM25 and several service-specific MiniLM comparisons.
6. Applies an absolute fit check based on explicit service evidence, proposed-work wording, and application stage.
7. Writes accepted leads to the Dataset and run diagnostics to `RUN_SUMMARY`.

The source phrases provide recall, BM25 rewards literal matches, MiniLM improves ordering by meaning, and the final guardrail prevents a first-place result from receiving a high `fitScore` merely because it was best in a small run.

### Example result

```json
{
  "authority": "Leeds",
  "description": "...installation of side-mounted extraction and ventilation flue...",
  "fit": true,
  "fitScore": 83,
  "workIntent": "commercial_extraction",
  "combinedRank": 1,
  "bm25Rank": 1,
  "semanticRank": 2,
  "matchedServices": ["Commercial extraction"],
  "intentEvidence": ["extraction", "ventilation flue"],
  "risks": [],
  "sourceUrl": "https://publicaccess.example/..."
}
```

Important fields:

- `fitScore` - absolute evidence-based qualification score; not a probability of winning work
- `combinedRank` - position from BM25 and semantic reciprocal-rank fusion within this run
- `workIntent`, `matchedServices`, and `intentEvidence` - the service and literal evidence found
- `whyMatch`, `risks`, and `unknowns` - explanations for review or downstream automation
- `sourceUrl` - link to the authority record where available
- `isNew` and `firstSeenAt` - monitoring state for repeated runs

### Exact keyword controls

Use `filters.requiredTerms` when a word or phrase must literally occur in the description. Set `requiredTermMode` to `any` or `all`, and use `filters.excludedTerms` for deterministic exclusions. These filters run before semantic ranking.

For example, a solar installer can select the Solar pack and still require `warehouse` if its workflow must only receive warehouse projects.

### Monitoring new opportunities

After an initial calibration run, set `onlyNew` to `true`. The Actor remembers previously returned projects for the same search configuration in an Apify Key-Value Store, making scheduled Tasks suitable for feeds, webhooks, Make, Zapier, or CRM workflows.

### Validation and limitations

- The catalog was checked against 1,154 saved real PlanIt applications from Leeds and Manchester. Literal examples were observed for 54 of 63 services, and all 17 packs with candidates placed a plausible direct job first in the latest full local spot-check.
- This is development evidence, not a nationwide accuracy guarantee. Industrial/agricultural buildings and eight other individual services need broader multi-authority validation.
- A planning application is an early project signal, not a tender, contract award, confirmed budget, or request for quotation.
- Conditions, amendments, retrospective records, and retained work can still be useful signals, but the Actor lowers their fit score and explains the risk.
- Coverage, field quality, and freshness vary by planning authority.
- The Actor currently reads application metadata and descriptions; it does not download or interpret attached drawings or PDFs.

Keep early runs bounded with `maxCandidates`, review the explanations, and create a scheduled Task only after the selected trade and authority produce useful results.

# Actor input Schema

## `tradePack` (type: `string`):

The Actor uses a curated vocabulary and semantic descriptions for this trade.

## `serviceIds` (type: `array`):

Leave empty to monitor all standard services in the selected trade. If used, select services belonging to that trade.

## `targetBuildingTypes` (type: `array`):

Examples: warehouse, restaurant, school. Leave empty for the trade defaults.

## `authorities` (type: `array`):

One or more PlanIt authority names, for example Leeds, Hackney or Manchester.

## `recentDays` (type: `integer`):

Limit collection to applications received within this many days.

## `onlyNew` (type: `boolean`):

Seen-project history is scoped to this search and persisted across cloud runs.

## `filters` (type: `object`):

Optional deterministic rules applied before semantic ranking.

## `maxCandidates` (type: `integer`):

Maximum number of applications collected before qualification and ranking.

## `maxResults` (type: `integer`):

Maximum number of accepted leads written to the Dataset.

## `sourceSearch` (type: `string`):

Leave blank to generate the broad source query from the selected services.

## `scoring` (type: `object`):

Control how accepted planning applications are ranked for business fit.

## `includeRejected` (type: `boolean`):

Write rejected projects and their rejection reasons to the Dataset.

## Actor input object example

```json
{
  "tradePack": "commercial_roofing",
  "serviceIds": [],
  "targetBuildingTypes": [],
  "authorities": [
    "Leeds"
  ],
  "recentDays": 30,
  "onlyNew": false,
  "filters": {
    "states": [
      "Undecided",
      "Permitted",
      "Conditions"
    ],
    "applicationTypes": [],
    "minimumProjectSize": "Small",
    "requiredTerms": [],
    "requiredTermMode": "any",
    "requiredTermScope": "description",
    "excludedTerms": []
  },
  "maxCandidates": 30,
  "maxResults": 10,
  "scoring": {
    "mode": "catalog_hybrid",
    "minimumScore": 45,
    "transformerBatchSize": 32
  },
  "includeRejected": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("porcelain_vista/uk-planning-lead-qualifier").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("porcelain_vista/uk-planning-lead-qualifier").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call porcelain_vista/uk-planning-lead-qualifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,porcelain_vista/uk-planning-lead-qualifier"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/S4Hd98763AJvvb7sa/builds/TfUyIBDCgca9MtmDW/openapi.json
