# Planning Radar AI (`conceivable_extension/planning-radar-ai`) Actor

Real UK planning applications near a postcode — proximity-matched, classified, status-change tracked, and synthesized into plain-English summaries from real government data. Not raw council search — the diff/alert/explain layer on top of it.

- **URL**: https://apify.com/conceivable\_extension/planning-radar-ai.md
- **Developed by:** [joseph fadero](https://apify.com/conceivable_extension) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 1,000 application returned with summaries

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## 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

## Planning Radar AI

**The diff/alert/explain layer on top of UK planning data — not another raw extraction tool.** A May 2026 analysis scraping 2.6 million UK planning decisions found real, actionable variance between councils — approval-rate differences exceeding 20 percentage points and worsening decision times. The one existing Apify actor for UK planning data has 2 total users and functions as raw extraction. This is explicitly scoped as a Type 3 intelligence actor: real proximity matching, real status-change tracking, real classification, and plain-English synthesis — from real UK government data.

### No master index existed for this — designed as I built, documented for reuse

This is the first actor in a new "Radar" series (build order 1 of 4); the PRD referenced a shared "Radar architecture" master index that didn't exist yet. Per the user's direction, that shared diff/classify/explain pipeline was designed here and is meant to be reused by Actors 38-40 the same way `deltaStore.ts` (see below) has been reused across 5 actors now — not rebuilt from scratch each time.

### Phase 1 research — a real finding that reshaped scope

**`planning.data.gov.uk`** — the UK government's own official national planning-data aggregator (Department for Levelling Up, Housing and Communities) — turned out to be the right primary source: confirmed live, genuinely free and keyless, real per-application records (100,627 real rows at build time), with a documented `organisation_entity` filter and (for councils that provide it) real point geometry. Cross-validated live: a real Camden application's own embedded coordinates matched postcodes.io's independent geocode for the same postcode almost exactly.

**Coverage varies significantly by council** — confirmed live by sampling across the full dataset, not assumed: some councils (e.g. Doncaster) have 0% of records with real point/geometry data; others (Camden, Worthing) had 100% real point data across every sample checked. This actor covers the councils *confirmed* to have real, usable geometry — 2 at launch (Camden, Worthing) — not presented as national coverage. Adding a council means verifying its real point-data completeness first, the same way these two were checked.

### The finding that changed the PRD's "killer feature"

The PRD's central differentiator was developer entity resolution — a "repeat developer" signal from applicant/developer name matching. **Checked three real paths, live, and applicant/developer identity is not available from any of them:**

1. The national dataset's own field specification (`digital-land.github.io/specification/dataset/planning-application`) — 21 defined fields, none of them applicant/developer/agent name.
2. A real council's own bulk open-data export (Wigan's 2025 planning CSV, the PRD's own cited example) — same gap, no applicant field.
3. Free-text pattern matching against real application descriptions for company-name-like strings (e.g. "... Ltd", "... Developments") — tested against a real 100-record sample, zero matches.

This is very likely a real, structural, GDPR-driven choice by UK councils (individual applicants' names are personal data) rather than an oversight — but the practical result is the same: `applicantOrDeveloper` and `developerRepeatCount` are **always `null`** in this build's output, kept in the schema rather than removed so the gap is visible, not silently absent. `developer_lookup` mode from the original spec is dropped for the same reason — it would have nothing real to search on. The genuine differentiator that remains, and that this actor delivers in full, is the diff/proximity/classify/explain layer itself — which the existing 2-user competitor doesn't do at all.

### "AI" in the name — what it actually means here

`plainEnglishSummary` is generated by a real, deterministic, rule-based sentence generator built from real extracted facts (type, status, distance, scale, status-change) — **not a call to an external LLM.** No actor in this build program has wired in an LLM API (a real API-key/budget decision outside any single actor's scope), and this one doesn't either. "AI" refers to the intelligence *layer* — proximity matching, status-change detection, classification, synthesis — not literally an AI model writing the sentence. Said plainly here rather than left ambiguous.

### The shared "Radar" architecture (for Actors 38-40 to reuse)

- **Diff:** `src/shared/deltaStore.ts` — reused wholesale, unmodified, from Actor 26 (now used by 5 actors across two separate build programs: 26, 27, 34, 35, and this one).
- **Classify:** keyword-based classification against real free-text (`src/processors/classifier.ts`) — order-sensitive checks (specific phrases before generic ones), a lesson learned the hard way in a previous actor where the wrong order silently filtered out every active record.
- **Explain:** rule-based summary generation (`src/processors/summaryGenerator.ts`) — real facts in, readable sentences out, no external API.
- **Proximity:** standard haversine distance (`src/processors/geoDistance.ts`) against a free, keyless UK geocoder.

### Modes

| Mode | Behavior |
|---|---|
| `area_snapshot` | Real applications within `radiusMiles` of `postcode`, right now |
| `property_watch` | Same, plus `statusChangedSinceLastRun` from this actor's own stored history |

### A real, honest classification note

In a first real test (0.5 miles around a central Bloomsbury/Camden postcode), 76% of matched applications classified as `other`. Checked directly against the real description text before assuming a bug: these are genuinely minor works (dormer windows, condition discharges, satellite dish installations, listed-building refurbishment) in a historic, listed-building-dense area — an accurate reflection of that area's real planning activity, not a classifier defect. Confirmed by reading actual descriptions, not assumed.

### Output schema

```json
{
  "applicationId": "string",
  "councilArea": "string",
  "applicantOrDeveloper": "null (see above)",
  "developerRepeatCount": "null (see above)",
  "developmentType": "residential | commercial | mixed_use | change_of_use | other",
  "status": "submitted | approved | refused | under_review | appealed | withdrawn | null",
  "unitsOrScale": "string | null",
  "address": "string",
  "latitude": "number | null",
  "longitude": "number | null",
  "distanceFromWatchedPointMiles": "number | null",
  "policyReferences": ["string"],
  "statusChangedSinceLastRun": "boolean | null",
  "plainEnglishSummary": "string",
  "sourceUrl": "string",
  "checkedAt": "ISO timestamp",
  "fetchStatus": "success | failed",
  "errorMessage": "string | null"
}
```

`status` includes `withdrawn` — a real, common status this source returns that the original spec's 5-value enum didn't anticipate; kept rather than forced into a misleading nearby category.

### Setup note

No browser needed — both real sources (planning.data.gov.uk, postcodes.io) work over plain HTTP. Each council fetch is capped at 2,000 most-recent records (paginated) — this is a recent-activity radar, not a full historical archive; capped and documented, not silently incomplete.

### Pricing

Pay-per-event (PPE). Prices are configured in the Apify Console; event names below are the real ones this actor charges.

| Event | Fires when |
|---|---|
| `apify-actor-start` | Built-in, auto-billed on every run start. |
| `application-returned-basic` | An application is returned to the dataset. This is the actual charge for every successfully-returned application in this build — see note below. |
| `status-change-detected` | (property\_watch mode only) An application's status changed since this actor's last run against the same postcode/council. |
| `application-not-in-radius` | An application was found and evaluated but fell outside the requested `radiusMiles`. |
| `developer-pattern-identified` | Not currently reachable — see note below. |
| `apify-default-dataset-item` | Built-in default dataset-item event. Not currently reachable in this build — see note below. |

**Note on `application-returned-basic` vs the built-in `apify-default-dataset-item`:** the fully-enriched criteria for the built-in event (plain-English summary *and* developer repeat count both populated) can never be met by this actor today, because no verified free UK planning source exposes applicant/developer identity (see `applicantOrDeveloper`/`developerRepeatCount`, always `null`, above). Every successfully-returned application is therefore charged as `application-returned-basic` instead. If developer-entity resolution is ever added on top of a new data source, the fully-enriched branch (and `developer-pattern-identified`, charged when a developer is resolved across 3+ applications) becomes reachable without further code changes to the branching logic itself — only the developer-resolution step would need to be built.

Failed geocoding or a council-level fetch failure is never charged.

### n8n integration

- **Workflow A (trigger):** scheduled weekly run against a watched property/postcode list.
- **Workflow B (processing):** alert on `statusChangedSinceLastRun: true` or a new application entering the watched radius, using `plainEnglishSummary` as the alert body.

# Actor input Schema

## `mode` (type: `string`):

area\_snapshot: real applications within radiusMiles of postcode right now. property\_watch: same, plus statusChangedSinceLastRun computed from this actor's own stored run history. (developer\_lookup from the original spec is not included — see README for why: no verified free UK planning source exposes applicant/developer identity.)

## `postcode` (type: `string`):

A UK postcode, e.g. "WC1H 9AQ". Geocoded via postcodes.io (real, free, official ONS data).

## `radiusMiles` (type: `number`):

Search radius around the postcode.

## `councilAreas` (type: `array`):

Optional. Restrict to specific covered councils (see README for the current list — a real, curated, growing set, not national coverage yet). Leave blank to search all covered councils.

## `developmentTypes` (type: `array`):

Optional filter, classified from each application's real description text.

## Actor input object example

```json
{
  "mode": "area_snapshot",
  "postcode": "WC1H 9AQ",
  "radiusMiles": 1
}
```

# Actor output Schema

## `resultsDatasetUrl` (type: `string`):

Dataset of planning-application records produced by this run.

# 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 = {
    "postcode": "WC1H 9AQ"
};

// Run the Actor and wait for it to finish
const run = await client.actor("conceivable_extension/planning-radar-ai").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 = { "postcode": "WC1H 9AQ" }

# Run the Actor and wait for it to finish
run = client.actor("conceivable_extension/planning-radar-ai").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 '{
  "postcode": "WC1H 9AQ"
}' |
apify call conceivable_extension/planning-radar-ai --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,conceivable_extension/planning-radar-ai"
        }
    }
}
```

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/bKi7klcBcyKa48fnH/builds/0Vg0HPHQAoKsuCLoo/openapi.json
